236 lines
7.4 KiB
Java
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;
|
|
|
|
}
|
|
|
|
}
|