RAG Chatbot mit Spring AI, AWS Bedrock und Vektor Datenbank

Wie bringt man einem KI Chatbot Unternehmens-Knowhow bei? Retrieval Augmented Generation (RAG) ist eine Lösung, die ich in diesem Artikel mit Amazon Bedrock, der Vektor Datenbank pgvector und Spring AI umsetze.

RAG Architektur-Überblick


Der Benutzer chattet mit dem Chatbot unserer Anwendung (hier "RAG Chat Service"). Diesen Teil implementieren wir selbst mit Java und Spring AI. Als LLM bzw. Chat-Service verwendet unsere Spring AI Anwendung den AWS Dienst Bedrock mit dem Sprachmodell Titan (eine Alternative zu GPT von OpenAI). Ohne eigene Dokumente in einer Vektor Datenbank, hätten wir so einen Chatbot ohne Unternehmens-spezifisches Wissen - vergleichbar mit ChatGPT. So etwas habe ich schon mit GPT gebaut, siehe dazu gpt-3 in deiner Java Webanwendung.

Unsere Anwendung sucht in der Vektor Datenbank nach zur Chat-Anfrage des Benutzers passenden Dokumenten. Mit diesen Unternehmens-spezifische Texten reichert unsere Anwendung die Interaktion mit dem Bedrock Titan Chat Service an. Dadurch bekommt der Benutzer eine Chat-Antwort passend zum Kontext des Unternehmens.

Um die Datenbank mit vektorisierten Dokumenten zu füllen, verwendet der "Document Importer" unserer Anwendung den AWS Bedrock Embedding Service des Sprachmodells Titan. Die Dokumente bzw. Texte werden aus Quellen des Unternehmens eingelesen, mit Hilfe des AWS Embedding Services vektorisiert und dann in der Vektor-DB gespeichert.

Video about Spring AI and Azure

Dokumente in pgvector importieren

Als Vektor-Datenbank verwende ich PostgreSQL mit der Erweiterung pgvector. Diese Datenbank wird von Spring AI unterstützt und lässt sich im Docker Container starten:
docker run -d --name vector_db --restart always -e POSTGRES_USER=postgres -e POSTGRES_PASSWORD=postgres -e POSTGRES_DB=vector_store -e PGPASSWORD=postgres --log-opt max-size=10m --log-opt max-file=3 -p 5433:5432 ankane/pgvector:v0.5.1
Die Maven Konfiguration für den Document Importer und den Chatbot im nächsten Abschnitt sieht so aus:
<dependencies>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-bedrock-ai-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-pgvector-store-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<scope>runtime</scope>
</dependency>
    ...
</dependencies>

<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-bom</artifactId>
<version>0.8.1</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<repositories>
<repository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/milestone</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
</repositories>
  • Da Spring AI noch keine 1.0 Version hat, finden wir es noch nicht im zentralen Maven-Repository sondern im Spring Milestones Repository.
  • Für den Chatbot und die Vektorisierung benötigen wir die "spring-ai-bedrock-ai-spring-boot-starter" Bibliothek.
  • Für die Vektor-Datenbank die andere Spring AI und PostgreSQL Dependencies.
Damit bauen wir einen einfachen Importer:
@Component
public class DocumentImport {
private final VectorStore vectorStore;
private final TokenTextSplitter textSplitter = new TokenTextSplitter();

private static final String EXAMPLE_DOCUMENT = "Long text with company specific content.";
    ...
    public void importDocuments() {
var vectorizedDocumentChunks = textSplitter.split(EXAMPLE_DOCUMENT, 800)
.stream()
.map(chunk -> new Document(chunk, Map.of("Add", "meta", "data", "here")))
.toList();
vectorStore.accept(vectorizedDocumentChunks);
}
}
  • Die VectorStore Bean stellt Spring AI automatisch zur Verfügung.
  • TokenTextSplitter ist eine Klasse aus den Spring AI Bibliotheken zum Aufsplitten langer Texte. 800 definiert die Größe der aufgeteilten Textblöcke. Den Wert 800 habe ich in einer Spring-Klasse als dortigen default abgeschaut.
  • Die aufgeteilten Textblöcke wandle ich in Spring AI Document Objekte um. Die Document Objekte könnt ihr mit Metadaten in Form einer Map anreichern.
  • Der letzte Schritt vectorStore.accept schreibt eine Liste aus allen Document Objekten des ursprünglichen, Firmen-spezifischen Beispiel-Dokuments in die Vektor-DB. Im VectorStore ist eine Spring AI Bean EmbeddingClient injiziert, welche die Vektorisierung der Document Objekte vornimmt.
Damit Spring AI automatisch alle benötigten Beans bereitstellt, konfiguriert ihr in application.porperties:

spring.ai.bedrock.aws.region
=eu-central-1
spring.ai.bedrock.aws.access-key=TODO_set_me
spring.ai.bedrock.aws.secret-key=TODO_set_me
spring.ai.bedrock.titan.chat.enabled=true

spring.ai.bedrock.titan.embedding.enabled=true
spring.ai.bedrock.titan.embedding.input-type=text
spring.ai.bedrock.titan.embedding.model=amazon.titan-embed-text-v1

