rag servcie impl

This commit is contained in:
Ralph Soika 2025-06-02 17:22:06 +02:00
parent 36f06a6d6a
commit b4c4cd49f6
7 changed files with 179 additions and 71 deletions

View file

@ -272,7 +272,6 @@
<version>${apache.pdfbox.version}</version>
</dependency>
<!-- Imixs-AI -->
<dependency>
<groupId>org.imixs.workflow</groupId>
@ -280,24 +279,6 @@
<version>${org.imixs.ai.version}</version>
</dependency>
<!-- DaraStax Java Driver -->
<dependency>
<groupId>com.datastax.oss</groupId>
<artifactId>java-driver-core</artifactId>
<version>${apache.cassandra.driver.version}</version>
</dependency>
<dependency>
<groupId>com.datastax.oss</groupId>
<artifactId>java-driver-query-builder</artifactId>
<version>${apache.cassandra.driver.version}</version>
</dependency>
<dependency>
<groupId>com.datastax.oss</groupId>
<artifactId>java-driver-mapper-runtime</artifactId>
<version>${apache.cassandra.driver.version}</version>
</dependency>
<!-- Imixs-QR-Code -->
<dependency>
<groupId>org.imixs.workflow</groupId>

View file

@ -166,17 +166,17 @@
<!-- DaraStax Java Driver -->
<dependency>
<groupId>com.datastax.oss</groupId>
<groupId>org.apache.cassandra</groupId>
<artifactId>java-driver-core</artifactId>
<version>${apache.cassandra.driver.version}</version>
</dependency>
<dependency>
<groupId>com.datastax.oss</groupId>
<groupId>org.apache.cassandra</groupId>
<artifactId>java-driver-query-builder</artifactId>
<version>${apache.cassandra.driver.version}</version>
</dependency>
<dependency>
<groupId>com.datastax.oss</groupId>
<groupId>org.apache.cassandra</groupId>
<artifactId>java-driver-mapper-runtime</artifactId>
<version>${apache.cassandra.driver.version}</version>
</dependency>

View file

@ -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<String> 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 <FLOAT, 768>, 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 <FLOAT, " + DIMENSIONS + ">,\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<Float> 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<Float> 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);
}
}
}

View file

@ -200,13 +200,23 @@ public class RAGAdapter implements SignalAdapter {
logger.info(llmPrompt);
}
float[] indexResult = llmService.postEmbedding(llmPrompt, llmAPIEndpoint, llmAPIDebug);
// chunk text....
List<String> chunk_list = RAGUtil.chunkMarkupDocument(llmPrompt);
for (String chunk : chunk_list) {
if (llmAPIDebug) {
logger.info("├── 🔸 chunk: ");
logger.info(chunk);
}
List<Float> indexResult = llmService.postEmbedding(chunk, llmAPIEndpoint, llmAPIDebug);
// write to cassandra
clusterService.insertVector(promptTemplate, indexResult);
clusterService.insertVector(workitem.getUniqueID(), chunk, indexResult);
if (llmAPIDebug) {
logger.info("├── ⇨ " + indexResult.size() + " floats stored in RAG db.");
}
}
if (llmAPIDebug) {
logger.info("├── ✅ " + indexResult.length + " floats stored in RAG db.");
logger.info(
"├── ✅ Total processing time: " + (System.currentTimeMillis() - processingTime) + "ms");
}

View file

@ -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<String> chunkMarkupDocument(String markupText) {
List<String> 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<String> chunks, StringBuilder currentChunk) {
if (currentChunk.length() > 0) {
chunks.add(currentChunk.toString().trim());
currentChunk.setLength(0);
}
}
}

View file

@ -70,7 +70,7 @@
<org.imixs.signature.version>1.0.0</org.imixs.signature.version>
<net.sf.saxon.version>9.9.1-4</net.sf.saxon.version>
<apache.pdfbox.version>2.0.24</apache.pdfbox.version>
<apache.cassandra.driver.version>4.17.0</apache.cassandra.driver.version>
<apache.cassandra.driver.version>4.19.0</apache.cassandra.driver.version>
<!-- test dependencies -->
<junit.jupiter.version>5.9.2</junit.jupiter.version>
<mockito.version>5.8.0</mockito.version>
@ -273,7 +273,7 @@
<version>${org.imixs.ai.version}</version>
</dependency>
<!-- DaraStax Java Driver -->
<!-- DaraStax Java Driver
<dependency>
<groupId>com.datastax.oss</groupId>
<artifactId>java-driver-core</artifactId>
@ -289,7 +289,7 @@
<artifactId>java-driver-mapper-runtime</artifactId>
<version>${apache.cassandra.driver.version}</version>
</dependency>
-->
<!-- Imixs-QR-Code -->
<dependency>

View file

@ -2115,7 +2115,7 @@ result.isValid=true;
</imixs:item>
<imixs:item name="txtactivityresult" type="xs:string">
<imixs:value><![CDATA[<imixs-ai name="INDEX">
<endpoint><propertyvalue>llm.service.endpoint</propertyvalue></endpoint>
<endpoint>https://llama.cpp2.imixs.com/</endpoint>
<debug>true</debug>
</imixs-ai>
]]></imixs:value>