rag servcie impl
This commit is contained in:
parent
36f06a6d6a
commit
b4c4cd49f6
7 changed files with 179 additions and 71 deletions
|
|
@ -272,7 +272,6 @@
|
||||||
<version>${apache.pdfbox.version}</version>
|
<version>${apache.pdfbox.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
|
||||||
<!-- Imixs-AI -->
|
<!-- Imixs-AI -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.imixs.workflow</groupId>
|
<groupId>org.imixs.workflow</groupId>
|
||||||
|
|
@ -280,24 +279,6 @@
|
||||||
<version>${org.imixs.ai.version}</version>
|
<version>${org.imixs.ai.version}</version>
|
||||||
</dependency>
|
</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 -->
|
<!-- Imixs-QR-Code -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.imixs.workflow</groupId>
|
<groupId>org.imixs.workflow</groupId>
|
||||||
|
|
|
||||||
|
|
@ -166,17 +166,17 @@
|
||||||
|
|
||||||
<!-- DaraStax Java Driver -->
|
<!-- DaraStax Java Driver -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.datastax.oss</groupId>
|
<groupId>org.apache.cassandra</groupId>
|
||||||
<artifactId>java-driver-core</artifactId>
|
<artifactId>java-driver-core</artifactId>
|
||||||
<version>${apache.cassandra.driver.version}</version>
|
<version>${apache.cassandra.driver.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.datastax.oss</groupId>
|
<groupId>org.apache.cassandra</groupId>
|
||||||
<artifactId>java-driver-query-builder</artifactId>
|
<artifactId>java-driver-query-builder</artifactId>
|
||||||
<version>${apache.cassandra.driver.version}</version>
|
<version>${apache.cassandra.driver.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.datastax.oss</groupId>
|
<groupId>org.apache.cassandra</groupId>
|
||||||
<artifactId>java-driver-mapper-runtime</artifactId>
|
<artifactId>java-driver-mapper-runtime</artifactId>
|
||||||
<version>${apache.cassandra.driver.version}</version>
|
<version>${apache.cassandra.driver.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
package ai.dynamixs.rag;
|
package ai.dynamixs.rag;
|
||||||
|
|
||||||
import java.net.InetSocketAddress;
|
import java.net.InetSocketAddress;
|
||||||
import java.time.Instant;
|
import java.util.List;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
import java.util.logging.Level;
|
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.InvalidKeyspaceException;
|
||||||
import com.datastax.oss.driver.api.core.cql.BoundStatement;
|
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.PreparedStatement;
|
||||||
|
import com.datastax.oss.driver.api.core.data.CqlVector;
|
||||||
import com.datastax.oss.driver.api.core.servererrors.InvalidQueryException;
|
import com.datastax.oss.driver.api.core.servererrors.InvalidQueryException;
|
||||||
|
|
||||||
import jakarta.annotation.PostConstruct;
|
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_CONTACTPOINTS = "EMBEDDINGS_CLUSTER_CONTACTPOINTS";
|
||||||
public static final String ENV_EMBEDDINGS_CLUSTER_KEYSPACE = "EMBEDDINGS_CLUSTER_KEYSPACE";
|
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
|
// optional environment settings
|
||||||
public static final String ENV_EMBEDDINGS_CLUSTER_AUTH_USER = "EMBEDDINGS_CLUSTER_AUTH_USER";
|
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_AUTH_PASSWORD = "EMBEDDINGS_CLUSTER_AUTH_PASSWORD";
|
||||||
|
|
@ -118,6 +122,7 @@ public class ClusterService {
|
||||||
Optional<String> keystorePwd;
|
Optional<String> keystorePwd;
|
||||||
|
|
||||||
private CqlSession session;
|
private CqlSession session;
|
||||||
|
private PreparedStatement insertVectorStmt;
|
||||||
|
|
||||||
@Resource
|
@Resource
|
||||||
private TimerService timerService;
|
private TimerService timerService;
|
||||||
|
|
@ -178,6 +183,7 @@ public class ClusterService {
|
||||||
logger.info("├── initializing cluster and keyspace...");
|
logger.info("├── initializing cluster and keyspace...");
|
||||||
session = initSessionWithKeyspace();
|
session = initSessionWithKeyspace();
|
||||||
logger.info("│ ├── ✅ schema status = OK");
|
logger.info("│ ├── ✅ schema status = OK");
|
||||||
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
throw new ClusterException(ClusterException.CLUSTER_ERROR,
|
throw new ClusterException(ClusterException.CLUSTER_ERROR,
|
||||||
"Failed to init cassandra session: " + e.getMessage(), e);
|
"Failed to init cassandra session: " + e.getMessage(), e);
|
||||||
|
|
@ -303,11 +309,61 @@ public class ClusterService {
|
||||||
/**
|
/**
|
||||||
* This method creates the keySpace schema.
|
* 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
|
* @param cluster
|
||||||
* @throws ArchiveException
|
* @throws ArchiveException
|
||||||
*/
|
*/
|
||||||
private void createSchema() throws ClusterException {
|
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...");
|
logger.info("│ ├── create schema...");
|
||||||
// now create table schemas
|
// now create table schemas
|
||||||
String query = "CREATE TABLE IF NOT EXISTS document_vectors (\n" + //
|
String query = "CREATE TABLE IF NOT EXISTS document_vectors (\n" + //
|
||||||
|
|
@ -328,44 +384,4 @@ public class ClusterService {
|
||||||
session.execute(query);
|
session.execute(query);
|
||||||
logger.info("│ ├── ✅ database schema created.");
|
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -200,13 +200,23 @@ public class RAGAdapter implements SignalAdapter {
|
||||||
logger.info(llmPrompt);
|
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
|
// 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) {
|
if (llmAPIDebug) {
|
||||||
logger.info("├── ✅ " + indexResult.length + " floats stored in RAG db.");
|
|
||||||
logger.info(
|
logger.info(
|
||||||
"├── ✅ Total processing time: " + (System.currentTimeMillis() - processingTime) + "ms");
|
"├── ✅ Total processing time: " + (System.currentTimeMillis() - processingTime) + "ms");
|
||||||
}
|
}
|
||||||
|
|
|
||||||
101
dynamixs-ai-rag/src/main/java/ai/dynamixs/rag/RAGUtil.java
Normal file
101
dynamixs-ai-rag/src/main/java/ai/dynamixs/rag/RAGUtil.java
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
6
pom.xml
6
pom.xml
|
|
@ -70,7 +70,7 @@
|
||||||
<org.imixs.signature.version>1.0.0</org.imixs.signature.version>
|
<org.imixs.signature.version>1.0.0</org.imixs.signature.version>
|
||||||
<net.sf.saxon.version>9.9.1-4</net.sf.saxon.version>
|
<net.sf.saxon.version>9.9.1-4</net.sf.saxon.version>
|
||||||
<apache.pdfbox.version>2.0.24</apache.pdfbox.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 -->
|
<!-- test dependencies -->
|
||||||
<junit.jupiter.version>5.9.2</junit.jupiter.version>
|
<junit.jupiter.version>5.9.2</junit.jupiter.version>
|
||||||
<mockito.version>5.8.0</mockito.version>
|
<mockito.version>5.8.0</mockito.version>
|
||||||
|
|
@ -273,7 +273,7 @@
|
||||||
<version>${org.imixs.ai.version}</version>
|
<version>${org.imixs.ai.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<!-- DaraStax Java Driver -->
|
<!-- DaraStax Java Driver
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.datastax.oss</groupId>
|
<groupId>com.datastax.oss</groupId>
|
||||||
<artifactId>java-driver-core</artifactId>
|
<artifactId>java-driver-core</artifactId>
|
||||||
|
|
@ -289,7 +289,7 @@
|
||||||
<artifactId>java-driver-mapper-runtime</artifactId>
|
<artifactId>java-driver-mapper-runtime</artifactId>
|
||||||
<version>${apache.cassandra.driver.version}</version>
|
<version>${apache.cassandra.driver.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
-->
|
||||||
|
|
||||||
<!-- Imixs-QR-Code -->
|
<!-- Imixs-QR-Code -->
|
||||||
<dependency>
|
<dependency>
|
||||||
|
|
|
||||||
|
|
@ -2115,7 +2115,7 @@ result.isValid=true;
|
||||||
</imixs:item>
|
</imixs:item>
|
||||||
<imixs:item name="txtactivityresult" type="xs:string">
|
<imixs:item name="txtactivityresult" type="xs:string">
|
||||||
<imixs:value><![CDATA[<imixs-ai name="INDEX">
|
<imixs:value><![CDATA[<imixs-ai name="INDEX">
|
||||||
<endpoint><propertyvalue>llm.service.endpoint</propertyvalue></endpoint>
|
<endpoint>https://llama.cpp2.imixs.com/</endpoint>
|
||||||
<debug>true</debug>
|
<debug>true</debug>
|
||||||
</imixs-ai>
|
</imixs-ai>
|
||||||
]]></imixs:value>
|
]]></imixs:value>
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue