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");
}
}
}
}
}