/* * 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. *

* The adapter automatically index and embed text data. *

* The imixs-ai prompt definition defines the API endpoint and processing events * to adapt the indexing and the prompt template. *

* The Adapter can be configured by a event entity. *

* INDEX configuration - to add embeddings: * *

 * {@code
        
            llm.service.endpoint
            true
        
 * }
 * 
* * The Endpoint defines the Rest API endpoint of the llama-cpp http server or * any compatible OpenAI / Open API rest service endpoint. *

* Example Prompt Template: * * *

 * {@code
    
    
    {"n_predict": 4096, "temperature": 0}
    $workflowgroup: $workflowstatus

    # Summary
    $workflowsummary

    offer.summary

    ]]>
     
 * }
 * 
* * @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 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 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. *

* 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; } }