In the second part of the blog series https://blog.kie.org/2020/12/how-to-integrate-your-kogito-application-with-trustyai-part-2.html we showed how to setup the OpenShift cluster that will host the TrustyAI infrastructure and the Kogito application we created in the first part https://blog.kie.org/2020/12/how-to-integrate-your-kogito-application-with-trustyai-part-1.html . In this third and last part of our journey, we are going to demonstrate how to deploy the TrustyAI infrastructureRead more →
KIE
Featured Posts: AI
How to integrate your Kogito application with TrustyAI – Part 3
In the second part of the blog series https://blog.kie.org/2020/12/how-to-integrate-your-kogito-application-with-trustyai-part-2.html we showed how to setup the OpenShift cluster that will host the TrustyAI infrastructure and the Kogito application we created in the first part https://blog.kie.org/2020/12/how-to-integrate-your-kogito-application-with-trustyai-part-1.html .
In this third and last part of our journey, we are going to demonstrate how to deploy the TrustyAI infrastructure and the Kogito application we created so far.
Let’s have a look at the TrustyAI infrastructure, just to have an high level overview of the services that we are going to deploy.

In the yellow box the Kogito application is represented: it contains our DMN model and every time a decision is evaluated, a new tracing event is generated. This event contains all the information that the TrustyAI services need to calculate the explainability and keep track of inputs/outputs of each decision.
The tracing events are then consumed by the Trusty Service, which stores all the data and makes it available for the frontend (alias the AuditUI). It also communicates with the Explainability Service: in a nutshell, starting from the decision taken by the Kogito application, it creates many different new decisions perturbing the original one. Once all of the new decisions have been evaluated by the Kogito application, a machine learning model is trained to figure out the most relevant features that contributed to the original outcome.
Deployment of the Trusty Service
Let’s deploy first of all the Trusty service using the Kogito Operator: go to Operators -> Installed Operators -> Kogito -> Kogito Service -> Create KogitoSupportingService
.
Create a new resource named trusty-service
, select the Resource Type
TrustyAI
and in the Infra
section add the two KogitoInfra
resource names that we created so far: kogito-kafka-infra
and kogito-infinispan-infra
.

Deployment of the AuditUI
The frontend, i.e. the AuditUI, consumes some API exposed by the Trusty Service: by consequence it needs to know what is the URL of the Trusty Service. This information has to be injected using an environment variable called KOGITO_TRUSTY_ENDPOINT
.
On OpenShift, it is possible to get the URL of the Trusty Service we have just deployed under Networking -> Routes
. Copy the URL.

On the Kogito Operator console, create a new KogitoSupportingService
named trustyui-service
, select the Resource Type
TrustyUI
and add the environment variable KOGITO_TRUSTY_ENDPOINT
with the URL of the Trusty Service you’ve just copied.

Deployment of the Explainability Service
On the Kogito Operator console, once again create a new KogitoSupportingService
named explainability-service
with Resource Type
Explainability
. The KogitoInfra
to be linked is only kogito-kafka-infra
.

Deployment of the Kogito Application
The TrustyAI infrastructure has been deployed, and we’re ready to deploy the Kogito application finally.
Create a new Kogito Service custom resource with name my-kogito-application-service
and label app=my-kogito-application-service
. The Image should be the tag you used for the docker image that contains your Kogito application (the one that we created in the first video/blogpost). If you have used https://quay.io/
, it should be something like quay.io/<your_username>/my-kogito-application:1.0
.
The last step is to add the string kogito-kafka-infra
in the Infra
section.

Execute, Audit, Explain
We’re ready to play with the Kogito application, look at the AuditUI and investigate the explainability results!
Under Networking -> Routes
click on the URL of the Kogito Application: a new tab should be opened.

Let’s execute a request to the /LoanEligibility
endpoint to evaluate our DMN model. If you’d like, you can use the swagger ui at http://<your_kogito_application_url>/swagger-ui
.
A sample payload is the following:
{"Bribe": 1000,"Client": {"age": 43,"existing payments": 100,"salary": 1950},"Loan": {"duration": 15,"installment": 180}, "SupremeDirector": "Yes"}

From the OpenShift console under Networking -> Routes
, open the AuditUI URL. You should be able to see the executions, the explainability results and all the inputs/outputs of each decision. Enjoy!

How to integrate your Kogito application with TrustyAI – Part 1
How can you audit a decision out of your new Kogito application? It’s pretty simple: in this series of articles, we are going to demonstrate how to create a new Kogito application and how to deploy the TrustyAI infrastructure on an OpenShift cluster.If you are new to TrustyAI, we suggest you read this introduction: https://blog.kie.org/2020/06/trusty-ai-introduction.htmlWithRead more →
How can you audit a decision out of your new Kogito application? It’s pretty simple: in this series of articles, we are going to demonstrate how to create a new Kogito application and how to deploy the TrustyAI infrastructure on an OpenShift cluster.
If you are new to TrustyAI, we suggest you read this introduction: https://blog.kie.org/2020/06/trusty-ai-introduction.html
With the additional capabilities of TrustyAI, you will get a nice overview of all the decisions that have been taken by the Kogito application, as well as a representation of why the model took those decisions (i.e. the explanation of the decisions).
At the moment, TrustyAI provides via Audit UI two main features:
1) The complete list of all the executions of the DMN models in the Kogito application.

2) The details of each execution, including all the inputs, all the internal outcomes and their explainability.

