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"); 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(imixsAIContextHandler, 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); } boolean booleanExpressionResult = Boolean.parseBoolean(conditionalExpressionResult.trim()); // Store in transaction-scoped cache promptResultCache.put(hashPrompt, "" + booleanExpressionResult); conditionalEvent.setCondition("" + booleanExpressionResult); logger.info("└── ✓ evaluation time: " + (System.currentTimeMillis() - l) + "ms"); } } } } }