diff --git a/dynamixs-ai-app/pom.xml b/dynamixs-ai-app/pom.xml index 5e679e1..1b9a0da 100644 --- a/dynamixs-ai-app/pom.xml +++ b/dynamixs-ai-app/pom.xml @@ -272,7 +272,6 @@ ${apache.pdfbox.version} - org.imixs.workflow @@ -280,24 +279,6 @@ ${org.imixs.ai.version} - - - com.datastax.oss - java-driver-core - ${apache.cassandra.driver.version} - - - com.datastax.oss - java-driver-query-builder - ${apache.cassandra.driver.version} - - - com.datastax.oss - java-driver-mapper-runtime - ${apache.cassandra.driver.version} - - - org.imixs.workflow diff --git a/dynamixs-ai-rag/pom.xml b/dynamixs-ai-rag/pom.xml index f3b0c74..1a826b4 100644 --- a/dynamixs-ai-rag/pom.xml +++ b/dynamixs-ai-rag/pom.xml @@ -166,17 +166,17 @@ - com.datastax.oss + org.apache.cassandra java-driver-core ${apache.cassandra.driver.version} - com.datastax.oss + org.apache.cassandra java-driver-query-builder ${apache.cassandra.driver.version} - com.datastax.oss + org.apache.cassandra java-driver-mapper-runtime ${apache.cassandra.driver.version} diff --git a/dynamixs-ai-rag/src/main/java/ai/dynamixs/rag/ClusterService.java b/dynamixs-ai-rag/src/main/java/ai/dynamixs/rag/ClusterService.java index 0c259b1..cdb50ab 100644 --- a/dynamixs-ai-rag/src/main/java/ai/dynamixs/rag/ClusterService.java +++ b/dynamixs-ai-rag/src/main/java/ai/dynamixs/rag/ClusterService.java @@ -1,7 +1,7 @@ package ai.dynamixs.rag; import java.net.InetSocketAddress; -import java.time.Instant; +import java.util.List; import java.util.Optional; import java.util.UUID; import java.util.logging.Level; @@ -15,6 +15,7 @@ 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.data.CqlVector; import com.datastax.oss.driver.api.core.servererrors.InvalidQueryException; import jakarta.annotation.PostConstruct; @@ -53,6 +54,9 @@ public class ClusterService { 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"; @@ -118,6 +122,7 @@ public class ClusterService { Optional keystorePwd; private CqlSession session; + private PreparedStatement insertVectorStmt; @Resource private TimerService timerService; @@ -178,6 +183,7 @@ public class ClusterService { logger.info("├── initializing cluster and keyspace..."); session = initSessionWithKeyspace(); logger.info("│ ├── ✅ schema status = OK"); + } catch (Exception e) { throw new ClusterException(ClusterException.CLUSTER_ERROR, "Failed to init cassandra session: " + e.getMessage(), e); @@ -303,11 +309,61 @@ public class ClusterService { /** * 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 * @throws ArchiveException */ private void createSchema() throws ClusterException { + logger.info("│ ├── create schema..."); + // now create table schemas + String query = "CREATE TABLE IF NOT EXISTS document_vectors (\n" + // + " id text,\n" + // + " chunk_id uuid,\n" + // + " content_chunk text,\n" + // + " content_vector VECTOR ,\n" + // + " PRIMARY KEY (id, chunk_id)\n" + // + ");"; + session.execute(query); + + logger.info("│ ├── create index..."); + query = "CREATE INDEX IF NOT EXISTS edv_ann_index\n" + // + " ON document_vectors(content_vector) USING 'sai';"; + + session.execute(query); + logger.info("│ ├── ✅ database schema created."); + } + + public void insertVector(String uniqueID, String content, List vector) throws ClusterException { + + // Prepare statement? + if (insertVectorStmt == null) { + String insertQuery = "INSERT INTO document_vectors " + + "(id, chunk_id, content_chunk, content_vector) VALUES (?, ?, ?, ?)"; + insertVectorStmt = session.prepare(insertQuery); + } + + try { + // Generiere UUID und aktuelle Zeit + UUID chunk_id = UUID.randomUUID(); + + // CqlVector verwenden + CqlVector cqlVector = CqlVector.newInstance(vector); + + BoundStatement boundStmt = insertVectorStmt.bind(uniqueID, chunk_id, content, cqlVector); + session.execute(boundStmt); + + } catch (Exception e) { + throw new ClusterException(ClusterException.CLUSTER_ERROR, + "Failed to insert embeddings into keyspace: " + e.getMessage(), e); + } + } + + private void oldcreateSchema() throws ClusterException { + logger.info("│ ├── create schema..."); // now create table schemas String query = "CREATE TABLE IF NOT EXISTS document_vectors (\n" + // @@ -328,44 +384,4 @@ public class ClusterService { session.execute(query); logger.info("│ ├── ✅ database schema created."); } - - public void insertVector(String content, float[] vector) throws ClusterException { - String keySpacename = keySpace.orElse(null); - try { - // Generiere UUID und aktuelle Zeit - UUID id = UUID.randomUUID(); - Instant createdAt = Instant.now(); - - // CQL-Query mit Platzhaltern - String insertQuery = "INSERT INTO " + keySpacename + ".document_vectors " - + "(record_id, id, content_chunk, content_vector, created_at) VALUES (now(), ?, ?, ?, ?)"; - - // Vorbereiten und Ausführen der Query - PreparedStatement preparedStmt = session.prepare(insertQuery); - BoundStatement boundStmt = preparedStmt.bind(id, content, vector, createdAt); - session.execute(boundStmt); - - // id = uuid.uuid4() - // content_chunk = content - // content_vector = vector - // created_at = datetime.datetime.now() - // insert_query = """ - // INSERT INTO embeddings.document_vectors (record_id, id, content_chunk, - // content_vector, created_at) - // VALUES (now(), ?, ?, ?, ?) - // """ - // prepared_stmt = session.prepare(insert_query) - // session.execute(prepared_stmt, [ - // id, - // content_chunk, - // content_vector, - // created_at - // ]) - - } catch (Exception e) { - throw new ClusterException(ClusterException.CLUSTER_ERROR, - "Failed to insert embeddings into keyspace: " + e.getMessage(), e); - } - } - } 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 index 17b7e89..59b80be 100644 --- a/dynamixs-ai-rag/src/main/java/ai/dynamixs/rag/RAGAdapter.java +++ b/dynamixs-ai-rag/src/main/java/ai/dynamixs/rag/RAGAdapter.java @@ -200,13 +200,23 @@ public class RAGAdapter implements SignalAdapter { logger.info(llmPrompt); } - float[] indexResult = llmService.postEmbedding(llmPrompt, llmAPIEndpoint, llmAPIDebug); + // 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(), chunk, indexResult); + if (llmAPIDebug) { + logger.info("├── ⇨ " + indexResult.size() + " floats stored in RAG db."); - // write to cassandra - clusterService.insertVector(promptTemplate, indexResult); + } + } if (llmAPIDebug) { - logger.info("├── ✅ " + indexResult.length + " floats stored in RAG db."); logger.info( "├── ✅ Total processing time: " + (System.currentTimeMillis() - processingTime) + "ms"); } 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 new file mode 100644 index 0000000..4bd341a --- /dev/null +++ b/dynamixs-ai-rag/src/main/java/ai/dynamixs/rag/RAGUtil.java @@ -0,0 +1,101 @@ +/* + * 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/pom.xml b/pom.xml index 4f96194..31c5e02 100644 --- a/pom.xml +++ b/pom.xml @@ -70,7 +70,7 @@ 1.0.0 9.9.1-4 2.0.24 - 4.17.0 + 4.19.0 5.9.2 5.8.0 @@ -273,7 +273,7 @@ ${org.imixs.ai.version} - + diff --git a/workflow/bedarfsmeldung-de-1.0.0.bpmn b/workflow/bedarfsmeldung-de-1.0.0.bpmn index 42936fe..dcddb90 100644 --- a/workflow/bedarfsmeldung-de-1.0.0.bpmn +++ b/workflow/bedarfsmeldung-de-1.0.0.bpmn @@ -2115,7 +2115,7 @@ result.isValid=true; - llm.service.endpoint + https://llama.cpp2.imixs.com/ true ]]>