office-dynamixs-ai/dynamixs-ai-rag/src/main/java/ai/dynamixs/rag/RAGAdapter.java
2025-05-31 14:46:56 +02:00

236 lines
7.4 KiB
Java

/*
* Imixs-Workflow
*
* Copyright (C) 2001-2020 Imixs Software Solutions GmbH,
* http://www.imixs.com
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You can receive a copy of the GNU General Public
* License at http://www.gnu.org/licenses/gpl.html
*
* Project:
* https://www.imixs.org
* https://github.com/imixs/imixs-workflow
*
* Contributors:
* Imixs Software Solutions GmbH - Project Management
* Ralph Soika - Software Developer
*/
package ai.dynamixs.rag;
import java.util.List;
import java.util.Optional;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.eclipse.microprofile.config.inject.ConfigProperty;
import org.imixs.ai.workflow.OpenAIAPIService;
import org.imixs.workflow.ItemCollection;
import org.imixs.workflow.SignalAdapter;
import org.imixs.workflow.engine.WorkflowService;
import org.imixs.workflow.exceptions.AdapterException;
import org.imixs.workflow.exceptions.PluginException;
import jakarta.inject.Inject;
/**
* The RAGAdapter is used for Retrieval-Augmented Generation workflows.
* <p>
* The adapter automatically index and embed text data.
* <p>
* The imixs-ai prompt definition defines the API endpoint and processing events
* to adapt the indexing and the prompt template.
* <p>
* The Adapter can be configured by a event entity.
* <p>
* INDEX configuration - to add embeddings:
*
* <pre>
* {@code
<imixs-ai name="INDEX">
<endpoint><propertyvalue>llm.service.endpoint</propertyvalue></endpoint>
<debug>true</debug>
</imixs-ai>
* }
* </pre>
*
* The Endpoint defines the Rest API endpoint of the llama-cpp http server or
* any compatible OpenAI / Open API rest service endpoint.
* <p>
* Example Prompt Template:
*
*
* <pre>
* {@code
<?xml version="1.0" encoding="UTF-8"?>
<IndexDefinition>
<index_options>{"n_predict": 4096, "temperature": 0}</index_options>
<![CDATA[<text><itemvalue>$workflowgroup</itemvalue>: <itemvalue>$workflowstatus</itemvalue>
# Summary
<itemvalue>$workflowsummary</itemvalue>
<itemvalue>offer.summary</itemvalue>
]]></text>
</IndexDefinition>
* }
* </pre>
*
* @author Ralph Soika
* @version 1.0
*
*/
public class RAGAdapter implements SignalAdapter {
public static final String ML_ENTITY = "entity";
public static final String API_ERROR = "API_ERROR";
public static final String LLM_PROMPT = "PROMPT";
public static final String LLM_SUGGEST = "SUGGEST";
public static final String LLM_INDEX = "INDEX";
public static final int API_EVENT_SUCCESS = 110;
public static final int API_EVENT_FAILURE = 90;
private static Logger logger = Logger.getLogger(RAGAdapter.class.getName());
@Inject
@ConfigProperty(name = OpenAIAPIService.ENV_LLM_SERVICE_ENDPOINT)
Optional<String> serviceEndpoint;
@Inject
private WorkflowService workflowService;
@Inject
private OpenAIAPIService llmService;
@Inject
private ClusterService clusterService;
/**
* Default Constructor
*/
public RAGAdapter() {
super();
}
/**
* CDI Constructor to inject WorkflowService
*
* @param workflowService
*/
@Inject
public RAGAdapter(WorkflowService workflowService) {
super();
this.workflowService = workflowService;
}
public void setWorkflowService(WorkflowService workflowService) {
this.workflowService = workflowService;
}
/**
* This method parses the LLM Event definitions.
*
* For each PROMPT the method posts a context data (e.g text from an attachment)
* to the Imixs-AI Analyse service endpoint
*
* @throws PluginException
*/
public ItemCollection execute(ItemCollection workitem, ItemCollection event)
throws AdapterException, PluginException {
String llmAPIEndpoint = null;
String llmAPIResultEvent = null;
String llmAPIResultItem = null;
boolean llmAPIDebug = false;
long processingTime = System.currentTimeMillis();
logger.finest("...running api adapter...");
// read optional configuration form the model or imixs.properties....
try {
List<ItemCollection> llmIndexDefinitions = workflowService.evalWorkflowResultXML(event, "imixs-ai",
LLM_INDEX, workitem, false);
// verify if we have an INDEX configuration
if (llmIndexDefinitions != null && llmIndexDefinitions.size() > 0) {
for (ItemCollection indexDefinition : llmIndexDefinitions) {
String llmText = indexDefinition.getItemValueString("text");
logger.info("POST index text: " + llmText);
float[] indexResult = llmService.processRAGIndex(llmText, llmAPIEndpoint);
// clusterService.insertVector
logger.info("POST result: " + indexResult);
logger.info("result size= " + indexResult.length);
}
}
} catch (PluginException e) {
logger.severe("Unable to parse item definitions for 'imixs-ai', verify model - " + e.getMessage());
throw new PluginException(
RAGAdapter.class.getSimpleName(), e.getErrorCode(),
"Unable to parse item definitions for 'imixs-ai', verify model - " + e.getMessage(), e);
}
if (llmAPIDebug) {
logger.info("===> Total Processing Time=" + (System.currentTimeMillis() - processingTime) + "ms");
}
return workitem;
}
/**
* This helper method parses the ml api endpoint either provided by a model
* definition or a imixs.property or an environment variable.
* <p>
* If not api endpoint is defined by the model the adapter uses the default api
* endpoint.
*
* @param llmPrompt
* @return
* @throws PluginException
*/
private String parseLLMEndpointByBPMN(ItemCollection llmPrompt) throws PluginException {
boolean debug = logger.isLoggable(Level.FINE);
String llmAPIEndpoint = null;
// Test if the model provides a API Endpoint.
llmAPIEndpoint = null;
if (llmPrompt != null) {
llmAPIEndpoint = llmPrompt.getItemValueString("endpoint");
}
// switch to default api endpoint?
if (llmAPIEndpoint == null || llmAPIEndpoint.isEmpty()) {
// set defautl api endpoint if defined
if (serviceEndpoint.isPresent() && !serviceEndpoint.get().isEmpty()) {
llmAPIEndpoint = serviceEndpoint.get();
}
}
if (debug) {
logger.info("......llm api endpoint " + llmAPIEndpoint);
}
// adapt text...
llmAPIEndpoint = workflowService.adaptText(llmAPIEndpoint, null);
if (!llmAPIEndpoint.endsWith("/")) {
llmAPIEndpoint = llmAPIEndpoint + "/";
}
return llmAPIEndpoint;
}
}