spring.datasource.password=postgres
spring.datasource.username=postgres
spring.datasource.url=jdbc:postgresql://localhost:5433/vector_store
  • spring.ai.bedrock.aws definiert die Verbindung zum AWS Bedrock Service sowohl für den Embedding Service zur Dokumenten-Vektorisierung als auch für den Chat Service.
  • spring.ai.bedrock.titan aktiviert und konfiguriert die beiden AWS Bedrock Dienste. Hier sind noch weitere Feineinstellungen der KI möglich (maxtoken, temprature etc.).
  • spring.datasource beschreibt die Verbindung zur Datenbank.

RAG Chatbot mit Vektor-DB

Einen Chat mit Spring AI und AWS Bedrock implementieren wir so:
private final BedrockTitanChatClient chatClient;

public void chat(String message) {
var userMessage = new UserMessage(message);
    // TODO Integrate VectorStore
    var prompt = new Prompt(List.of(userMessage));
    var aiResponse = chatClient.call(prompt);
    log.info("AI response: {}", aiResponse.getResult().getOutput().getContent());
} 
  • Die Chat message des Benutzers wandeln wir in ein Objekt der Spring AI Klasse UserMessage um.
  • Das UserMessage Objekt überführen wir in ein Spring AI Prompt Objekt. (In das Prompt Objekt integrieren wir in einem späteren Schritt noch die Daten aus der Vektor-DB.)
  • Das Prompt Objekt schicken wir an den AWS Bedrock Chat Service. Dazu verwenden wir eine Bean, die von Spring AI automatisch erzeugt wurde: BedrockTitanChatClient. Die Antwort der KI logge ich in die Konsole - es ist eine kleine Demo-Anwendung.
Um aus diesem einfachen Chat-Service ein RAG zu machen, binden wir noch die Vektor-Datenbank mit unserem Unternehmens Know-How ein. Dazu ergänzen wir den zuvor gezeigten Code:
private final VectorStore vectorStore;

private static final SystemPromptTemplate template = new SystemPromptTemplate("""
Du assistierst bei Fragen zum UseCase X.

Verwende die Informationen aus dem Abschnitt DOKUMENTE, um genaue Antworten zu geben,
aber tu so, als ob du diese Informationen von Natur aus wüsstest.
Wenn du dir nicht sicher bist, gib einfach an, dass du es nicht weißt.

DOKUMENTE:
{documents}
""");

public void chat(String message) {
    ...
    var similarDocuments = vectorStore.similaritySearch(message)
            .stream()
            .map(Document::getContent)
            .collect(Collectors.joining(System.lineSeparator()));
var contextMessage = template.createMessage(Map.of("documents", similarDocuments));

var prompt = new Prompt(List.of(contextMessage, userMessage));
    ... }
  • Mit der VectorStore Bean suchen wir nach ähnlichen Dokumenten in der Vektor-Datenbank: vectorStore.similaritySearch
  • Die so gefundenen Document Objekte wandeln wir in einen langen String um. Das passiert in den Stream-Operationen direkt nach der Ähnlichkeits-Suche. Mehr zu Streams gibt es hier.
  • Neben der UserMessage stecken wir dieses mal ein 2. Message Objekt (contextMessage) als Kontext in die Prompt Instanz. Diese 2. Message bauen wir mit dem Spring AI SystemPromptTemplate. Das Template beschreibt das Verhalten unserer KI und auf welche Dokumente mit Firmen-spezifischen Wissen die KI zugreift. Auf diese Weise ist im AWS Bedrock Chat Service kein Firmen-spezifisches Wissen dauerhaft persistiert. Den Unternehmenskontext übertragen wir mit jeder Chat Anfrage passend zur Nachricht des Benutzers.

Fazit

Durch AWS Bedrock habe ich einfachen Zugriff auf KI Sprachmodelle. Eine einfache Vektor Datenbank lässt sich schnell mit Docker starten. Mit Spring AI benutzen wir diese Dienste leicht und verbinden sie so zu einem RAG.

Wenn euch die Qualität des Amazon Titan Sprachmodells nicht überzeugt, testet andere Sprachmodelle wie Mistral AI oder Llama in AWS Bedrock. Dank Spring AI braucht ihr nur die Maven-Dependencies und die application.properties anzupassen.

Den von mir gezeigten Code, findet ihr bei GitHub: https://github.com/elmar-brauch/aws-bedrock-rag

OpenAI Chatbot

Kommentare

Sebastian hat gesagt…
Ich bin begeistert von den Inhalten auf deinem Blog! Deine Erklärungen sind klar und praxisnah, was das Verständnis von agilen Methoden enorm erleichtert. Besonders gefallen mir die anschaulichen Beispiele, die du verwendest. Weiter so – ich freue mich auf weitere spannende Beiträge!
joomla extensions hat gesagt…
Thanks for the insightful post on Spring and AI integration, especially the focus on Retrieval-Augmented Generation (RAG). The explanation of how Spring can support AI models, including integrating GPT and vector databases like Pinecone for enhanced data retrieval, was eye-opening. The practical examples really helped me understand how to structure such systems within a Spring framework. I’m eager to try implementing these ideas in my own project! Looking forward to more AI-related content from your blog!

Beliebte Posts aus diesem Blog

OpenID Connect mit Spring Boot 3

CronJobs mit Spring

Kernkonzepte von Spring: Beans und Dependency Injection