diff --git a/dynamixs-ai-rag/pom.xml b/dynamixs-ai-rag/pom.xml
deleted file mode 100644
index 7ed471f..0000000
--- a/dynamixs-ai-rag/pom.xml
+++ /dev/null
@@ -1,147 +0,0 @@
-
- 4.0.0
-
-
- dynamixs-ai
- ai.dynamixs
- 1.0.0
-
- dynamixs-ai-rag
- jar
- Dynamixs AI RAG Library
-
-
-
-
-
-
-
-
- maven-compiler-plugin
- 3.8.1
-
- ${java.source}
- ${java.target}
-
-
-
-
-
- org.apache.maven.plugins
- maven-surefire-plugin
- 2.22.2
-
-
-
-
-
- org.apache.maven.plugins
- maven-surefire-plugin
- 3.3.1
-
-
- default-test
- test
-
- test
-
-
-
- --add-opens java.base/java.lang=ALL-UNNAMED
-
-
-
-
-
-
-
-
-
-
-
-
- jakarta.platform
- jakarta.jakartaee-api
- ${jakarta.version}
- provided
-
-
-
- org.eclipse.microprofile
- microprofile
- ${microprofile.version}
- pom
- provided
-
-
-
-
- org.imixs.workflow
- imixs-workflow-core
-
-
- org.imixs.workflow
- imixs-workflow-engine
-
-
- org.imixs.workflow
- imixs-workflow-jax-rs
-
-
- org.imixs.workflow
- imixs-workflow-faces
-
-
- org.imixs.workflow
- imixs-workflow-index-lucene
-
-
-
-
- org.imixs.workflow
- imixs-marty
-
-
-
-
- org.imixs.workflow
- imixs-archive-api
-
-
-
-
-
- org.imixs.workflow
- imixs-ai-workflow
-
-
-
-
- org.apache.cassandra
- java-driver-core
- ${apache.cassandra.driver.version}
-
-
- org.apache.cassandra
- java-driver-query-builder
- ${apache.cassandra.driver.version}
-
-
- org.apache.cassandra
- java-driver-mapper-runtime
- ${apache.cassandra.driver.version}
-
-
-
-
-
\ No newline at end of file
diff --git a/dynamixs-ai-rag/src/main/java/ai/dynamixs/DynamixsSetupService.java b/dynamixs-ai-rag/src/main/java/ai/dynamixs/DynamixsSetupService.java
deleted file mode 100644
index 5598344..0000000
--- a/dynamixs-ai-rag/src/main/java/ai/dynamixs/DynamixsSetupService.java
+++ /dev/null
@@ -1,69 +0,0 @@
-/*******************************************************************************
- * Imixs Workflow
- * Copyright (C) 2001, 2011 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:
- * http://www.imixs.org
- * http://java.net/projects/imixs-workflow
- *
- * Contributors:
- * Imixs Software Solutions GmbH - initial API and implementation
- * Ralph Soika - Software Developer
- *******************************************************************************/
-
-package ai.dynamixs;
-
-import java.util.logging.Logger;
-
-import org.imixs.workflow.engine.SetupEvent;
-
-import jakarta.annotation.security.DeclareRoles;
-import jakarta.annotation.security.RolesAllowed;
-import jakarta.ejb.Stateless;
-import jakarta.enterprise.event.Observes;
-
-/**
- * This DynamixsSetupService reacts on the
- * 'org.imixs.workflow.engine.SetupEvent' and initializes a Cassandra Session.
- * If the connection can not be established immediately the method retries a
- * connection every 10 seconds.
- *
- *
- *
- *
- * @author rsoika
- *
- */
-@DeclareRoles({ "org.imixs.ACCESSLEVEL.NOACCESS", "org.imixs.ACCESSLEVEL.READERACCESS",
- "org.imixs.ACCESSLEVEL.AUTHORACCESS", "org.imixs.ACCESSLEVEL.EDITORACCESS",
- "org.imixs.ACCESSLEVEL.MANAGERACCESS" })
-@RolesAllowed({ "org.imixs.ACCESSLEVEL.NOACCESS", "org.imixs.ACCESSLEVEL.READERACCESS",
- "org.imixs.ACCESSLEVEL.AUTHORACCESS", "org.imixs.ACCESSLEVEL.EDITORACCESS",
- "org.imixs.ACCESSLEVEL.MANAGERACCESS" })
-@Stateless
-public class DynamixsSetupService {
- private static Logger logger = Logger.getLogger(DynamixsSetupService.class.getName());
-
- /**
- * Init....
- *
- * @param setupEvent
- */
- public void setup(@Observes SetupEvent setupEvent) {
- //
- }
-}
diff --git a/dynamixs-ai-rag/src/main/java/ai/dynamixs/rag/RAGAdapter.java b/dynamixs-ai-rag/src/main/java/ai/dynamixs/rag/RAGAdapter.java
deleted file mode 100644
index bad8f47..0000000
--- a/dynamixs-ai-rag/src/main/java/ai/dynamixs/rag/RAGAdapter.java
+++ /dev/null
@@ -1,386 +0,0 @@
-/*
- * 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 java.util.stream.Collectors;
-
-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.EventLogService;
-import org.imixs.workflow.engine.WorkflowService;
-import org.imixs.workflow.exceptions.AdapterException;
-import org.imixs.workflow.exceptions.PluginException;
-
-import ai.dynamixs.rag.cluster.ClusterException;
-import ai.dynamixs.rag.cluster.ClusterService;
-import ai.dynamixs.rag.cluster.RetrievalResult;
-import ai.dynamixs.rag.events.RAGEventService;
-import jakarta.inject.Inject;
-
-/**
- * The RAGAdapter is used for Retrieval-Augmented Generation workflows.
- *
- * The adapter operates in two modes:
- *
- * In the 'INDEX' mode the adapter evaluates an associated imixs-ai prompt
- * definition and generates - based on the given prompt template - a text to be
- * indexed in the RAG database.
- *
- *
- * In the 'RETRIEVAL' mode, the adapter evaluates an associated imixs-ai prompt
- * definition to get retrieval embeddings from the RAG database.
- *
- *
- *
- * In both modes the Endpoint defines the Rest API endpoint of the llama-cpp
- * http server or any compatible OpenAI / Open API rest service endpoint.
- *
- * The Prompt Template is based on the concepts of Imixs-AI:
- *
- *
- *
- * The index Process is
- *
- * @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 RAG_INDEX = "INDEX";
- public static final String RAG_RETRIEVAL = "RETRIEVAL";
-
- private static Logger logger = Logger.getLogger(RAGAdapter.class.getName());
-
- @Inject
- @ConfigProperty(name = OpenAIAPIService.ENV_LLM_SERVICE_ENDPOINT)
- Optional serviceEndpoint;
-
- @Inject
- private WorkflowService workflowService;
-
- @Inject
- EventLogService eventLogService;
-
- @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 {
-
- logger.finest("...running api adapter...");
-
- // read optional configuration form the model or imixs.properties....
- List llmIndexDefinitions = workflowService.evalWorkflowResultXML(event, "imixs-ai",
- RAG_INDEX, workitem, false);
- List llmRetrievalDefinitions = workflowService.evalWorkflowResultXML(event, "imixs-ai",
- RAG_RETRIEVAL, workitem, false);
-
- // verify if we have an INDEX configuration
- if (llmIndexDefinitions != null && llmIndexDefinitions.size() > 0) {
- createIndex(llmIndexDefinitions, workitem, event);
- }
-
- // verify if we have an RETRIEVAL configuration
- if (llmRetrievalDefinitions != null && llmRetrievalDefinitions.size() > 0) {
- createRetrieval(llmRetrievalDefinitions, workitem, event);
- }
-
- return workitem;
- }
-
- /**
- * Creates a RAG Index for a Workitem based on IndexDefinitions
- *
- * @param llmIndexDefinitions
- * @param workitem
- * @param event
- * @throws PluginException
- * @throws AdapterException
- */
- private void createIndex(List llmIndexDefinitions, ItemCollection workitem, ItemCollection event)
- throws PluginException, AdapterException {
- long processingTime = System.currentTimeMillis();
- String llmAPIEndpoint = null;
- boolean llmAPIDebug = false;
- try {
- for (ItemCollection indexDefinition : llmIndexDefinitions) {
- llmAPIEndpoint = parseLLMEndpointByBPMN(indexDefinition);
- // do we have a valid endpoint?
- if (llmAPIEndpoint == null || llmAPIEndpoint.isEmpty()) {
- throw new PluginException(RAGAdapter.class.getSimpleName(), API_ERROR,
- "imixs-ai configuration error: no llm service endpoint defined!");
- }
- if ("true".equalsIgnoreCase(indexDefinition.getItemValueString("debug"))) {
- llmAPIDebug = true;
- }
-
- logger.info("├── post RAG index request: " + llmAPIEndpoint);
- String promptTemplate = llmService.loadPromptTemplate(event);
- indexDefinition.setItemValue("promptTemplate", promptTemplate);
- indexDefinition.setItemValue("debug", llmAPIDebug);
- indexDefinition.setItemValue("endpoint", llmAPIEndpoint);
-
- if (llmAPIDebug) {
- logger.info("│ ├── PromptTemplate: ");
- logger.info(promptTemplate);
- }
-
- // Build the prompt template...
- String llmPrompt = llmService.buildPrompt(promptTemplate, workitem);
- if (llmPrompt.isBlank()) {
- throw new PluginException(
- RAGAdapter.class.getSimpleName(), API_ERROR,
- "Unable to parse prompt for RAG indexing");
- }
- indexDefinition.setItemValue("prompt", llmPrompt);
-
- // send eventLog ....
- eventLogService.createEvent(
- RAGEventService.EVENTLOG_TOPIC_RAG_EVENT_INDEX,
- workitem.getUniqueID(),
- indexDefinition);
-
- // if (llmAPIDebug) {
- // logger.info("│ ├── Total Prompt Length = " + llmPrompt.length());
- // logger.info("│ ├── Prompt: ");
- // logger.info(llmPrompt);
- // }
-
- // // remove old embeddings
- // clusterService.removeAllEmbeddings(workitem.getUniqueID());
- // // chunk text....
- // List chunk_list = RAGUtil.chunkMarkupDocument(llmPrompt);
- // for (String chunk : chunk_list) {
- // if (llmAPIDebug) {
- // logger.info("│ ├── 🔸 chunk: ");
- // logger.info(chunk);
- // }
- // List indexResult = llmService.postEmbedding(chunk, llmAPIEndpoint,
- // llmAPIDebug);
- // // write to cassandra
- // clusterService.insertVector(workitem.getUniqueID(),
- // workitem.getModelVersion(),
- // workitem.getTaskID(),
- // chunk,
- // indexResult);
- // if (llmAPIDebug) {
- // logger.info("│ ├── ⇨ " + indexResult.size() + " floats stored in RAG db.");
- // }
- // }
-
- if (llmAPIDebug) {
- logger.info(
- "├── ✅ Total processing time: " + (System.currentTimeMillis() - processingTime) + "ms");
- }
- }
- } catch (PluginException e) {
- logger.severe("├── ⚠️ Failed to post embeddings: " + e.getMessage());
- throw e;
- }
- }
-
- /**
- * Creates a RAG retrieval for a Worktiem based on RetrievalDefinitions
- *
- * @param llmRetrievalfDefinitions
- * @param workitem
- * @param event
- * @throws PluginException
- * @throws AdapterException
- */
- private void createRetrieval(List llmRetrievalfDefinitions, ItemCollection workitem,
- ItemCollection event) throws PluginException, AdapterException {
- long processingTime = System.currentTimeMillis();
- String llmAPIEndpoint = null;
- boolean llmAPIDebug = false;
- try {
- for (ItemCollection indexDefinition : llmRetrievalfDefinitions) {
- llmAPIEndpoint = parseLLMEndpointByBPMN(indexDefinition);
- // do we have a valid endpoint?
- if (llmAPIEndpoint == null || llmAPIEndpoint.isEmpty()) {
- throw new PluginException(RAGAdapter.class.getSimpleName(), API_ERROR,
- "imixs-ai configuration error: no llm service endpoint defined!");
- }
- if ("true".equalsIgnoreCase(indexDefinition.getItemValueString("debug"))) {
- llmAPIDebug = true;
- }
-
- logger.info("├── post RAG Retrievalf request: " + llmAPIEndpoint);
- String promptTemplate = llmService.loadPromptTemplate(event);
- logger.info("│ ├── PromptTemplate: ");
- logger.info(promptTemplate);
- String llmPrompt = llmService.buildPrompt(promptTemplate, workitem);
- if (llmPrompt.isBlank()) {
- throw new PluginException(
- RAGAdapter.class.getSimpleName(), API_ERROR,
- "Unable to parse prompt for RAG indexing");
- }
- if (llmAPIDebug) {
- logger.info("│ ├── Total Prompt Length = " + llmPrompt.length());
- logger.info("│ ├── Prompt: ");
- logger.info(llmPrompt);
- }
-
- // retrieve prompt....
- List embeddings = llmService.postEmbedding(llmPrompt, llmAPIEndpoint, llmAPIDebug);
- if (llmAPIDebug) {
- logger.info("├── ⇨ " + embeddings.size() + " floats stored in RAG db.");
- }
- // search cassandra
- List retrievalResultList = clusterService.searchEmbeddings(embeddings, 5);
- List listOfIds = retrievalResultList.stream()
- .map(RetrievalResult::getUniqueId)
- .collect(Collectors.toList());
- workitem.setItemValue("product.ref", listOfIds);
- if (llmAPIDebug) {
- logger.info(
- "├── found " + retrievalResultList.size() + " matches");
- logger.info(
- "├── ✅ Total processing time: " + (System.currentTimeMillis() - processingTime) + "ms");
- }
- }
- } catch (PluginException e) {
- logger.severe("├── ⚠️ Failed to post embeddings: " + e.getMessage());
- throw e;
- } catch (ClusterException e) {
- throw new PluginException(RAGAdapter.class.getSimpleName(), API_ERROR, e.getMessage(), e);
- }
- }
-
- /**
- * 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;
- }
-
-}
diff --git a/dynamixs-ai-rag/src/main/java/ai/dynamixs/rag/RAGPlugin.java b/dynamixs-ai-rag/src/main/java/ai/dynamixs/rag/RAGPlugin.java
deleted file mode 100644
index f75f1e2..0000000
--- a/dynamixs-ai-rag/src/main/java/ai/dynamixs/rag/RAGPlugin.java
+++ /dev/null
@@ -1,107 +0,0 @@
-/*
- * 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.logging.Logger;
-
-import org.imixs.workflow.ItemCollection;
-import org.imixs.workflow.engine.EventLogService;
-import org.imixs.workflow.engine.WorkflowService;
-import org.imixs.workflow.engine.plugins.AbstractPlugin;
-import org.imixs.workflow.exceptions.PluginException;
-
-import ai.dynamixs.rag.events.RAGEventService;
-import jakarta.inject.Inject;
-
-/**
- * The RAGIndexPlugin updates the workflow meta data for existing embeddings.
- * The plugin can be deactivated with:
- *
- *
- *
- {@code
-
- false
-
- }
- *
- *
- * The tag 'debug' is optional
- *
- *
- * @author rsoika
- *
- */
-public class RAGPlugin extends AbstractPlugin {
-
- private static final Logger logger = Logger.getLogger(RAGPlugin.class.getName());
-
- @Inject
- private WorkflowService workflowService;
-
- @Inject
- EventLogService eventLogService;
-
- @Override
- public ItemCollection run(ItemCollection workitem, ItemCollection event) throws PluginException {
- List deleteDefinitions = null;
- List disableDefinitions = null;
-
- logger.finest("running TaxonomyPlugin");
- String workflowResult = event.getItemValueString("workflow.result");
- if (workflowResult.contains(" 0) {
- // no op!
- return workitem;
- }
-
- // delete mode?
- if (deleteDefinitions != null && deleteDefinitions.size() > 0) {
- eventLogService.createEvent(RAGEventService.EVENTLOG_TOPIC_RAG_EVENT_DELETE, workitem.getUniqueID());
- return workitem;
- }
-
- // default update meta information
- eventLogService.createEvent(RAGEventService.EVENTLOG_TOPIC_RAG_EVENT_UPDATE, workitem.getUniqueID());
-
- return workitem;
-
- }
-
-}
diff --git a/dynamixs-ai-rag/src/main/java/ai/dynamixs/rag/RAGUtil.java b/dynamixs-ai-rag/src/main/java/ai/dynamixs/rag/RAGUtil.java
deleted file mode 100644
index 4bd341a..0000000
--- a/dynamixs-ai-rag/src/main/java/ai/dynamixs/rag/RAGUtil.java
+++ /dev/null
@@ -1,101 +0,0 @@
-/*
- * 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.ArrayList;
-import java.util.List;
-import java.util.logging.Logger;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-/**
- * The RAGUtil provides methods to chunk markup text
- *
- * @author Ralph Soika
- * @version 1.0
- *
- */
-public class RAGUtil {
-
- private static Logger logger = Logger.getLogger(RAGAdapter.class.getName());
-
- /**
- * This helper method creates smaller text chunks from a large markup text.
- */
- public static List chunkMarkupDocument(String markupText) {
- List chunks = new ArrayList<>();
- StringBuilder currentChunk = new StringBuilder();
-
- // Regex für Abschnitts-Header (# oder **)
- Pattern sectionPattern = Pattern.compile("^(# .+|\\*\\*.+\\*\\*)", Pattern.MULTILINE);
- Matcher matcher = sectionPattern.matcher(markupText);
-
- int lastEnd = 0;
- while (matcher.find()) {
- // Text zwischen den Headern
- String sectionContent = markupText.substring(lastEnd, matcher.start()).trim();
-
- if (!sectionContent.isEmpty()) {
- // Füge Inhalt zum aktuellen Chunk hinzu
- if (currentChunk.length() + sectionContent.length() > 500) {
- flushChunk(chunks, currentChunk);
- }
- currentChunk.append(sectionContent).append("\n");
- }
-
- // Header selbst behandeln
- String header = matcher.group(1);
- if (currentChunk.length() + header.length() > 300) {
- flushChunk(chunks, currentChunk);
- }
- currentChunk.append(header).append("\n");
-
- lastEnd = matcher.end();
- }
-
- // Restlichen Text nach dem letzten Header
- String remainingContent = markupText.substring(lastEnd).trim();
- if (!remainingContent.isEmpty()) {
- currentChunk.append(remainingContent);
- flushChunk(chunks, currentChunk);
- } else if (currentChunk.length() > 0) {
- chunks.add(currentChunk.toString().trim());
- }
-
- return chunks;
- }
-
- private static void flushChunk(List chunks, StringBuilder currentChunk) {
- if (currentChunk.length() > 0) {
- chunks.add(currentChunk.toString().trim());
- currentChunk.setLength(0);
- }
- }
-
-}
diff --git a/dynamixs-ai-rag/src/main/java/ai/dynamixs/rag/cluster/ClusterException.java b/dynamixs-ai-rag/src/main/java/ai/dynamixs/rag/cluster/ClusterException.java
deleted file mode 100644
index 0bda617..0000000
--- a/dynamixs-ai-rag/src/main/java/ai/dynamixs/rag/cluster/ClusterException.java
+++ /dev/null
@@ -1,99 +0,0 @@
-/*******************************************************************************
- * Imixs Workflow
- * Copyright (C) 2001, 2011 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:
- * http://www.imixs.org
- * http://java.net/projects/imixs-workflow
- *
- * Contributors:
- * Imixs Software Solutions GmbH - initial API and implementation
- * Ralph Soika - Software Developer
- *******************************************************************************/
-
-package ai.dynamixs.rag.cluster;
-
-/**
- * The ClusterService provides methods to persist the content of a Imixs AI
- * dataset into a Cassandra keystore.
- *
- * The ClusterService creates a Core-KeySpace automatically which is used for
- * the internal management.
- *
- * @author rsoika
- * @version 1.0
- */
-public class ClusterException extends Exception {
-
- private static final long serialVersionUID = 1L;
-
- protected String errorContext = "UNDEFINED";
- protected String errorCode = "UNDEFINED";
-
- public static final String INVALID_DOCUMENT_OBJECT = "INVALID_DOCUMENT_OBJECT";
- public static final String INVALID_KEYSPACE = "INVALID_KEYSPACE";
- public static final String CLUSTER_ERROR = "CLUSTER_ERROR";
- public static final String INVALID_WORKITEM = "INVALID_WORKITEM";
- public static final String MD5_ERROR = "MD5_ERROR";
-
- public static final String MISSING_CONTACTPOINT = "MISSING_CONTACTPOINT";
- public static final String SYNC_ERROR = "SYNC_ERROR";
-
- public ClusterException(String aErrorCode, String message) {
- super(message);
- errorCode = aErrorCode;
-
- }
-
- public ClusterException(String aErrorContext, String aErrorCode, String message) {
- super(message);
- errorContext = aErrorContext;
- errorCode = aErrorCode;
-
- }
-
- public ClusterException(String aErrorContext, String aErrorCode, String message, Exception e) {
- super(message, e);
- errorContext = aErrorContext;
- errorCode = aErrorCode;
-
- }
-
- public ClusterException(String aErrorCode, String message, Exception e) {
- super(message, e);
-
- errorCode = aErrorCode;
-
- }
-
- public String getErrorContext() {
- return errorContext;
- }
-
- public void setErrorContext(String errorContext) {
- this.errorContext = errorContext;
- }
-
- public String getErrorCode() {
- return errorCode;
- }
-
- public void setErrorCode(String errorCode) {
- this.errorCode = errorCode;
- }
-
-}
diff --git a/dynamixs-ai-rag/src/main/java/ai/dynamixs/rag/cluster/ClusterService.java b/dynamixs-ai-rag/src/main/java/ai/dynamixs/rag/cluster/ClusterService.java
deleted file mode 100644
index 4868e60..0000000
--- a/dynamixs-ai-rag/src/main/java/ai/dynamixs/rag/cluster/ClusterService.java
+++ /dev/null
@@ -1,753 +0,0 @@
-package ai.dynamixs.rag.cluster;
-
-import java.net.InetSocketAddress;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Optional;
-import java.util.UUID;
-import java.util.logging.Level;
-import java.util.logging.Logger;
-import java.util.stream.Collectors;
-
-import org.eclipse.microprofile.config.inject.ConfigProperty;
-
-import com.datastax.oss.driver.api.core.CqlSession;
-import com.datastax.oss.driver.api.core.CqlSessionBuilder;
-import com.datastax.oss.driver.api.core.InvalidKeyspaceException;
-import com.datastax.oss.driver.api.core.cql.BoundStatement;
-import com.datastax.oss.driver.api.core.cql.PreparedStatement;
-import com.datastax.oss.driver.api.core.cql.ResultSet;
-import com.datastax.oss.driver.api.core.cql.Row;
-import com.datastax.oss.driver.api.core.data.CqlVector;
-import com.datastax.oss.driver.api.core.servererrors.InvalidQueryException;
-
-import jakarta.annotation.PostConstruct;
-import jakarta.annotation.PreDestroy;
-import jakarta.annotation.Resource;
-import jakarta.ejb.Singleton;
-import jakarta.ejb.Startup;
-import jakarta.ejb.Timeout;
-import jakarta.ejb.Timer;
-import jakarta.ejb.TimerConfig;
-import jakarta.ejb.TimerService;
-import jakarta.inject.Inject;
-
-/**
- * The ClusterService provides methods to persist the content of a Imixs
- * Document into a Cassandra keystore.
- *
- * The service saves the content in XML format. The size of an XML
- * representation of a Imixs document is only slightly different in size from
- * the serialized map object. This is the reason why we do not store the
- * document map in a serialized object format.
- *
- * The ClusterService creates a Core-KeySpace automatically which is used for
- * the internal management.
- *
- * @author rsoika
- *
- */
-@Singleton
-@Startup
-public class ClusterService {
-
- public static final String KEYSPACE_REGEX = "^[a-z_]*[^-]$";
- public static final int SEARCH_LIMIT_MAX = 100;
-
- // mandatory environment settings
- public static final String ENV_EMBEDDINGS_CLUSTER_CONTACTPOINTS = "EMBEDDINGS_CLUSTER_CONTACTPOINTS";
- public static final String ENV_EMBEDDINGS_CLUSTER_KEYSPACE = "EMBEDDINGS_CLUSTER_KEYSPACE";
-
- public static final int DIMENSIONS = 768;
- // public static final int DIMENSIONS = 384;
-
- // optional environment settings
- public static final String ENV_EMBEDDINGS_CLUSTER_AUTH_USER = "EMBEDDINGS_CLUSTER_AUTH_USER";
- public static final String ENV_EMBEDDINGS_CLUSTER_AUTH_PASSWORD = "EMBEDDINGS_CLUSTER_AUTH_PASSWORD";
- public static final String ENV_EMBEDDINGS_CLUSTER_SSL = "EMBEDDINGS_CLUSTER_SSL";
- public static final String ENV_EMBEDDINGS_CLUSTER_SSL_TRUSTSTOREPATH = "EMBEDDINGS_CLUSTER_SSL_TRUSTSTOREPATH";
- public static final String ENV_EMBEDDINGS_CLUSTER_SSL_TRUSTSTOREPASSWORD = "EMBEDDINGS_CLUSTER_SSL_TRUSTSTOREPASSWORD";
- public static final String ENV_EMBEDDINGS_CLUSTER_SSL_KEYSTOREPATH = "EMBEDDINGS_CLUSTER_SSL_KEYSTOREPATH";
- public static final String ENV_EMBEDDINGS_CLUSTER_SSL_KEYSTOREPASSWORD = "EMBEDDINGS_CLUSTER_SSL_KEYSTOREPASSWORD";
-
- public static final String ENV_EMBEDDINGS_CLUSTER_REPLICATION_FACTOR = "EMBEDDINGS_CLUSTER_REPLICATION_FACTOR";
- public static final String ENV_EMBEDDINGS_CLUSTER_REPLICATION_CLASS = "EMBEDDINGS_CLUSTER_REPLICATION_CLASS";
-
- // workflow rest service endpoint
- public static final String ENV_WORKFLOW_SERVICE_ENDPOINT = "WORKFLOW_SERVICE_ENDPOINT";
- public static final String ENV_WORKFLOW_SERVICE_USER = "WORKFLOW_SERVICE_USER";
- public static final String ENV_WORKFLOW_SERVICE_PASSWORD = "WORKFLOW_SERVICE_PASSWORD";
- public static final String ENV_WORKFLOW_SERVICE_AUTHMETHOD = "WORKFLOW_SERVICE_AUTHMETHOD";
-
- private static Logger logger = Logger.getLogger(ClusterService.class.getName());
-
- @Inject
- @ConfigProperty(name = ENV_EMBEDDINGS_CLUSTER_REPLICATION_FACTOR, defaultValue = "1")
- String repFactor;
-
- @Inject
- @ConfigProperty(name = ENV_EMBEDDINGS_CLUSTER_REPLICATION_CLASS, defaultValue = "SimpleStrategy")
- String repClass;
-
- @Inject
- @ConfigProperty(name = ENV_EMBEDDINGS_CLUSTER_CONTACTPOINTS)
- Optional contactPoint;
-
- @Inject
- @ConfigProperty(name = ENV_EMBEDDINGS_CLUSTER_KEYSPACE)
- Optional keySpace;
-
- @Inject
- @ConfigProperty(name = ENV_EMBEDDINGS_CLUSTER_AUTH_USER)
- Optional userid;
-
- @Inject
- @ConfigProperty(name = ENV_EMBEDDINGS_CLUSTER_AUTH_PASSWORD)
- Optional password;
-
- @Inject
- @ConfigProperty(name = ENV_EMBEDDINGS_CLUSTER_SSL, defaultValue = "false")
- boolean bUseSSL;
-
- @Inject
- @ConfigProperty(name = ENV_EMBEDDINGS_CLUSTER_SSL_TRUSTSTOREPATH)
- Optional truststorePath;
-
- @Inject
- @ConfigProperty(name = ENV_EMBEDDINGS_CLUSTER_SSL_TRUSTSTOREPASSWORD)
- Optional truststorePwd;
-
- @Inject
- @ConfigProperty(name = ENV_EMBEDDINGS_CLUSTER_SSL_KEYSTOREPATH)
- Optional keystorePath;
-
- @Inject
- @ConfigProperty(name = ENV_EMBEDDINGS_CLUSTER_SSL_KEYSTOREPASSWORD)
- Optional keystorePwd;
-
- private CqlSession session;
-
- private PreparedStatement insertVectorStmt;
- // private PreparedStatement searchVectorStmt;
- private final Map searchVectorStatements = new HashMap<>();
- private PreparedStatement selectVectorStmt;
- private PreparedStatement removeVectorStmt;
- private PreparedStatement selectChunksStmt = null;
- private PreparedStatement updateChunkStmt = null;
-
- @Resource
- private TimerService timerService;
-
- @PostConstruct
- public void init() {
- scheduleClusterCheck();
- }
-
- /**
- * Versucht, die Session herzustellen. Bei Fehler wird ein neuer Timer geplant.
- */
- private void scheduleClusterCheck() {
- try {
- logger.log(Level.INFO, "├── connecting Cassandra cluster...");
- getSession();
- logger.log(Level.INFO, "├── ✅ successfully connected to cassandra!");
- } catch (ClusterException e) {
- logger.log(Level.WARNING, "├── " + e.getMessage());
- logger.log(Level.WARNING, "├── ⚠️ cluster not ready yet. retrying in 10 seconds...");
- // retry in 10 sec
- timerService.createSingleActionTimer(10000, new TimerConfig());
- }
- }
-
- /**
- * called by timer. Tries to connect cassandra clauster
- */
- @Timeout
- public void retryClusterCheck(Timer timer) {
- scheduleClusterCheck(); // retry Check
- }
-
- @PreDestroy
- private void tearDown() {
- // close session and cluster object
- if (session != null) {
- session.close();
- }
- }
-
- /**
- * Returns the Cassandra Session object. If the session is not yet created, the
- * method tries to init the session the first time. If initialization failed the
- * method throws a ClusterException
- *
- * Note: the initialization is only successful if the cassandra cluster is fully
- * started.
- *
- * The method also verifies the keyspace and creates a table schema if not yet
- * available.
- *
- * @return
- */
- public CqlSession getSession() throws ClusterException {
- if (session == null) {
- try {
- logger.info("├── initializing cluster and keyspace...");
- session = initSessionWithKeyspace();
- logger.info("│ ├── ✅ schema status = OK");
-
- createSchema();
-
- } catch (Exception e) {
- throw new ClusterException(ClusterException.CLUSTER_ERROR,
- "Failed to init cassandra session: " + e.getMessage(), e);
- }
- }
- return session;
- }
-
- /**
- * This method opens the configured keyspace. If the keyspace does no yet exist
- * it creates a new keyspace with the table schema
- *
- * @return
- * @throws ClusterException
- */
- private CqlSession initSessionWithKeyspace() throws ClusterException {
- // validate keyspace name
- String keySpacename = keySpace.orElseThrow(
- () -> new ClusterException(ClusterException.INVALID_KEYSPACE, "EMBEDDINGS_CLUSTER_KEYSPACE not set!"));
-
- if (!isValidKeyspaceName(keySpacename)) {
- throw new ClusterException(ClusterException.INVALID_KEYSPACE,
- "keyspace '" + keySpacename + "' name invalid.");
- }
-
- try {
- // try to connect keyspace ...
- session = createSession(keySpacename);
- logger.info("│ ├── ✅ keyspace '" + keySpacename + "' status = OK");
- return session;
- } catch (InvalidQueryException | InvalidKeyspaceException e) {
- logger.warning("│ ├── ⚠️ Keyspace does not yet exist, creating new keyspace...");
- // Create a basic session object to create the keyspace....
- session = createSession(null);
- createKeySpace(keySpacename);
- // close and reconnect session...
- session.close();
- session = createSession(keySpacename);
-
- return session;
- }
- }
-
- /**
- * This method creates a Cassandra Cluster object. The cluster is defined by
- * ContactPoints provided in the environmetn variable
- * 'EMBEDDINGS__CLUSTER_CONTACTPOINTS' or in the imixs.property
- * 'archive.cluster.contactpoints'
- *
- * @return Cassandra Cluster instacne
- */
-
- private CqlSession createSession(String keyspace) throws ClusterException {
- CqlSessionBuilder builder = CqlSession.builder();
- // Boolean used to check if at least one host could be resolved
- boolean found = false;
-
- if (!contactPoint.isPresent() || contactPoint.get().isEmpty()) {
- throw new ClusterException(ClusterException.MISSING_CONTACTPOINT,
- "missing cluster contact points - verify configuration!");
- }
-
- logger.info("│ ├── cluster connecting: " + contactPoint.get());
- String[] hosts = contactPoint.get().split(",");
- for (String host : hosts) {
- try {
- logger.info("│ ├── adding host: " + host + ":9042");
- builder.addContactPoint(new InetSocketAddress(host, 9042));
- // One host could be resolved
- found = true;
- } catch (IllegalArgumentException e) {
- // This host could not be resolved so we log a message and keep going
- logger.warning("...the host '" + host + "' is unknown so it will be ignored");
- }
- }
- if (!found) {
- // No host could be resolved so we throw an exception
- throw new IllegalStateException("All provided hosts are unknown - check cluster status and configuration!");
- }
- // set optional credentials...
- if (userid.isPresent() && !userid.get().isEmpty()) {
- builder = builder.withAuthCredentials(userid.get(), password.get());
- }
-
- if (keyspace != null) {
- builder.withKeyspace(keyspace);
- }
- builder.withLocalDatacenter("datacenter1");
- return builder.build();
- }
-
- /**
- * Test if the keyspace name is valid.
- *
- * @param keySpace
- * @return
- */
- public boolean isValidKeyspaceName(String keySpace) {
- if (keySpace == null || keySpace.isEmpty()) {
- return false;
- }
- return keySpace.matches(KEYSPACE_REGEX);
- }
-
- /**
- * This method creates a cassandra keySpace
- *
- * @param cluster
- */
- private void createKeySpace(String keySpace) throws ClusterException {
- logger.info("│ ├── creating new keyspace '" + keySpace + "'...");
-
- String statement = "CREATE KEYSPACE IF NOT EXISTS " + keySpace + " WITH replication = {'class': '" + repClass
- + "', 'replication_factor': " + repFactor + "};";
-
- session.execute(statement);
- logger.info("│ ├── ✅ keyspace '" + keySpace + "' created.");
-
- }
-
- /**
- * This method creates the keySpace schema.
- *
- * CREATE TABLE embeddings.document_vectors ( business_document_id uuid,
- * chunk_id text, chunk_text text, content_vector VECTOR , PRIMARY
- * KEY (business_document_id, chunk_id) ); );
- *
- * @param cluster
- */
- private void createSchema() throws ClusterException {
-
- logger.info("│ ├── verify schema...");
- // now create table schemas
- String query = "CREATE TABLE IF NOT EXISTS document_vectors (\n" + //
- " id text,\n" + //
- " chunk_id uuid,\n" + //
- " model_version text,\n" + //
- " task_id int,\n" + //
- " content_chunk text,\n" + //
- " content_vector VECTOR ,\n" + //
- " PRIMARY KEY (id, chunk_id)\n" + //
- ");";
- session.execute(query);
-
- logger.info("│ ├── verify index...");
- query = "CREATE INDEX IF NOT EXISTS edv_ann_index\n" + //
- " ON document_vectors(content_vector) USING 'sai';";
- session.execute(query);
-
- // Additional indexes for metadata filtering
- query = "CREATE INDEX IF NOT EXISTS idx_model_version " + //
- " ON document_vectors(model_version) USING 'sai';";
- session.execute(query);
- query = "CREATE INDEX IF NOT EXISTS idx_task_id " + //
- " ON document_vectors(task_id) USING 'sai';";
- session.execute(query);
-
- logger.info("│ ├── ✅ database schema OK.");
- }
-
- /**
- * Inserts an embedding together with a content chunk into the database. The
- * uniqueID is the reference to the corresponding workitem holding the full meta
- * data.
- *
- * @param uniqueID
- * @param modelVersion
- * @param taskId
- * @param content
- * @param vector
- * @throws ClusterException
- */
- public void insertVector(String uniqueID, String modelVersion, int taskId, String content, List vector)
- throws ClusterException {
-
- // Prepare statement?
- if (insertVectorStmt == null) {
- String insertQuery = "INSERT INTO document_vectors "
- + "(id, chunk_id, model_version, task_id, content_chunk, content_vector) VALUES (?, ?, ?, ?, ?, ?)";
- insertVectorStmt = session.prepare(insertQuery);
- }
-
- try {
- // Generiere UUID
- UUID chunk_id = UUID.randomUUID();
- // CqlVector
- CqlVector cqlVector = CqlVector.newInstance(vector);
-
- BoundStatement boundStmt = insertVectorStmt.bind(uniqueID, chunk_id, modelVersion, taskId, content,
- cqlVector);
- session.execute(boundStmt);
-
- } catch (Exception e) {
- throw new ClusterException(ClusterException.CLUSTER_ERROR,
- "Failed to insert embeddings into keyspace: " + e.getMessage(), e);
- }
- }
-
- /**
- * Removes all embeddings for a given uniqueID. The method returns true if
- * entries have existed.
- *
- * @param uniqueID
- * @param content
- * @param vector
- * @throws ClusterException
- */
- public boolean removeAllEmbeddings(String uniqueID) throws ClusterException {
- // Prepare statement if not already done
- if (removeVectorStmt == null) {
- String deleteQuery = "DELETE FROM document_vectors WHERE id = ?";
- removeVectorStmt = session.prepare(deleteQuery);
- }
-
- try {
- // test if entries exist
- long count = countIndexEntries(uniqueID);
- if (count > 0) {
- // remove all
- BoundStatement deleteBoundStmt = removeVectorStmt.bind(uniqueID);
- session.execute(deleteBoundStmt);
- logger.info("│ ├── ✅ Removed " + count + " entries for uniqueID: " + uniqueID);
- return true;
- } else {
- // logger.info("│ ├── ℹ️ No entries found for uniqueID: " + uniqueID);
- }
-
- } catch (Exception e) {
- throw new ClusterException(ClusterException.CLUSTER_ERROR,
- "Failed to remove entries for uniqueID '" + uniqueID + "': " + e.getMessage(), e);
- }
- return false;
- }
-
- /**
- * This method returns the number of index entries with the given uniqueId. The
- * method can be used to test if index data is available.
- *
- * @param uniqueId
- * @return - count of entries, or 0 if no entries exist
- * @throws ClusterException
- */
- public long countIndexEntries(String uniqueId) throws ClusterException {
- if (selectVectorStmt == null) {
- String selectQuery = "SELECT COUNT(*) FROM document_vectors WHERE id = ?";
- selectVectorStmt = session.prepare(selectQuery);
- }
- try {
- // test if entries exist
- BoundStatement selectBoundStmt = selectVectorStmt.bind(uniqueId);
- ResultSet resultSet = session.execute(selectBoundStmt);
- Row row = resultSet.one();
-
- if (row != null) {
- long count = row.getLong(0);
- return count;
- }
- } catch (Exception e) {
- throw new ClusterException(ClusterException.CLUSTER_ERROR,
- "Failed to lookup entries for uniqueID '" + uniqueId + "': " + e.getMessage(), e);
- }
- return 0;
- }
-
- /**
- * Performs a semantic search on the document vector database using cosine
- * similarity.
- *
- * This method searches for the most relevant document chunks based on the
- * provided query embedding. If multiple chunks exist for the same document ID,
- * only the chunk with the highest similarity score is returned. The results are
- * sorted by relevance score in descending order.
- *
- * The params 'modelVrsions' and 'taskIds' are optional and can be used to
- * refine the search result to specific process instances only.
- *
- * @param queryEmbedding The embedding vector of the search query as a list of
- * float values. The dimensionality must match the
- * DIMENSIONS constant used in the database schema.
- * @return A list of {@link RetrievalResult} objects containing the most
- * relevant documents, limited to a maximum of 5 results. Each result
- * includes the document ID, the best matching content chunk, and the
- * similarity score. The list is sorted by similarity score in
- * descending order (most relevant first).
- * @throws ClusterException if the database query fails or if there's an error
- * processing the embedding vector
- *
- * @see RetrievalResult
- * @since 1.0
- */
- public List searchEmbeddings(List embedding,
- int maxResults,
- List modelVersions,
- List taskIds,
- Integer taskIdRangeStart,
- Integer taskIdRangeEnd) throws ClusterException {
-
- if (maxResults <= 0) {
- throw new ClusterException(ClusterException.CLUSTER_ERROR,
- "maxResults must be greater than 0, but was: " + maxResults);
- }
- // Calculate a reasonable DB limit based on maxResults
- // Factor 3-4 times to have enough chunks for deduplication.
- int searchLimit = Math.min(maxResults * 4, SEARCH_LIMIT_MAX); // Max 100
-
- StringBuilder queryBuilder = new StringBuilder();
- queryBuilder.append("SELECT id, model_version, task_id, content_chunk, ")
- .append("similarity_cosine(content_vector, ?) ")
- .append("FROM document_vectors ");
-
- List whereConditions = new ArrayList<>();
- // Model version filtering (with wildcard support)
- if (modelVersions != null && !modelVersions.isEmpty()) {
- List modelConditions = new ArrayList<>();
- for (String modelVersion : modelVersions) {
- if (modelVersion.endsWith("*")) {
- // Wildcard search using LIKE
- modelConditions.add("model_version LIKE ?");
- } else {
- // Exact match
- modelConditions.add("model_version = ?");
- }
- }
- whereConditions.add("(" + String.join(" OR ", modelConditions) + ")");
- }
-
- // Task ID filtering - either list or range
- if (taskIds != null && !taskIds.isEmpty()) {
- whereConditions.add("task_id IN " + formatInClause(taskIds.size()));
- } else if (taskIdRangeStart != null && taskIdRangeEnd != null) {
- whereConditions.add("task_id >= ? AND task_id <= ?");
- } else if (taskIdRangeStart != null) {
- whereConditions.add("task_id >= ?");
- } else if (taskIdRangeEnd != null) {
- whereConditions.add("task_id <= ?");
- }
-
- if (!whereConditions.isEmpty()) {
- queryBuilder.append("WHERE ").append(String.join(" AND ", whereConditions)).append(" ");
- }
-
- queryBuilder.append("ORDER BY content_vector ANN OF ? LIMIT ").append(searchLimit);
- // Prepare statement (with caching)
- String statementKey = buildStatementKey(searchLimit, modelVersions, taskIds,
- taskIdRangeStart, taskIdRangeEnd);
- PreparedStatement searchStmt = searchVectorStatements.get(statementKey);
- if (searchStmt == null) {
- searchStmt = session.prepare(queryBuilder.toString());
- searchVectorStatements.put(statementKey, searchStmt);
- }
-
- try {
- // use CqlVector
- CqlVector cqlVector = CqlVector.newInstance(embedding);
- // Build parameters list
- List