office-dynamixs-ai/_OLD/cognition/ConditionalAIAdapter.java
2026-02-09 11:14:10 +01:00

175 lines
7.6 KiB
Java

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
* <p>
* The Adapter defines a Default Expression template for LLMs. The BPMN
* Configuration must only include the user prompt. See the following example:
* <p>
*
* <pre>
* {@code
<imixs-ai name="CONDITION">
<debug>true</debug>
<endpoint><propertyvalue>llm.service.endpoint</propertyvalue></endpoint>
<result-item>my.condition</result-item>
<prompt><![CDATA[
Is Germany an EU member country? ]]>
</prompt>
</imixs-ai>
* }
* </pre>
*
* Caching: The ConditionalAIAdapter implements a caching mechnaism. The adapter
* class stores the hash value in to the item <result-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<Long, String> promptResultCache = new HashMap<>();
@Inject
protected WorkflowService workflowService;
@Inject
protected ImixsAIPromptService imixsAIPromptService;
@Inject
protected OpenAIAPIService llmService;
@Inject
ImixsAIContextHandler imixsAIContextHandler;
public static String DEFAULT_EXPRESSION_TEMPLATE = "<imixs-ai name=\"CONDITION\">\n" + //
" <debug>true</debug>\n" + //
" <endpoint><propertyvalue>llm.service.endpoint</propertyvalue></endpoint>\n" + //
" <result-event>BOOLEAN</result-event>\n" + //
" <PromptDefinition>\n" + //
" <prompt_options>{\"n_predict\": 16, \"temperature\": 0 }</prompt_options>\n" + //
" <prompt role=\"system\"><![CDATA[\n" + //
" Evaluate the following condition to 'true' or 'false'. Do only answer with 'true' or 'false'. Do not add any additional context into your answer.]]>\n"
+ //
" </prompt>\n" + //
" </PromptDefinition>\n" + //
"</imixs-ai>";
/**
* 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("<imixs-ai")) {
long l = System.currentTimeMillis();
List<ItemCollection> 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");
}
}
}
}
}