In diesem Teil der „AI in Java“ Serie schauen wir uns das Thema Retrieval Augmented Generation (RAG) genauer an. Damit können wir LLMs unter anderem mit spezifisch für den Geschäftsprozess relevanten Informationen versorgen ohne das Model erst mit neuen Daten zu trainieren.
In den vorherigen Blogposts dieser Serie haben wir LLMs bereits ermöglicht Schnittstellen (Tools) zu nutzen um weitere Informationen zu erhalten oder sogar Geschäftsprozesse zu steuern (mit Spring AI und mit Quarkus+LangChain4j). Bei RAG wird bei der Anfrage bereits bestimmt welche Informationen (z.B. aus einer vorbereiteten Sammlung an Dokumenten) für die Anfrage relevant scheinen und dem LLM direkt mit der Anfrage als Kontext zur Verfügung gestellt. Das LLM kann nun auf Basis dieser Informationen eine Antwort auf die Anfrage generieren. Wir probieren das ganze anhand eines einfachen Beispielprojekts mit Quarkus und LangChain4j aus. Als LLM verwenden wir wieder mistral mit Ollama.
Ähnlich wie in den bisherigen Beispielen stellen wir dem LLM auch hier wieder Verkehrs- und Wetterinformationen zur Verfügung. Konkret geschieht das in diesem Fall durch einfache Textdateien mit einem Wetterbericht oder Informationen zu Baustellen. Um einfach mal anzufangen und die Möglichkeiten auszuprobieren eignet sich das Easy RAG Feature von LangChain4j das, insbesondere mit der Quarkus Integration, sehr einfach einzubinden ist. Wir fügen einfach die quarkus-langchain4j-easy-rag Maven Dependency hinzu, legen die entsprechenden Dokumente in unseren Projekt ab und spezifizieren den Pfad in der application.properties
Datei.
quarkus.langchain4j.easy-rag.path=documents
quarkus.langchain4j.easy-rag.reuse-embeddings.enabled=true
In unserem Fall legen wir die Textdateien im Ordner documents
ab. Wir aktivieren zusätzlich eine weitere Property, damit die Anwendung bei einem Neustart, zum Beispiel durch eine Änderung im Dev-Modus, die Embeddings für unsere Informationen nicht neu berechnen muss. Moment — Embeddings? Embeddings sind Vektoren, die die Semantik (also die Bedeutung) von Textabschnitten darstellen sollen. Um die Relevanz eines Textabschnitts für eine bestimmte Fragestellung zu ermitteln wird dann die Ähnlichkeit der Embeddings (also zeigen die Vektoren in eine ähnliche Richtung) verglichen. Easy RAG stellt die Embeddings der Textsegmente als Default in einem in-memory store bereit, das reicht für unsere einfachen Beispiele zum Ausprobieren aus. Für weiterführende Projekte gibt es Schnittstellen für persistente Vektordatenbanken. Bestimmt werden die Embeddings durch das Model nomic-embed-text, das standardmäßig für Projekte mit Ollama Integration ausgewählt wird.
Aber was passiert nun eigentlich, wenn wir Anfragen an unser LLM stellen ohne weitere Einstellungen vorzunehmen?
Nutzerfrage:
What is the current weather in Bielefeld?
Angereicherte (augmented) Anfrage:
What is the current weather in Bielefeld?
Answer using the following information:
Weather forecast for Bielefeld:
Today: 19°C/12°C, partly cloudy
Tomorrow: 22°C/13°C, mostly sunny
Day after tomorrow: 22°C/12°C, partly cloudy
Weather forecast for Berlin:
Today: 25°C/14°C, sunny
Tomorrow: 26°C/15°C, partly cloudy
Day after tomorrow: 24°C/14°C, mostly sunny
Weather forecast for Cologne:
Today: 22°C/13°C, sunny
Tomorrow: 24°C/14°C, sunny
Day after tomorrow: 24°C/12°C, sunny
Streetworks on the A2:
From March 15, 2024 to August 24, 2024, bridge construction work between Bielefeld and Hanover.
From January 23, 2024 to February 24, 2024, two-lane traffic between Hanover and Oberhausen.
From June 1, 2024 to June 10, 2024, full closure near Dortmund.
Streetworks on the A1:
From July 15, 2024 to December 24, 2024, bridge construction work between Cologne and Dortmund.
From July 23, 2024 to July 24, 2024, single-lane traffic between Osnabrück and Bremen.
From January 1, 2024 to January 10, 2024, full closure near Hamburg.
Antwort:
The current weather in Bielefeld is partly cloudy with temperatures ranging from 19°C during the day to 12°C at night.
If you're planning a trip to Berlin or Cologne, tomorrow and the day after will be mostly sunny with higher temperatures in both cities compared to Bielefeld.
However, please note that there are ongoing and upcoming streetworks on several highways in your travel route, especially the A2 between Bielefeld and Hanover from March 15, 2024 to August 24, 2024.
The A1 also has some construction work between Cologne and Dortmund from July 15, 2024 to December 24, 2024. It's advisable to check real-time traffic updates before your travel.
Okay unsere Frage wurde beantwortet. Aber wie man sieht beinhaltet die angereicherte Anfrage mehr Informationen, als für die Beantwortung notwendig gewesen wären — und das LLM nutzt sie bereitwillig für eine ausschweifende Antwort. Das bedarf vielleicht doch noch etwas fine-tuning. Für einen kleinen Überblick können wir z.B. eine kleine HTTP-Schnittstelle hinzufügen, in der die Anfrage mit den gespeicherten Embeddings verglichen und die Embeddings samt einem Score für die Ähnlichkeit zurückgegeben werden. Der Score nimmt einen Wert zwischen 0 und 1 abhängig von der Ähnlichkeit ein.
@POST
@Path("/checkEmbeddingScore")
@Consumes(MediaType.TEXT_PLAIN)
@Produces(MediaType.TEXT_PLAIN)
public Response checkEmbeddingScore(String question) {
EmbeddingSearchResult<TextSegment> embeddingSearchResults = embeddingStore.search(new EmbeddingSearchRequest(embeddingModel.embed(question).content(), 100, 0.0, null));
return Response.ok(embeddingSearchResults.matches().stream().map(match -> match.score() + ": " + match.embedded().text()).collect(Collectors.joining("\n---\n"))).build();
}
Eine relativ einfache Konfiguration ist die Menge an Ergebnissen, die bei einer Anfrage an den Embedding-Store zurückgegeben werden, weiter zu begrenzen. Hierfür kann der Wert der Property quarkus.langchain4j.easy-rag.max-results
von der Voreinstellung (5 Ergebnisse) abgeändert werden. Eine Begrenzung auf Basis des Score ist über die Property quarkus.langchain4j.easy-rag.min-score
möglich. Statt des bereitgestellten RetrievalAugmentor
s können wir auch einen eigenen Augmentor verwenden und hier die Ergebnisse anhand des Scores oder anderen Filteranweisungen beschränken. Auch Komponenten wie der ContentInjector
, der bestimmt wie der Prompt an das LLM formatiert wird, können hier angepasst werden.
@ApplicationScoped
public class DemoAugmentor implements Supplier<RetrievalAugmentor> {
private final static String PROMPT_TEMPLATE = """
{{userMessage}}
To answer this question you can use the following information if applicable:
{{contents}}\
""";
private final RetrievalAugmentor augmentor;
public DemoAugmentor(EmbeddingModel embeddingModel, EmbeddingStore<TextSegment> embeddingStore) {
EmbeddingStoreContentRetriever contentRetriever = EmbeddingStoreContentRetriever.builder()
.embeddingModel(embeddingModel)
.embeddingStore(embeddingStore)
.minScore(0.8)
.maxResults(3)
.build();
ContentInjector contentInjector = DefaultContentInjector.builder()
.promptTemplate(new PromptTemplate(PROMPT_TEMPLATE))
.build();
this.augmentor = DefaultRetrievalAugmentor.builder()
.contentRetriever(contentRetriever)
.contentInjector(contentInjector)
.build();
}
@Override
public RetrievalAugmentor get() {
return augmentor;
}
}
@RegisterAiService(retrievalAugmentor = DemoAugmentor.class)
public interface RagAiService {
String chat(@UserMessage String question);
}
Durch die Beschränkung des mitgelieferten Kontexts erreichen wird schonmal etwas bessere Ergebnisse. Durch Anpassungen des Prompts können auch hier noch weitere Optimierungen abhängig vom konkreten Anwendungsfall vorgenommen werden. Wobei auch hier gilt: LLMs sind sehr große stochastisch operierende Maschinen und jede Verarbeitung ist im Regelfall anders.
Nutzerfrage:
What is the current weather in Bielefeld?
Angereicherte (augmented) Anfrage:
What is the current weather in Bielefeld?
To answer this question you can use the following information if applicable:
Weather forecast for Bielefeld:
Today: 19°C/12°C, partly cloudy
Tomorrow: 22°C/13°C, mostly sunny
Day after tomorrow: 22°C/12°C, partly cloudy
Weather forecast for Berlin:
Today: 25°C/14°C, sunny
Tomorrow: 26°C/15°C, partly cloudy
Day after tomorrow: 24°C/14°C, mostly sunny
Weather forecast for Cologne:
Today: 22°C/13°C, sunny
Tomorrow: 24°C/14°C, sunny
Day after tomorrow: 24°C/12°C, sunny
Antwort:
The current weather in Bielefeld today is partly cloudy with temperatures of 19°C (66.2°F) during the day and 12°C (53.6°F) at night.
Auch beim Prozess des Einbettens und Speichern der Dokumenteninhalte können wir noch Optimierungen vornehmen. Über die Properties quarkus.langchain4j.easy-rag.max-segment-size
und quarkus.langchain4j.easy-rag.max-overlap-size
kann die maximale Größe von Segmenten und deren Überlappung beim Einlesen für den spezifischen Inhalt angepasst werden. Natürlich ist sobald es über kleine Beispiele hinausgeht auch eine persistente Vektordatenbank sehr sinnvoll. Für Quarkus sind bereits einige Integrationen wie quarkus-langchain4j-redis verfügbar, die auch Dev-Services für die Entwicklung mitbringen.
Das Beispielprojekt gibt es wie immer auch auf GitHub.