diff --git a/dynamixs-ai-app/src/main/java/ai/dynamixs/ConditionalAIAdapter.java b/dynamixs-ai-app/src/main/java/ai/dynamixs/ConditionalAIAdapter.java
new file mode 100644
index 0000000..f9d6a3c
--- /dev/null
+++ b/dynamixs-ai-app/src/main/java/ai/dynamixs/ConditionalAIAdapter.java
@@ -0,0 +1,184 @@
+package ai.dynamixs;
+
+import java.io.Serializable;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.logging.Logger;
+
+import org.imixs.ai.ImixsAIContextHandler;
+import org.imixs.ai.workflow.ImixsAIPromptService;
+import org.imixs.ai.workflow.OpenAIAPIService;
+import org.imixs.workflow.ItemCollection;
+import org.imixs.workflow.engine.ConditionalExpressionEvent;
+import org.imixs.workflow.engine.WorkflowService;
+import org.imixs.workflow.exceptions.AdapterException;
+import org.imixs.workflow.exceptions.PluginException;
+
+import jakarta.enterprise.event.Observes;
+import jakarta.inject.Inject;
+import jakarta.json.JsonObject;
+import jakarta.transaction.TransactionScoped;
+
+/**
+ * The ConditionalAIAdapter reacts on CDI Events of type BPMNConditionEvent and
+ * evaluates a condition against an LLM
+ *
+ * The Adapter defines a Default Expression template for LLMs. The BPMN
+ * Configuration must only include the user prompt. See the following example:
+ *
+ *
+ *
+* {@code
+
+ true
+ llm.service.endpoint
+ my.condition
+
+
+
+
+* }
+*
+ *
+ * Caching: The ConditionalAIAdapter implements a caching mechnaism. The adapter
+ * class stores the hash value in to the item .hash to avoid
+ * duplicate calls against the llm with the same prompt in one processing cycle!
+ */
+@TransactionScoped
+public class ConditionalAIAdapter implements Serializable {
+
+ private static final Logger logger = Logger.getLogger(ConditionalAIAdapter.class.getName());
+
+ // Transaction-scoped cache - survives across multiple eval() calls
+ private Map promptResultCache = new HashMap<>();
+
+ @Inject
+ protected WorkflowService workflowService;
+
+ @Inject
+ protected ImixsAIPromptService imixsAIPromptService;
+
+ @Inject
+ protected OpenAIAPIService llmService;
+
+ @Inject
+ ImixsAIContextHandler imixsAIContextHandler;
+
+ public static String DEFAULT_EXPRESSION_TEMPLATE = "\n" + //
+ " true\n" + //
+ " llm.service.endpoint\n" + //
+ " BOOLEAN\n" + //
+ " \n" + //
+ " {\"n_predict\": 16, \"temperature\": 0 }\n" + //
+ " \n"
+ + //
+ " \n" + //
+ " \n" + //
+ "";
+
+ /**
+ * Test method
+ *
+ * @param conditionEvent
+ * @throws PluginException
+ * @throws AdapterException
+ */
+ public void onConditionEvent(@Observes ConditionalExpressionEvent conditionalEvent)
+ throws PluginException, AdapterException {
+
+ // run only if we have a prompt definition
+ String condition = conditionalEvent.getCondition();
+ if (condition != null && condition.contains(" llmConditionDefinitions;
+ boolean llmAPIDebug = false;
+
+ condition = condition.trim();
+ llmConditionDefinitions = workflowService.evalXMLExpressionList(
+ conditionalEvent.getCondition(), "imixs-ai", "CONDITION", conditionalEvent.getWorkitem(), true);
+
+ for (ItemCollection promptDefinition : llmConditionDefinitions) {
+ String llmAPIEndpoint = imixsAIPromptService.parseLLMEndpointByBPMN(promptDefinition);
+ String userPrompt = promptDefinition.getItemValueString("prompt");
+ String llmAPIResultItem = promptDefinition.getItemValueString("result-item");
+ if (llmAPIResultItem.isEmpty()) {
+ throw new PluginException(OpenAIAPIService.class.getSimpleName(),
+ OpenAIAPIService.ERROR_PROMPT_TEMPLATE,
+ "Prompt Definition must include the tag 'result-item'.");
+ }
+
+ if ("true".equalsIgnoreCase(promptDefinition.getItemValueString("debug"))) {
+ llmAPIDebug = true;
+ }
+ if (llmAPIDebug) {
+ logger.info("├── evaluate conditional expression");
+ }
+ // set DEFAULT_EXPRESSION_TEMPLATE.
+ imixsAIContextHandler.setWorkItem(conditionalEvent.getWorkitem());
+ imixsAIContextHandler.addPromptDefinition(DEFAULT_EXPRESSION_TEMPLATE);
+
+ // add the user prompt!
+ imixsAIContextHandler.addMessage(ImixsAIContextHandler.ROLE_USER, userPrompt,
+ workflowService.getSessionContext().getCallerPrincipal().getName(), null);
+
+ // POST PromptCompletion
+ JsonObject jsonPrompt = imixsAIContextHandler.getOpenAIMessageObject();
+ if (llmAPIDebug) {
+ logger.info("│ ├── conditional prompt:");
+ logger.info(jsonPrompt.toString());
+ }
+
+ // Caching:
+ // if jsonPrompt (hash) was already stored, we do not call the LLM
+ long hashPrompt = jsonPrompt.hashCode();
+ // Check transaction-scoped cache
+ if (promptResultCache.containsKey(hashPrompt)) {
+ if (llmAPIDebug) {
+ logger.info("│ ├── reuse cached result from transaction cache - hash=" + hashPrompt);
+ logger.info("└── ✓ evaluation time: " + (System.currentTimeMillis() - l) + "ms");
+ }
+ conditionalEvent.setCondition(promptResultCache.get(hashPrompt));
+
+ return;
+ }
+
+ String completionResult = llmService.postPromptCompletion(jsonPrompt, llmAPIEndpoint);
+ if (llmAPIDebug) {
+ logger.info("│ ├── ⚙ Completion Request conditional expression...");
+ }
+ String conditionalExpressionResult = llmService.processPromptResult(completionResult, "BOOLEAN",
+ conditionalEvent.getWorkitem());
+
+ // store the result message
+ if (conditionalExpressionResult != null && !conditionalExpressionResult.isBlank()) {
+ // trim result
+ conditionalExpressionResult = conditionalExpressionResult.trim().toLowerCase();
+
+ if (!"true".equals(conditionalExpressionResult) && !"false".equals(conditionalExpressionResult)) {
+ logger.warning(
+ "│ ├── ⚠ Unexpected conditional expression result: " + conditionalExpressionResult);
+ throw new PluginException(OpenAIAPIService.class.getSimpleName(),
+ OpenAIAPIService.ERROR_PROMPT_INFERENCE,
+ "Conditional expression should result into 'true' or 'false' only");
+ }
+ if (llmAPIDebug) {
+ logger.info("│ ├── ⚐ Conditional expression result: " + conditionalExpressionResult);
+ }
+ // store result
+ boolean booleanExpressionResult = Boolean.parseBoolean(conditionalExpressionResult.trim());
+ conditionalEvent.getWorkitem().setItemValue(llmAPIResultItem, booleanExpressionResult);
+
+ // Store in transaction-scoped cache
+ promptResultCache.put(hashPrompt, "" + booleanExpressionResult);
+ conditionalEvent.setCondition("" + booleanExpressionResult);
+ logger.info("└── ✓ evaluation time: " + (System.currentTimeMillis() - l) + "ms");
+ }
+ }
+ }
+
+ }
+
+}
\ No newline at end of file
diff --git a/dynamixs-ai-app/src/main/java/ai/dynamixs/ConditionalDebugAdapter.java b/dynamixs-ai-app/src/main/java/ai/dynamixs/ConditionalDebugAdapter.java
deleted file mode 100644
index d14dbb7..0000000
--- a/dynamixs-ai-app/src/main/java/ai/dynamixs/ConditionalDebugAdapter.java
+++ /dev/null
@@ -1,136 +0,0 @@
-package ai.dynamixs;
-
-import java.io.Serializable;
-import java.util.List;
-import java.util.logging.Logger;
-
-import org.imixs.ai.ImixsAIContextHandler;
-import org.imixs.ai.workflow.ImixsAIPromptService;
-import org.imixs.ai.workflow.ImixsAIResultEvent;
-import org.imixs.ai.workflow.OpenAIAPIService;
-import org.imixs.workflow.ItemCollection;
-import org.imixs.workflow.engine.ConditionalExpressionEvent;
-import org.imixs.workflow.engine.WorkflowService;
-import org.imixs.workflow.exceptions.AdapterException;
-import org.imixs.workflow.exceptions.PluginException;
-import org.imixs.workflow.faces.data.WorkflowEvent;
-
-import jakarta.enterprise.event.Observes;
-import jakarta.inject.Inject;
-
-/**
- * Debug class to test CDI Events of type BPMNConditionEvent
- *
- *
- * Note: To run this plugin the conditional configuration should look like this
- *
- *
-* {@code
-
- true
- llm.service.endpoint
- BOOLEAN
-
- {"n_predict": 16, "temperature": 0 }
-
-
-
-
-
-
-* }
-*
- *
- */
-public class ConditionalDebugAdapter implements Serializable {
-
- private static final Logger logger = Logger.getLogger(ConditionalDebugAdapter.class.getName());
-
- @Inject
- protected WorkflowService workflowService;
-
- @Inject
- protected ImixsAIPromptService imixsAIPromptService;
-
- @Inject
- protected OpenAIAPIService llmService;
-
- @Inject
- ImixsAIContextHandler imixsAIContextHandler;
-
- /**
- * Test method
- *
- * @param conditionEvent
- * @throws PluginException
- * @throws AdapterException
- */
- public void onConditionEvent(@Observes ConditionalExpressionEvent conditionalEvent)
- throws PluginException, AdapterException {
-
- List llmConditionDefinitions;
- boolean llmAPIDebug = false;
-
- llmConditionDefinitions = workflowService.evalXMLExpressionList(
- conditionalEvent.getCondition(), "imixs-ai", "CONDITION", conditionalEvent.getWorkitem(), true);
-
- for (ItemCollection promptDefinition : llmConditionDefinitions) {
- String llmAPIEndpoint = imixsAIPromptService.parseLLMEndpointByBPMN(promptDefinition);
- String llmAPIResultEvent = promptDefinition.getItemValueString("result-event");
- String llmAPIResultItem = promptDefinition.getItemValueString("result-item");
- if ("true".equalsIgnoreCase(promptDefinition.getItemValueString("debug"))) {
- llmAPIDebug = true;
- }
-
- // get PromptDefintion template...
- String prompt = "" + promptDefinition.getItemValueString("PromptDefinition")
- + "";
- logger.info("PromptDefinition=" + prompt);
-
- imixsAIContextHandler.setWorkItem(conditionalEvent.getWorkitem());
- imixsAIContextHandler.addPromptDefinition(prompt);
-
- }
-
- }
-
- public void onWorkflowEvent(@Observes WorkflowEvent conditionEvent) {
-
- logger.info("We observe a WorkflowEvent");
-
- }
-
- /**
- * This observer method reacts on a ImixsAI Result event and evaluates a boolean
- * value
- *
- * Note: To run this adapter the configuration should look like this
- *
- *
- * {@code
-
- llm.service.endpoint
- BOOLEAN
-
- * }
- *
- *
- * @param event
- */
- public void onEvent(@Observes ImixsAIResultEvent event) {
- if (event.getWorkitem() == null) {
- return;
- }
-
- // we only adapt the result in case the eventType===JSON
- if ("BOOLEAN".equalsIgnoreCase(event.getEventType())) {
- // get result string
- String booleanString = event.getPromptResult();
-
- logger.info("Prompt Boolean Result= " + booleanString);
-
- }
- }
-}
\ No newline at end of file
diff --git a/workflow/en/invoice-demo-c-1.0.0.bpmn b/workflow/en/invoice-demo-c-1.0.0.bpmn
index 4fb3f08..d4c01fd 100644
--- a/workflow/en/invoice-demo-c-1.0.0.bpmn
+++ b/workflow/en/invoice-demo-c-1.0.0.bpmn
@@ -121,6 +121,7 @@ th {
event_HBu5SQ
event_ykc34Q
event_kXkdQw
+ event_jP2H0w
IntermediateCatchEvent_8
@@ -1385,6 +1386,7 @@ Bei Fragen wenden Sie sich bitte an info@imixs.com
$workflowgroup - $workflowstatus]]>
SequenceFlow_31
+ sequenceFlow_rTkelw
sequenceFlow_b9RMyw
SequenceFlow_7
sequenceFlow_KW7K3Q
@@ -1601,7 +1603,6 @@ Debitor: customer.name (customer.number
sequenceFlow_JClvDg
sequenceFlow_PL3AfA
- sequenceFlow_tZVF2w
sequenceFlow_hbRxpg
@@ -1867,19 +1868,13 @@ Debitor: customer.name (customer.number
-
+
true
llm.service.endpoint
- BOOLEAN
-
- {"n_predict": 16, "temperature": 0 }
-
-
-
-
-
+ compliance.check
+
+
]]>
@@ -2111,9 +2106,103 @@ Debitor: customer.name (customer.number
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ sequencenumber.*\.xlsx
+
+ INVOICE_TOTAL
+ invoice.total
+ number
+
+
+ INVOICE_CURRENCY
+ invoice.currency
+
+
+]]>
+
+
+
+
+
+
+
+
+
+
+
+ false
+
+
+
+
+ sequenceFlow_PL3AfA
+ sequenceFlow_rTkelw
+
+
+ DataOutput_5
+
+
+ DataOutput_5
+
+ sequenceFlow_tZVF2w
+
+
+
+
@@ -2971,6 +3060,12 @@ th { font-weight: bold;}
+
+
+
+
+
+
@@ -3208,14 +3303,17 @@ th { font-weight: bold;}
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+