In order to achieve this goal, we’ll go through these three steps:
1) Create the Kogito application, enable the so-called tracing addon and create the docker image (the subject of the current blogpost).
2) Prepare your OpenShift cluster.
3) Deploy the infrastructure.
Let’s go into the details of the first step!
Create the kogito application and enable the tracing addon
Let’s assume you have already created your DMN model that contains your business logic using your preferred channel (http://dmn.new/ for instance).
For the sake of the demo, we will use the following DMN model: https://raw.githubusercontent.com/kiegroup/kogito-examples/master/dmn-tracing-quarkus/src/main/resources/LoanEligibility.dmn .
You can create a new Kogito application using maven:
mvn archetype:generate \
-DarchetypeGroupId=org.kie.kogito \
-DarchetypeArtifactId=kogito-quarkus-archetype \
-DgroupId=com.redhat.developer -DartifactId=my-kogito-application \
-DarchetypeVersion=1.0.0.Final \
-Dversion=1.0-SNAPSHOT
And then put your DMN model under the folder my-kogito-application/src/main/resources
(and delete the default BPMN resource). The project structure should look like the following:

The Kogito tracing addon is needed to export the necessary information about the decisions taken by the model. Modify the pom.xml
file to import the following dependency:
<dependency>
<groupId>org.kie.kogito</groupId>
<artifactId>tracing-decision-quarkus-addon</artifactId>
</dependency>
The last thing to do is to configure the tracing addon: add the following lines to the file my-kogito-application/src/main/resources/application.properties
.
# Kafka Tracing
mp.messaging.outgoing.kogito-tracing-decision.group.id=kogito-runtimes
mp.messaging.outgoing.kogito-tracing-decision.connector=smallrye-kafka
mp.messaging.outgoing.kogito-tracing-decision.topic=kogito-tracing-decision
mp.messaging.outgoing.kogito-tracing-decision.value.serializer=org.apache.kafka.common.serialization.StringSerializer
# Kafka Tracing Model
mp.messaging.outgoing.kogito-tracing-model.group.id=kogito-runtimes
mp.messaging.outgoing.kogito-tracing-model.connector=smallrye-kafka
mp.messaging.outgoing.kogito-tracing-model.topic=kogito-tracing-model
mp.messaging.outgoing.kogito-tracing-model.value.serializer=org.apache.kafka.common.serialization.StringSerializer
For a more detailed overview on the tracing addon, a dedicated blogpost is available here: https://blog.kie.org/2020/11/trustyai-meets-kogito-the-decision-tracing-addon.html .
You can use the Kogito operator if you are running on OpenShift to automatically execute the build using the custom resource KogitoBuild
.

As an alternative approach in this demo we are going to deploy it using the KogitoService
one: this custom resource deploys a Kogito application from a docker image on a remote hub. Let’s create a docker image from our project then!
Create the jar application with
mvn clean package -DskipTests
And then use the following Dockerfile
to build a new docker image
FROM quay.io/kiegroup/kogito-quarkus-jvm-ubi8:latest
COPY target/*-runner.jar $KOGITO_HOME/bin
COPY target/lib $KOGITO_HOME/bin/lib
Assuming you have an account on docker hub (https://hub.docker.com/), build your image with
docker build --tag <your_username>/my-kogito-application:1.0 .
And then push it to the hub
docker push <your_username>/my-kogito-application:1.0
The second part is here https://blog.kie.org/2020/12/how-to-integrate-your-kogito-application-with-trustyai-part-2.html
TrustyAI meets Kogito: the decision tracing addon
New to Kogito? Check out our “get started” page and get up to speed! 😉 This post presents the decision tracing addon: a component of the Kogito runtime quite relevant for the TrustyAI initiative (introduced here and here). One of the key goals of TrustyAI is to enable advanced auditing capabilities, which, as written inRead more →
New to Kogito? Check out our “get started” page and get up to speed! 😉
This post presents the decision tracing addon: a component of the Kogito runtime quite relevant for the TrustyAI initiative (introduced here and here).
One of the key goals of TrustyAI is to enable advanced auditing capabilities, which, as written in the second introductory post, “enables a compliance officer to trace the decision making of the system and ensure it meets regulations”.
The capability to export auditable information whenever a key event occurs is the first mandatory feature that such a system must provide in order to achieve this goal. This is exactly the purpose of the decision tracing addon.
Key concepts
The addon focuses on Kogito applications exposing DMN models (hence the “decision” in the name). Every time a model is executed, it emits a TraceEvent containing all the relevant information about the execution.
Here are some key concepts:
- It works for Kogito applications based on both Quarkus and Spring Boot.
- The TraceEvent is wrapped in a CloudEvent envelope.
- TraceEvents are sent as JSON to a Kafka topic.
The Trusty Service is the designated component of TrustyAI that consumes these events. With Kafka acting as a decoupler, however, custom consumers can be written to match any specific need.
How to use it
The addon has been available as part of Kogito since v0.17.0
. It leverages the code generation capabilities of Kogito to minimize the effort required to enable it. There are only two steps:
- Add it to the dependencies of your project. Make sure to pick the flavour that matches the underlying framework of your Kogito service.
- Add some properties to configure the connection to Kafka. They currently differ depending on the flavour, so pick the right ones (check the examples below).
Note: be aware that API changes may still occur in the next releases. Also, bugs may be there (that’s code after all, isn’t it?). If you find one, let us know via Kogito Jira.
Usage with Quarkus
- Here is the dependency to add to your
pom.xml
(version is automatically taken fromkogito-bom
):
<dependency>
<groupId>org.kie.kogito</groupId>
<artifactId>tracing-decision-quarkus-addon</artifactId>
</dependency>
- The Quarkus addon uses MicroProfile Reactive Messaging under the hood, so the configuration follows the same rules. Here are the properties:
# Where to find the Kafka broker
kafka.bootstrap.address=localhost:9092
# These two properties must be set with these exact values
mp.messaging.outgoing.kogito-tracing-decision.connector=smallrye-kafka
mp.messaging.outgoing.kogito-tracing-decision.value.serializer=org.apache.kafka.common.serialization.StringSerializer
# These values can be changed with your kafka group ID and topic
# If the topic doesn't exist, the addon tries to create it automatically
mp.messaging.outgoing.kogito-tracing-decision.group.id=kogito-runtimes
mp.messaging.outgoing.kogito-tracing-decision.topic=kogito-tracing-decision
Example with Quarkus
A detailed example on how to use the decision tracing addon with Quarkus can be found here in our kogito-examples
repository.
Usage with Spring Boot
- Here is the dependency to add to your
pom.xml
(version is automatically taken fromkogito-springboot-starter
):
<dependency>
<groupId>org.kie.kogito</groupId>
<artifactId>tracing-decision-springboot-addon</artifactId>
</dependency>
- Here are the properties:
# Where to find the Kafka broker
kogito.addon.tracing.decision.kafka.bootstrapAddress=localhost:9092
# The topic name
kogito.addon.tracing.decision.kafka.topic.name:kogito-tracing-decision
# If the topic doesn't exist, the addon tries to create it automatically
# These two additional properties can configure the auto generation
kogito.addon.tracing.decision.kafka.topic.partitions=1
kogito.addon.tracing.decision.kafka.topic.replicationFactor=1
Example with Spring Boot
A detailed example on how to use the decision tracing addon with Spring Boot can be found here in our kogito-examples
repository.
Next steps
We’re only at the beginning of the TrustyAI journey. If you want to be part of it with us, stay tuned for more news!
Thanks for reading.
An introduction to TrustyAI Explainability capabilities
In this blog post you’ll learn about the TrustyAI explainability library and how to use it in order to provide explanations of “predictions” generated by decision services and plain machine learning models. The need for explainability Nowadays AI based systems and decision services are widely used in industry in a wide range of domains, likeRead more →
In this blog post you’ll learn about the TrustyAI explainability library and how to use it in order to provide explanations of “predictions” generated by decision services and plain machine learning models.
The need for explainability
Nowadays AI based systems and decision services are widely used in industry in a wide range of domains, like financial services and health care. Such large adoption poses some concerns about how much we, as humans, can rely on these systems when they are involved in taking important decisions. Are such services fair when making decisions ? What are such systems giving importance to, when providing recommendations in supporting decision makers ? All in all, can we trust them ?
The above issues particularly affect machine learning techniques like deep learning which, more than others, are considered “opaque” as they’re hard to interpret and understand. By just looking at the inner workings (e.g. neurons’ activations) of neural networks it is not easy to guess what the network is giving more importance to, when performing a prediction. Say an AI system to detect SARS-CoV-2 in chest x-ray images predicts Covid-19 is present in one such image, can a doctor trust that enough to decide upon further treatment? If the system also highlights portions of the image that are responsible for the final prediction, a doctor can more easily double check and decide if what the system is basing its output makes sense from the clinical perspective too and decide whether the prediction can be trusted. The highlighted portions of such an image are an example of an explanation, an interface between the human and the system, a human understandable description of an (AI) system prediction internals. Depending on the system (and user) at hand, different kinds of explanations might work best. For example a bank having an automated credit card request approval system may need to provide explanations too. Imagine that the user compiles a form with information about its financial situation and family. If a user gets its credit card approval request rejected, one might want to know the rationale behind the rejection.
An explanation of a credit card request having been rejected
A useful explanation in this case would show how the input data typed into the form by the user influenced the decision (rejection). As an example in the above picture the explanation is telling us that the data about the user age, the fact that it owns (or doesn’t own) a car and its income had a negative impact on the decision (hence being the major causes for rejection). On the other hand other information about the number of children and the fact that it owns a realty (or doesn’t own it) might have favoured an approval.
Explainable Artificial Intelligence (aka XAI or Explainability) is a research area of Artificial Intelligence that focuses on trying to make opaque AI systems more interpretable, understandable for different stakeholders, in order to make them trustworthy and allow humans to trust them, especially in sensitive processes that affect humans in their real lives. In the remainder of this post we’ll consider those systems used to make human impacting decisions (often based on AI techniques) as black boxes; you’ll learn about a way to provide explanations for their outputs. Note also that the insights provided by explanations are helpful in identifying what to fix in a system. For example you might find out that the credit card approval system is unnecessarily negatively biased towards users who own a car.
Local Interpretable Model agnostic Explanations
A quite well known explainability technique is called Local Interpretable Model agnostic Explanations (aka LIME). LIME provides a tool to explain the predictions of a black box classifier (but also works with regression models). It can be used seamlessly on any type of classifier; for example, a text classifier would get explanations based on which words were contributing most to the prediction output, an image classifier would get explanations based on which image patches were contributing most to the prediction output.
The way LIME generates explanation is by perturbing inputs, for example randomly dropping features from an input, and performing classification with such slightly modified inputs. The outputs and the sparse version of the perturbed inputs (e.g. a binary feature presence vector) are used to train a linear regression model, local with respect to the original input. Once training is done, sparse feature weights are analyzed in order to correlate them to the predictions, for example positive outputs are correlated to weights whose value is larger).
In summary you can use LIME to generate explanations like the one from the picture above, that explains a single output (the credit card approval system rejection). This kind of explanation is usually referred to as a local explanation. Explanations that provide feature level importance scores on a single prediction are usually referred to as saliency explanations.
TrustyAI Explainability
Kogito is a cloud-native business automation technology for building cloud-ready business applications, with a focus on hybrid cloud scenarios.
Kogito allows you to build and deploy a cloud application as the composition of different domain specific services.
In this context Kogito also provides a set of addons that you can include in a Kogito application.
The TrustyAI initiative in Kogito provides a set of services that aim to provide monitoring and explainability capabilities to such applications.
We implemented an explainability library to deliver such services.
You can use our own implementation of LIME by instantiating a LimeExplainer
.
int noOfSamples = 100; // number of perturbed samples to be generated
int noOfPerturbations = 1; // min number of perturbations to be performed for each input
LimeExplainer limeExplainer = new LimeExplainer(noOfSamples, noOfPerturbations);
LimeExplainer
takes an input PredictionProvider
and a Prediction
to be explained and (asynchronously) generates a saliency map (Map<String, Saliency>
). The saliency map contains a Saliency
object for each single output produced in a single Prediction
(e.g. it is common for DMN models to have multiple outputs). A Saliency
contains a FeatureImportance
object for each Feature
seen in PredictionInput
attached to the input Prediction
. A FeatureImportance
contains the score (the importance) attached to the related Feature
.
List<Feature> features = new ArrayList<>();
...
PredictionInput input = new PredictionInput(features);
PredictionProvider predictionProvider = ...
PredictionOutput output = predictionProvider.predictAsync(List.of(input))
.get(Config.INSTANCE.getAsyncTimeout(), Config.INSTANCE.getAsyncTimeUnit())
.get(0);
Prediction prediction = new Prediction(input, output);
Map<String, Saliency> saliencyMap = limeExplainer.explainAsync(prediction, model)
.get(Config.INSTANCE.getAsyncTimeout(), Config.INSTANCE.getAsyncTimeUnit());
With respect to the original LIME implementation our own implementation doesn’t require training data in case the input is in the form of tabular data. Actually our LIME implementation works seamlessly with plain text, tabular data and complex nested objects.
Explaining credit card approval decision service
Suppose we have a DMN model for the credit card approval system we discussed in the beginning of this post. Leveraging Kogito we can instantiate such a DecisionModel
from its definition (a file stored under /dmn/cc.dmn).
DMNRuntime dmnRuntime = DMNKogito.createGenericDMNRuntime(new InputStreamReader(getClass().getResourceAsStream("/dmn/cc.dmn")));
DecisionModel decisionModel = new DmnDecisionModel(dmnRuntime, "kiegroup", "cc");
Now suppose this DMN model takes the inputs we have discussed previously: for the sake of simplicity we focus on income, number of children, whether the user owns a car, whether the user owns a realty and the user age. We therefore build a PredictionInput
using those five Features
.
List<Feature> features = new ArrayList<>();
features.add(FeatureFactory.newNumericalFeature("income", 1000));
features.add(FeatureFactory.newNumericalFeature("noOfChildren", 3));
features.add(FeatureFactory.newBooleanFeature("ownsCar", true));
features.add(FeatureFactory.newBooleanFeature("ownsRealty", true));
features.add(FeatureFactory.newNumericalFeature("age", 18));
PredictionInput input = new PredictionInput(features);
We wrap the DMN model with the DecisionModelWrapper
convenience class provided by our library and run the model on our sample input.
PredictionProvider model = new DecisionModelWrapper(decisionModel);
PredictionOutput output = model.predictAsync(List.of(input))
.get(Config.INSTANCE.getAsyncTimeout(), Config.INSTANCE.getAsyncTimeUnit())
.get(0);
Our DMN model only generates one output named Approved, therefore the PredictionOutput
has only one underlying Output
, the approval / rejection result, as a boolean value. In this case we know it is false, as the credit card request was rejected.
boolean approved = output.getOutputs().get(0).getValue().asBoolean(); // <-- rejected
We’re now ready to pack PredictionInput
and PredictionOutput
into a Prediction
and generate the explanation using LIME.
Prediction prediction = new Prediction(input, output);
Map<String, Saliency> saliencyMap = limeExplainer.explainAsync(prediction, model)
.get(Config.INSTANCE.getAsyncTimeout(), Config.INSTANCE.getAsyncTimeUnit());
Now we can see what are the top n Features
that were important for having our request rejected.
List<FeatureImportance> negativeFeatures = saliencyMap.get("Approved").getNegativeFeatures(2);
for (FeatureImportance featureImportance : negativeFeatures) {
System.out.println(featureImportance.getFeature().getName() + ": " + featureImportance.getScore());
}
age: -0.61
ownsCar: -0.55
Explaining language detection machine learning model
Let’s consider another example of explaining a machine learning classifier trained to detect the language of an input text. For this sake we load a pretrained Apache OpenNLP LanguageDetector model (from a file called langdetect-183.bin).
InputStream is = new FileInputStream("langdetect-183.bin");
LanguageDetectorModel languageDetectorModel = new LanguageDetectorModel(is);
LanguageDetector languageDetector = new LanguageDetectorME(languageDetectorModel);
In order to make it possible to use it in the explainability API we wrap it as a PredictionProvider
. We assume that all input Features
are textual and we combine them together into a single String to be passed to the underlying LanguageDetector
.
PredictionProvider model = inputs -> CompletableFuture.supplyAsync(() -> {
List<PredictionOutput> results = new LinkedList<>();
for (PredictionInput predictionInput : inputs) {
StringBuilder builder = new StringBuilder();
for (Feature f : predictionInput.getFeatures()) {
if (builder.length() > 0) {
builder.append(' ');
}
builder.append(f.getValue().asString());
}
Language language = languageDetector.predictLanguage(builder.toString());
PredictionOutput predictionOutput = new PredictionOutput(List.of(new Output("lang", Type.TEXT, new Value<>(language.getLang()), language.getConfidence())));
results.add(predictionOutput);
}
return results;
});
Now let’s take an example input text, for example “italiani spaghetti pizza mandolino”, and create a new PredictionInput
to be passed to the model to obtain the Prediction
. Note that we have a single String of text, but since we’d like to understand what words influence more the detected language, we create a full text Feature so that each word in the input text becomes a separate Feature
(hence the actual input will contain four Features
, one for each word).
String inputText = "italiani spaghetti pizza mandolino";
List<Feature> features = new LinkedList<>();
features.add(FeatureFactory.newFulltextFeature("text", inputText));
The detected language is Italian, with a confidence score of ~0.03.
PredictionOutput output = model.predictAsync(List.of(input)).get().get(0);
Output output = output.getOutputs().get(0);
System.out.println(output.getValue().asString() + ": " + output.getScore();
ita: 0.029
Finally we can explain the Prediction
using LimeExplainer
and iterate through the most positively influencing Features
.
Prediction prediction = new Prediction(input, output);
Map<String, Saliency> saliencyMap = limeExplainer.explainAsync(prediction, model)
.get(Config.INSTANCE.getAsyncTimeout(), Config.INSTANCE.getAsyncTimeUnit());
for (FeatureImportance featureImportance : saliencyMap.get("lang").getPositiveFeatures(2)) {
System.out.println(featureImportance.getFeature().getName() + ": " + featureImportance.getScore());
}
spaghetti: 0.021
pizza: 0.019
Finding counterfactuals with OptaPlanner
Modern enterprises are increasingly integrated with Machine Learning (ML) algorithms in their business workflows as a means of leveraging existing data, optimising decision making processes, detecting anomalies or simply reducing repetitive tasks. One of the challenges with ML methods, especially with internally complex predictive models, is being able to provide non-technical explanations on why aRead more →
Modern enterprises are increasingly integrated with Machine Learning (ML) algorithms in their business workflows as a means of leveraging existing data, optimising decision making processes, detecting anomalies or simply reducing repetitive tasks.
One of the challenges with ML methods, especially with internally complex predictive models, is being able to provide non-technical explanations on why a certain prediction was made. This is vital, be it for regulatory purposes, increasing customer satisfaction or internal auditing. In this post we will focus on a particular kind of AI/ML explanation method commonly called a “counterfactual” and how it can easily be accomplished with OptaPlanner, an open-source constraint solver.
What are counterfactuals?
With Machine Learning methods taking an ever more prominent role in today’s life, there is a growing need to be able to justify and explain their outcomes. An entire field of research, Explainable AI (XAI) is dedicated to finding the best methods to quantify and qualify the ML predictions which might have an impact in our daily lives.
Such a method is the counterfactual explanation.
The goal of counterfactual explanations is to provide human-friendly examples of scenarios which have the desired outcome. As with many of these XAI methods, their power resides in the fact they are accessible to non-technical users, and in fact, if we do not look at the strong mathematical justifications behind the scenes, for simple models they might even seem like common sense.
Let’s look at an example of a counterfactual, by presenting a use case which we will use for the remainder of this post: credit card loan applications.
Let’s assume a simple system where a person can apply for a credit card by providing some personal information such as age, annual income, number of children, if they are a home-owner, number of days employed and whether they have a work phone or car. We will call this information the inputs.
This data will then be fed to a predictive model which will simply give an “approved” or “rejected” answer to the application (we will call this the outcome).

If an application is rejected, the applicant might be interested in knowing how they can improve their situation in order to have a successful application. Additionally, auditors of the process might want to understand what went wrong with that application. The problem is that many of the predictive models we encounter in the real-world might be too complex to interpret for non-technical stakeholders and they might be a “black box” model, that is a model where the same stakeholders do not have access to the predictive model working internals.
Counterfactual explanations work by providing a simple answer to the previous question. We chose an outcome (in this case an application approval) and this method provides the set of inputs such that “if your data was this, the application would have been approved.”. This is a very general overview of a counterfactual and we will provide some additional requirements to this method as well as a working example using an open-source constraint solver, OptaPlanner.
How to find counterfactuals?
To find a counterfactual we are not interested in any set of inputs that provides the desired outcome. We want to find the closest set of inputs to the original ones which provide this outcome.
Let’s assume, for the sake of an example, that our credit card approval model only took a single variable as its input: a person’s age.
A way of finding the counterfactual would be to start with the applicant’s age and search around for values for which the model gives an approval. Although possible, this type of brute-force optimisation is definitely not recommended. As any useful model will probably have a considerable amount of input variables, the brute-force search becomes unfeasible.

However, we might have domain-specific constraints that we want to impose on our counterfactuals.
As an example, let’s again consider the age variable and assume we found a solution which states if the applicant was 10 years younger, they could have the credit card. Although mathematically correct, the answer is not very helpful. It is not acceptable to provide such answers and as such we might want to impose constraints to the search, such as:
- The age must be unchanged
- The number of children must be unchanged

In this situation, we are searching for the best solution to a question (“what is the counterfactual?”), according to some score and over a finite search domain, in such a way that a number of constraints must be satisfied. This is, in essence, the definition of a Constraint Satisfaction Problem (CSP).
Since we have defined our problem as a CSP, we will not try to “reinvent the wheel” and implement our own solution. We will use already existing best-of-breed tools to achieve this, specifically OptaPlanner.
OptaPlanner is an open-source, battle-tested, flexible constraint solver which allows finding CSP solutions of any degree of complexity. Scores, solutions and logic can be expressed in most JVM supported languages. It also supports a variety of search algorithms and meta-heuristics as well as tools to help you research and test the best approaches, such as built-in benchmark reports. Using OptaPlanner as the counterfactual search engine in our example was very straight-forward.
It is important to note that for the example we are using we are treating the predictive model as a “black box”. That is, OptaPlanner will not need to know the internal workings of the predictive model. It only needs to communicate with it via an API, sending inputs and getting a prediction in return. This is important because, with this assumption, the counterfactual search can be completely decoupled from the type of model used, we could use any type of model from regressions to neural networks, that the principles outlined here (and the implementation) would work in the same way.
How to calculate counterfactuals with OptaPlanner
In order to use OptaPlanner to find counterfactuals we need to define our problem and consequently express at least the following in OptaPlanner’s terms:
- A planning entity
- A planning solution
- A way to score our solution
Our planning entity will be a CreditCardApproval class which will simply encapsulate the application information we mentioned previously.
The scoring of what constitutes the “best” counterfactual was implemented as a BendableScore
with the following levels:
- A “primary”
HardScore
which penalises if the outcome is different from the desired
This is our main predictive goal. For instance, if we are looking for approvals, a prediction of rejection will penalise this component - A “secondary”
HardScore
which penalises if any of the constrained variables is changed
We want to penalise solutions which have to change the constrained fields, such as age or number of children. - A
SoftScore
which is the distance from the input variables to the original variables.
This distance was calculated using a Manhattan distance, but other distance metrics can be used.
We could have excluded solutions which break our constraints from the search space, however, there are good reasons to include them.
We might be interested in solutions which are feasible because they do provide additional information about the model. For instance, it is still informative to know that there is no possible way to approve a credit card application given the data we have, without changing the age. Although it might not be useful from a business perspective, it can be very useful to give us an insight into the model’s behaviour.
The scoring was implemented using OptaPlanner’s constraint streams API (with an example below). The constraint streams API, on top of performance advantages, also allow us to have an individual breakdown of a score, which is useful to provide additional information about how the counterfactual search was performed.
An example of a method to penalise a constrained field:
public class ApprovalContraintsProvider implements ConstraintProvider {
// ...
private Constraint changedAge(ConstraintFactory constraintFactory) {
return constraintFactory
.from(CreditCardApprovalEntity.class)
.filter(entity -> !entity.getAge().equals(Facts.input.getAge()))
.penalize(
"Changed age",
BendableBigDecimalScore.ofHard(
HARD_LEVELS_SIZE, SOFT_LEVELS_SIZE, 1, BigDecimal.valueOf(1)));
}
// ...
Another important aspect of the counterfactual calculation is to provide search boundaries. This is done in the planning solution and we achieve this by using OptaPlanner’s ValueRangeProviders
and specifying the bound from domain-specific knowledge (e.g. what is the typical age or income range we expect to see from credit card applications). An important note is that we do not access the original training data. Using the original training data boundaries might even bring some problems, such as the counterfactual existing outside its range. Below are a few examples provided:
@PlanningSolution
public class Approval {
// ...
@ValueRangeProvider(id = "ageRange")
public CountableValueRange<Integer> getAgesList() {
return ValueRangeFactory.createIntValueRange(18, 100);
}
@ValueRangeProvider(id = "incomeRange")
public CountableValueRange<Integer> getIncomeList() {
return ValueRangeFactory.createIntValueRange(0, 1000000);
}
@ValueRangeProvider(id = "daysEmployedRange")
public CountableValueRange<Integer> getDayEmployedList() {
return ValueRangeFactory.createIntValueRange(0, 18250);
}
@ValueRangeProvider(id = "ownRealtyRange")
public CountableValueRange<Boolean> getOwnRealtyList() {
return ValueRangeFactory.createBooleanValueRange();
}
// ...
}
Once we implement our entity, score calculation and solution for a counterfactual, as an OptaPlanner problem, we are ready to test it.
The predictive model to be used was trained in Python using the scikit-learn library on a dataset containing simulated data for the variables mentioned previously. The model type chosen was a random forest classifier which, after training, was serialised into the PMML format.
The system was implemented as a simple REST server (built with Quarkus), with three main endpoints:
- A
/predict
endpoint which simply gives the approval prediction given applicant information - A
/counterfactual
endpoint which returns a counterfactual for a given input - A
/breakdown
endpoint which returns a breakdown of the OptaPlanner score associated with a counterfactual
Once we have the server running, we can query it with the data from an application we know before-hand will be unsuccessful:
curl --request POST \
--url http://0.0.0.0:8080/creditcard/prediction \
--header 'content-type: application/json' \
--data ' {
"age": 20,
"income": 50000,
"children": 0,
"daysEmployed": 100,
"ownRealty": false,
"workPhone": true,
"ownCar": true
}'
And the result will be similar to:
{
"APPROVED": 0,
"probability(0)": 0.7244007069725137,
"probability(1)": 0.27559929302748637
}
We can see that this application was rejected with a “confidence” of around 72%.
We now want to find the counterfactual, which is, as we’ve seen, the set of inputs closest to the above that would instead give an application approval.
To do so, we send the same data, but this time to the /counterfactual
endpoint:
curl --request POST \
--url http://0.0.0.0:8080/creditcard/counterfactual \
--header 'content-type: application/json' \
--data ' {
"age": 20,
"income": 50000,
"children": 0,
"daysEmployed": 100,
"ownRealty": false,
"workPhone": true,
"ownCar": true
}'
The result will be similar to:
{
"score": "[0/0]hard/[-2602632256.0]soft",
"approvalsList": [
{
"age": 20,
"income": 99120,
"children": 0,
"daysEmployed": 1996,
"ownRealty": false,
"workPhone": true,
"ownCar": true
}
// ...
}
First of all, we can see that the counterfactual honours the requirement of keeping the constrained fields unchanged (same age and same number of children). It tells us that:
- if the applicant earned the above amount and
- had been employed for a longer period of time,
the application would have been approved. So this is the information we needed!
We can confirm directly with the model that this hypothetical application would have been successful by sending it to the /prediction
endpoint:
curl --request POST \
--url http://0.0.0.0:8080/creditcard/prediction \
--header 'content-type: application/json' \
--data ' {
"age": 20,
"income": 99120,
"children": 0,
"daysEmployed": 1996,
"ownRealty": false,
"workPhone": true,
"ownCar": true
}'
With the result being:
{
"APPROVED": 1,
"probability(0)": 0.44784518470933066,
"probability(1)": 0.5521548152906692
}
As expected, the application would have been successful.
All the code is available at https://github.com/kiegroup/trusty-ai-sandbox/tree/master/counterfactual-op.
TrustyAI meets Kogito: decision monitoring
In this article, we introduce the metrics monitoring add-on for Kogito. This add-on is part of the TrustyAI initiative already introduced in the previous article https://blog.kie.org/2020/06/trusty-ai-introduction.html . Like Quarkus extensions, the Kogito add-ons are modules that can be imported as dependencies and add capabilities to the application. For example, another add-on is the infinispan-persistence-addon thatRead more →
In this article, we introduce the metrics monitoring add-on for Kogito. This add-on is part of the TrustyAI initiative already introduced in the previous article https://blog.kie.org/2020/06/trusty-ai-introduction.html .
Like Quarkus extensions, the Kogito add-ons are modules that can be imported as dependencies and add capabilities to the application. For example, another add-on is the infinispan-persistence-addon
that enables the Infinispan persistence.
The add-on we introduce in this post was already available but now with version 0.11 of Kogito new features related to decision monitoring have been added:
- Export of domain specific monitoring metrics with Prometheus.
- Generate operational and domain specific grafana dashboards.
In the article cited above, we introduced three personas: the devops engineer, the case worker and the compliance officer. The current version of the add-on exports one or more dashboards for each DMN and DRL resource. In particular, two types of dashboards might be generated depending on the type of the resource: one with operational metrics more oriented for the devops engineer and one with domain specific metrics, oriented to the case worker and the compliance worker needs.
Operational dashboard: this kind of dashboard is generated for each DMN and DRL endpoint and it contains useful information to monitor the status of the applications so as to have a fast reaction in case something goes wrong. The available graphs are about:
- The total number of requests on the endpoint.
- The average number of requests on the endpoint per minute.
- The quantiles on the elapsed time to evaluate the requests.
- Exception details.
Domain specific dashboard: currently this dashboard is exported only for each DMN endpoint. In particular, the domain specific dashboard contains a graph for each type of decision in the DMN model. At the moment, the types number
, boolean
and string
are supported, all the other time-based builtin types will be covered in the next Kogito release. In particular
- if the output of the decision is a number, the graph contains the quantiles for that metric (on a sliding window of 3 minutes).
- If the output is a boolean or a string, the graph contains the number of occurrences for each output (10 minutes average).
- if the output is time-based, a graph containing the quantiles is generated (with different scales and measurement unit depending on the type).
The plan for the next versions of the add-on is to also support complex structures and lists.
The devops engineer can directly use the dashboards that the add-on generates, or create custom ones manually based on the metrics exported by the application.
How To Use It
There are a few steps to follow to start using this add-on:
- In your kogito project, assuming that you have imported the kogito bom to manage properly the versions of the kogito modules, import the
monitoring-prometheus-addon
in yourpom.xml
:
<dependency>
<groupId>org.kie.kogito</groupId>
<artifactId>monitoring-prometheus-addon</artifactId>
</dependency>
- In case you are interested about the number of requests on the endpoints and the status code of the responses, add the following class to your project
import javax.ws.rs.container.ContainerRequestContext; import javax.ws.rs.container.ContainerResponseContext; import javax.ws.rs.ext.Provider; import org.kie.addons.monitoring.system.interceptor.MetricsInterceptor; @Provider public class MyInterceptor extends MetricsInterceptor { @Override public void filter(ContainerRequestContext requestContext, ContainerResponseContext responseContext) { super.filter(requestContext, responseContext); } }
At this point, once the application is packaged
mvn clean package
the dashboards are generated under the path target/generated-resources/kogito/dashboards
and the devops engineer can easily inject them during the deployment of the grafana container.
For the grafana provisioning, we suggest having a look at the official Grafana documentation https://grafana.com/docs/grafana/latest/administration/provisioning/ . The steps to deploy the dashboards will be integrated with Kogito Operator in a future release.
That’s it! The Kogito application is ready to export operational and domain specific metrics on the endpoint /metrics
.
A complete example can be found here https://github.com/kiegroup/kogito-examples/tree/stable/dmn-drools-quarkus-metrics.
TrustyAI Aspects
As mentioned in the previous blog post we have three aspects that we are implementing: explainability, runtime and accountability. However we need to see how these are connected with the use cases and personas. The first aspect we will consider is runtime tracing and monitoring. The term monitoring refers to the system overseeing performance orRead more →
As mentioned in the previous blog post we have three aspects that we are implementing: explainability, runtime and accountability. However we need to see how these are connected with the use cases and personas.
The first aspect we will consider is runtime tracing and monitoring. The term monitoring refers to the system overseeing performance or decision behaviour in real-time. Tracing refers to the historical data and queries over these. For example: how many decisions have been approved over the last week.
Monitoring and tracing applications at runtime is fundamental to enable auditing. Auditing enables a compliance officer to trace the decision making of the system and ensure it meets regulations.
The second aspect of TrustyAI is explaining machine learning predictive models so that the model outcomes can be trusted. This helps the data scientist to ensure unbiased and trusted models. Bias in ML systems is currently a huge problem with examples such as Google’s photo recognition, recognizing people as gorillas [1]. TrustyAI’s aims to leverage the explainability of the system to avoid model bias and unfair decisions, preserving company’s reputation and mitigating risks of litigation.
The third aspect of TrustyAI is auditing and accountability. This will help to increase compliance throughout the organization, by using auditing data to ensure that company rules and regulations have been met. The compliance officer will audit the system to review the requirements and the development of the system.
The use case covers the runtime and explainability aspects of the initiative. The bank manager can track the decisions of the system over time by using the monitoring implementation shown below. It is a sample dashboard that shows the number of loans that have been rejected or accepted over a given time period. The bank manager can monitor if the system is working correctly – and the constraints are still correct. For example, over time the economy changes and this means that loan constraints may change.

The bank manager then wishes to review individual loan decisions. This can be useful when there is a dispute about a loan or if a customer wants to know how their data has been processed. The bank manager can trace the decisions that have been made in the past as they have been stored for auditing and can be queried individually as shown below.

If the system makes a prediction based on an ML model which is used to make loan decisions, then the system can present these predictions and highlight which features were used to make that decision (see below). TrustyAI services will enrich products with this explainability feature so that users can answer these questions with confidence.

TrustyAI’s aim is to enable trust in AI systems. This includes being able to use AI in business environments and critical systems. This is vital for industries such as healthcare, finance and many others. TrustyAI will enable this support by building services which include the three aspects (explainability, monitoring and auditing) mentioned in this blog post.
References
[1] https://www.wired.com/story/when-it-comes-to-gorillas-google-photos-remains-blind/
TrustyAI Introduction
Have you ever used a machine learning (ML) algorithm and been confused by its predictions? How did it make this decision? AI-infused systems are increasingly being used within businesses, but how do you know you can trust them? We can trust a system if we have confidence that it will make critical business decisions accurately.Read more →
Have you ever used a machine learning (ML) algorithm and been confused by its predictions? How did it make this decision? AI-infused systems are increasingly being used within businesses, but how do you know you can trust them?
We can trust a system if we have confidence that it will make critical business decisions accurately. For example, can a medical diagnosis made by an AI system be trusted by a doctor? It is integral that domain experts (such as doctors) can trust the system to make accurate and correct decisions. Another important reason for this trust is customer understanding. New laws such as GDPR include the right to access how your data has been processed. Therefore, domain experts must understand the way in which a customer’s data has been processed, so that they can pass this information back to them.
This has led to a new initiative, within the kie group, to increase trust in decision making processes that depend on AI predictive models. This initiative focuses on three aspects: runtime, explainability and accountability.
Within TrustyAI we will combine ML models and decision logic (i.e. levaging on the integration of DMN and PMML) to enrich automated decisions by including predictive analytics. By monitoring the outcome of decision making we can audit systems to ensure they (like the above) meet regulations. We can also trace these results through the system to help with a global overview of the decisions and predictions made. TrustyAI will leverage on the combination of these two standards to ensure trusted automated decision making. Let’s look at a use case to put this into context.
“As a bank manager I want to manage current loans and approval rates to ensure they meet company regulations. I also want to review specific loan decisions so that I can explain to the customer why that loan was rejected or accepted.”
There are four personas that we will use in this example:
- A data scientist needs to check a predictive ML model in more detail to check if features are being used correctly. For example: if a bank manager makes a loan request but that loan is denied because of someone’s gender, then that would be an unfair bias in the model. This needs to be rectified. By enabling the user to investigate a model, they have the ability to identify these biases and ensure a model is balanced and accurate.
- A compliance officer wants to ensure the whole system is compliant with company policies and regulations.
- A caseworker is the end user of the system and they will use the system to make decisions. An example of an end user would be the bank manager who wants to tell the customer why a bank loan was rejected.
- A DevOps engineer looks at the health metrics of the system to ensure that the system runs correctly. They will monitor system behaviour over time.
Each of these personas uses a different aspect of TrustyAI, in our next blog post we will relate each of these aspects to the personas and the use cases mentioned here.
PMML revisited
Hi folks! The beginning of this year brings with it the initiative to re-design the Drools PMML module.In this post I will describe how we are going to approach it, what’s the current status, ideas for future development, etc. etc so… stay tuned! Background PMML is a standard whose aim is to “provide a wayRead more →
Hi folks! The beginning of this year brings with it the initiative to re-design the Drools PMML module.
In this post I will describe how we are going to approach it, what’s the current status, ideas for future development, etc. etc so… stay tuned!
Background
PMML is a standard whose aim is to “provide a way for analytic applications to describe and exchange predictive models produced by data mining and machine learning algorithms.” PMML standard defines a series of models that are managed, and we will refer to them as “Model”.
The maybe-not so obvious consequence of this is that, said differently, PMML may be thought as an orchestrator of different predictive models, each of which with different requirements.
Drools has its own PMML implementation. The original design of it was 100% drools-engine based, but in the long term this proved to be not so satisfactory for all the models, so a decision has taken to implement a new version with a different approach. And here the current story begin…
Requirements
To the bare-bone essence, what a PMML implementation should allow is to:
- load a PMML file (xml format)
- submit input data to it
- returns predicted values
Approach
The proposed architecture aims at fulfilling the requirements in a modular way, following “Clean Architecture” principles.
To achieve that, components are defined with clear boundaries and visibility.
General idea is that there are specific tasks strictly related to the core functionality that should be kept agnostic by other “outer” features.
Whoever wanting to deep delve in the matter may read the book “Clean Architecture” by R. C. Martin, but in the essence it is just a matter to apply good-ol’ design principles to the overall architecture.
With this target clearly defined, the steps required to achieve it are:
- identify the core-logic and the implementation details (model-specific)
- implement the core-logic inside “independent” modules
- write code for the model-specific modules
We choose to implement a plugin pattern to bind the core-logic to the model-specific implementations mostly for two reasons:
- incremental development and overall code-management: the core module itself does not depend on any of the model-specific implementations, so the latter may be provided/updated/replaced incrementally without any impact on the core
- possibility to replace the provided implementation with a custom one
- we also foresee the possibility to choose an implementation at runtime, depending on the original PMML structure (e.g. it may make sense to use a different implementation depending on the size of the given PMML)
Models
KiePMMLModel
- This is the definition of Kie-representation of the original PMML model.
- For every actual model there is a specific implementation, and it may be any kind of object (java map, drools rule, etc).
Components
We identified the following main functional components:
- Compiler
- Assembler
- Executor
Compiler
This component read the original PMML file and traslate it to our internal format.
The core-side of it simply unmarshall the xml data into Java object. Then, it uses java SPI to retrieve the model-compiler specific for the given PMML model (if it does not find one, the PMML is simply ignored).
Last, the retrieved model-compiler will “translate” the original PMML model to our model-specific representation (KiePMMLModels).
The core-side part of this component has no direct dependence on any specific Model Compiler implementation and not even with anything drools/kie related – so basically it is a lightweight/standalone library.
This component may be invoked at runtime (i.e. during the execution of the customer project), if its execution is not time-consuming, or during the compilation of the kjar (e.g. for drools-implemented models).
Assembler
This component stores KiePMMLModels created by the Compiler inside KIE knowledge base. None of the other components should have any dependency/knowledge of this one.
In turns, it must not have any dependency/knowledge/reference on actual Model Compiler implementations.
Executor
This component is responsible for actual execution of PMML models. It receives the PMML input data, retrieves the KiePMMLModel specific for the input data and calculates the output.
For each model there will be a specific “executor”, to allow different kinds of execution implementation (drools, external library, etc) depending on the model type.
The core-side of it simply receives the input data and retrieve the model-executor specific for the given PMML model (if it does not find one, the PMML is simply ignored).
Last, the retrieved model-executor will evaluate the prediction based on the input data.
The core-side part of this component has no direct dependence on any specific Model Executor implementation, but of course is strictly dependent on the drool runtime.
![]() |
Overall Architecture |
Model implementations
Drools-based models
- the compiler is invoked at kjar generation (or during runtime for hot-loading of PMML file)
- the compiler reads the PMML file and transform it to “descr” object (see BaseDescr, DescrFactory, DescrBuilderTest)
- regardless of how the model-compiler is invoked, the drools compiler must be invoked soon after it to have java-class generated based on the descr object
- the assembler put the generated classes in the kie base
- the executor loads the “drools-model” generated and invoke it with the input parameters
DRL details
- for each field in the DataDictionary, a specific DataType has to be defined
- for each branch/leaf of the tree, a full-path rule has to be generated (i.e. a rule with the path to get to it – e.g. “sunny”, “sunny_temperature”, “sunny_temperature_humidity”)
- a “status-holder” object is created and contains the value of the rule fired – changing that value will fire the children branch/leaf rules matching it (e.g. the rule “sunny” will fire “sunny_temperature” that – in turns – will fire “sunny_temperature_humidity”)
- such “status-holder” may contain informations/partial result of evaluation, to be eventually used where combination of results is needed
- missing value strategy may be implemented inside the status holder or as exploded rules
Testing
Regression
Regression model is the first one to have been implemented. Due to its inherent simplicity, we choose to provide a pure java-based implementation for it. For the moment being it is still under PR, and new full tests are being added.
Tree
After evaluating all the pros/cons, we decided that this model could be a good candidate to be implemented with a drools-based approach. Being also a simple model to follow, we choose to use it as first test for drools approach.
TO-DOs
This is a list of missing features that are not implemented, yet, and not strictly-related to a specific model. It will be (well, it should be) updated during the development:
- Setup Benchmarking skeleton project (see Drools Benchmark)
- Manage Extension tags (see xsdElement_Extension)
- Manage SimpleSetPredicate tags (see SimpleSetPredicate)
- Implement VariableWeight inside Segment (dynamic alternative to static “weight” value)
Introducing jBPM’s Human Task recommendation API
In this post, we’ll introduce a new jBPM API which allows for predictive models to be trained with Human Tasks (HT) data and for HT to incorporate model predictions as outputs and complete HT without user interaction. This API will allow you to add Machine Learning capabilities to your jBPM project by being able toRead more →
In this post, we’ll introduce a new jBPM API which allows for predictive models to be trained with Human Tasks (HT) data and for HT to incorporate model predictions as outputs and complete HT without user interaction.
This API will allow you to add Machine Learning capabilities to your jBPM project by being able to use, for instance, models trained with historical task data to recommend the most likely output. The API also gives developers the flexibility to implement a “recommendation-only” service (which only suggests outputs) as well as automatically completing the task if the prediction’s confidence meets a user-defined prediction confidence threshold.
This API exposes the HT handling to a recommendation service.
A recommendation service is simply any third-party class which implements the org.kie.internal.task.api.prediction.PredictionService
interface.
This interface consists of three methods:
getIdentifier()
– a method which returns a unique (String
) identifier for your prediction servicepredict(Task task, Map<String, Object> inputData)
– a method that takes task information and the task’s inputs from which we will derive the model’s inputs, as a map. The method returns aPredictionOutcome
instance, which we will look in closer detail later ontrain(Task task, Map<String, Object> inputData, Map<String, Object> outputData)
– this method, similarly to predict, takes task info and the task’s inputs, but now we also need to provide the task’s outputs, as a map, for training
This class will consist of:
- A
Map<String, Object>
outcome containing the prediction outputs, each entry represents an output attribute’s name and value. This map can be empty, which corresponds to the model not providing any prediction. - A
confidence
value. The meaning of this field is left to the developer (e.g. it could represent a probability between 0.0 and 1.0). It’s relevance is related to theconfidenceThreshold
below. - A
confidenceThreshold
– this value represents the confidence cutoff after which an action can be taken by the HT item handler.
As an example, let’s assume our confidence represents a prediction probability between 0.0 and 1.0. If the confidenceThreshold
is 0.7, that would mean that for confidence > 0.7
the HT outputs would be set to the outcome and the task automatically closed. If the confidence <= 0.7
, then the HT would set the prediction outcome as suggested values, but the task would not be closed and still need human interaction. If the outcome is empty, then the HT life cycle would proceed as if no prediction was made.
By defining a confidence threshold which is always higher than the confidence, developers can create a “recommendation-only” service, which will assign predicted outputs to the task, but never complete it.
The initial step is then, as defined above, the predict
step. In the scenario where the prediction’s confidence is above the threshold, the task is automatically completed. If the confidence is not above the threshold, however, when the task is eventually completed both the inputs and the outputs will then be used to further train the model by calling the prediction service’s train
method.
Example project
An example project is available here. This project consists of a single Human Task, which can be inspected using Business Central. The task is generic and simple enough in order to demonstrate the working of the jBPM’s recommendation API.
For the purposes of the demonstration, this task will be used to model a simple purchasing system where the purchase of a laptop of a certain brand is requested and must be, eventually, manually approved. The tasks inputs are:
item
– aString
with the brand’s nameprice
– aFloat
representing the laptop’s priceActorId
– aString
representing the user requesting the purchase
The task provides as outputs:
approved
– aBoolean
specifying whether the purchase was approved or not
This repository contains two example recommendation service implementations as Maven modules and a REST client to populate the project with tasks to allow the predictive model training.
Start by downloading, or alternatively cloning, the repository:
$ git clone git@github.com:ruivieira/jbpm-recommendation-demo.git
For this demo, two random forest-based services, one using the SMILE library and another as a Predictive Model Markup Language (PMML) model, will be used. The services, located respectively in services/jbpm-recommendation-smile-random-forest
and services/jbpm-recommendation-pmml-random-forest
, can be built with (using SMILE as an example):
$ cd services/jbpm-recommendation-smile-random-forest
$ mvn clean install
The resulting JARs files can then be included in the Business Central’s kie-server.war
located in standalone/deployments
directory of your jBPM server installation. To do this, simply create a WEB-INF/lib
, copy the compiled JARs into it and run
$ zip -r kie-server.war WEB-INF
The PMML-based service expects to find the PMML model in META-INF
, so after copying the PMML file in jbpm-recommendation-pmml-random-forest/src/main/resources/models/random_forest.pmml
into META-INF
, it should also be included in the WAR by using
$ zip -r kie-server.war META-INF
jBPM will search for a recommendation service with an identifier specified by a Java property named org.jbpm.task.prediction.service
. Since in our demo, the random forest service has the identifier SMILERandomForest
, we can set this value when starting Business Central, for instance as:
$ ./standalone.sh -Dorg.jbpm.task.prediction.service=SMILERandomForest
For the purpose of this documentation we will illustrate the steps using the SMILE-based service. The PMML-based service can be used by starting Business Central and setting the property as
$ ./standalone.sh -Dorg.jbpm.task.prediction.service=PMMLRandomForest
Once Business Central has completed the startup, you can go to http://localhost:8080/business-central/ and login using the default admin credential wbadmin/wbadmin
. After choosing the default workspace (or creating your own), then select “Import project” and use the project git
URL:
https://github.com/ruivieira/jbpm-recommendation-demo-project.git
The repository also contains a REST client (under client
) which allows to add Human Tasks in batch in order to have sufficient data points to train the model, so that we can have meaningful recommendations.
NOTE: Before running the REST client, make sure that Business Central is running and the demo project is deployed and also running.
The class org.jbpm.recommendation.demo.RESTClient
performs this task and can be executed from the client directory with:
$ mvn exec:java -Dexec.mainClass="org.jbpm.recommendation.demo.RESTClient"
The prices for Lenovo and Apple laptops are drawn from Normal distributions with respective means of 1500 and 2500 (pictured below). Although the recommendation service is not aware of the deterministic rules we’ve used to set the task outcome, it will train the model based on the data it receives. The tasks’ completion will adhere to the following logic:
- The purchase of a laptop of brand Lenovo requested by user John or Mary will be approved if the price is around $1500
- The purchase of a laptop of brand Apple requested by user John or Mary will be approved if the price is around $2500
- The purchase of a laptop of brand Lenovo requested by user John or Mary will be rejected if the price is around $2500
The client will then simulate the creation and completion of human tasks, during which the model will be trained.
SMILE-based service
As we’ve seen, when creating and completing a batch of tasks (as previously) we are simultaneously training the predictive model. The service implementation is based on a random forest model a popular ensemble learning method.
When running the RESTClient
, 1200 tasks will be created and completed to allow for a reasonably sized training dataset. The recommendation service initially has a confidence threshold of 1.0 and after a sufficiently large number (arbitrarily chosen as 1200) of observations are used for training, the confidence threshold drops to 0.75. This is simply to demonstrate the two possible actions, i.e. recommendation without completing and completing the task. This also allows us to avoid any cold start problems.
After the model is trained with the task from RESTClient
, we will now create a new Human Task.
If we create a HT requesting the purchase of an “Apple” laptop from “John” with the price $2500, we should expect it to be approved.
If fact, when claiming the task, we can see that the recommendation service recommends the purchase to be approved with a “confidence” of 91%.
If he now create a task for the request of a “Lenovo” laptop from “Mary” with the price $1437, he would expect it to be approved. We can see that this is the case, where the form is filled in by the recommendation service with an approved status with a “confidence” of 86.5%.
We can also see, as expected, what happens when “John” tries to order a “Lenovo” for $2700. The recommendation service fills the form as “not approved” with a “confidence” of 71%.
In this service, the confidence threshold is set as 1.0 and as such the task was not closed automatically.
The minimum number of data points was purposely chosen so that after running the REST client and completing a single task, the service will drop the confidence threshold to 0.75.
If we complete one of the above tasks manually, the next task you create will be automatically completed if the confidence is above 0.75. For instance, when creating a task we are pretty sure will be approved (e.g. John purchasing a Lenovo $1500) you can verify that the task is automatically completed.
PMML-based service
The second example implementation is the PMML-based recommendation service. PMML is a predictive model interchange standard, which allows for a wide variety of models to be reused in different platforms and programming languages.
The service included in this demo consists of pre-trained model (with a dataset similar to the one generated by RESTClient
) which is executed by a PMML engine. For this demo, the engine used was jpmml-evaluator, the de facto reference implementation of the PMML specification.
There are two main differences when comparing this service to the SMILE-based one:
- The model doesn’t need the training phase. The model has been already trained and serialised into the PMML format. This means that we can start using predictions straight away from jBPM.
- The
train
API method is a no-op in this case. This means that whenever the service’strain
method is called, it will not be used for training in this example (only thepredict
method is needed for a “read-only” model), as we can see from the figure below.
You can verify that the Business Central workflow is the same as with the SMILE service, although in this case no training is necessary.
The above instructions on how to setup the demo project are also available in the following video (details are in the subtitles):
In conclusion, in this post we’ve shown how to use a new API which allows for predictive models to suggest outputs and complete Human Tasks.
We’ve also shown a project which can use different recommendation service backends simply by registering them with jBPM without any changes to the project.
Why not create your own jBPM recommendation service using your favourite Machine Learning framework, today?