Spätestens mit ChatGPT haben es LLMs geschafft, in aller Munde zu sein. Und auch wenn LLMs nur ein Aspekt von AI sind, ist es aktuell der Teil, der wohl am meisten im Fokus steht. Dabei stechen vorrangig die LLMs der großen Firmen, wie ChatGPT von OpenAI, Googles Gemini und Microsoft Copilot heraus. Diese Firmen haben, aus der Historie heraus, die Unmenge an Daten, um diese Modelle anzulernen und auch das notwendige Kleingeld, um die dafür benötigte Hardware und Betriebskosten zu stemmen.
Gerade in Deutschland, bzw. der EU, ist diese Nutzung von Daten aus juristischer Perspektive, insbesondere hinsichtlich Datenschutz und Urheberrecht noch unklar und schwierig. Vor allem da die Anbindung eines LLM ohne spezifische interne Daten in den wenigsten Anwendungsfällen einen wirklichen Mehrwert liefert. Dementsprechend bleiben nur zwei Möglichkeiten. Entweder, wir lernen, mit unseren Daten, ein eigenes LLM an oder wir nutzen ein bestehendes LLM und geben unsere Daten als Kontext mit. Das eigene Anlernen benötigt, neben einer Menge Wissen und Arbeitskraft, dann auch die passende Hardware. Der zweite Weg ist deswegen aktuell der Übliche. Diesen werden wir daher in diesem Post einmal Schritt für Schritt abgehen.
Lokales LLM mit Ollama
Vereinfacht gesagt, handelt es sich bei einem LLM um ein auf Sprache spezialisiertes Modell. Das heißt, ein solches Modell kann entweder Anfragen in natürlicher Sprache verstehen oder Antworten in natürlicher Sprache produzieren oder beides. Dazu gibt es auch noch Modelle, die für einen spezifischen Anwendungsfall, wie das Schreiben oder Diskutieren über Code, optimiert wurden.
Mittlerweile gibt es auch viele Modelle zur freien und lokalen Nutzung. Diese laufen auch auf nicht spezialisierter Hardware. Natürlich muss man hier, im Vergleich zu den großen kommerziellen Cloud-Modellen, Abstriche machen. In der Regel ist die Performanz, vor allem auf normaler Consumer-Hardware, schlechter und auch die Modelle selbst sind qualitativ nicht komplett vergleichbar. Um sich mit dem Thema vertraut zu machen und auszuprobieren, wie man diese in eine Anwendung integrieren kann, reichen diese aber mehr als aus. Der Anlaufpunkt für diese Modelle, und noch mehr, ist Hugging Face - eine Open-Source-Community, die sich auf die Entwicklung von Künstlicher Intelligenz (KI) und Natural Language Processing (NLP) spezialisiert hat.
Um nun so ein LLM lokal zu nutzen, müssen wir uns als Erstes für eine Ablaufumgebung entscheiden. Hier gibt es zahlreiche Möglichkeiten, wie LM Studio, Ollama oder Text Generation Web UI. Für diesen Post nutzen wir Ollama. Ollama ist eine solide Lösung, die sich vor allem auf die Grundlagen, das Verwalten von LLMs, spezialisiert.
Nachdem wir Ollama installiert und gestartet haben können wir über die Kommandozeile Modelle herunterladen und diese starten. In Listing 1 ist zu sehen wie wir einen Prompt mit dem Modell Mistral starten und eine Frage an das LLM formulieren.
Bei der ersten Verwendung eines Modells muss dieses vorher jedoch noch heruntergeladen werden, was durchaus dauern kann. So ist das oben verwendete Mistral etwa 4,1 GB groß.
Wir müssen jedoch nicht den interaktiven Prompt nutzen, denn eigentlich ist Ollama ein Serverprozess, der, standardmäßig, auf Port 11434 gestartet wird. Diesen können wir also auch über ein Tool das HTTP-Anfragen absetzen kann, wie curl, ansprechen (siehe Listing 2).
Spring AI
Um nun dasselbe Model aus einer Spring Boot-Anwendung heraus anzusprechen könnten wir natürlich den dort vorhandenen RestClient nutzen, dieselbe HTTP-Anfrage wie oben erzeugen und absetzen. Glücklicherweise gibt es aber bereits erste Schritte, um diese Abfragen und Konzepte zu kapseln, nämlich Spring AI.
Um die dort vorhandene Unterstützung für Ollama einzubinden, nutzen wir die
spring-ai-bom
und können anschließend spring-ai-ollama-spring-boot-starter
als Abhängigkeit hinzufügen (siehe Listing 3).
Für dieselbe Abfrage wie bisher können wir jetzt einen Prompt
mit einer
UserMessage
erzeugen und diesen über einen ChatClient
an das LLM senden und
die Antwort auswerten (siehe Listing 4).
Um das gewünschte Modell auszuwählen, müssen wir die zusätzlich die Option
spring.ai.ollama.chat.options.model
, hier über die application.properites
(siehe Listing 5) setzen.
Zusätzlich verhindern wir noch das versucht wird ein HTTP-Server zu starten, da wir diesen für das Beispiel nicht benötigen. Starten wir jetzt die Anwendung erscheint nach einiger Zeit die Antwort des LLMs auf der Standardausgabe (siehe Listing 6).
Kontext und RAG
Ein LLM kann zur Beantwortung der Prompts lediglich die beim Training verwendeten Daten zur Beantwortung nutzen. Deswegen wird etwa die Frage „What date is today?“ mit „I’m an artificial intelligence and don’t have the ability to experience time or know the current date without being connected to a database or external source. However, I can help you check the date if you tell me which specific date you have in mind, or I can tell you the current date if you provide me with your location so I can access an online calendar or database.“ beantwortet.
Wir können dem Prompt aber neben der eigentlichen Frage auch zusätzlichen
Kontext mitgeben. Diesen kann das LLM dann mit nutzen. Um dies in der Anwendung
zu machen, können wir dem Prompt
eine Liste von Nachrichten mitgeben und neben
der Frage als UserMessage
auch eine SystemMessage
erzeugen (siehe Listing
7).
Mit diesem Kontext bekommen wir nun „Today, February 27, 2024, falls in the year 2024. It is the 58th day of the year in the Gregorian calendar, with only 306 days remaining until the end of the year on December 31. This date represents a Wednesday according to the standard 7-day weekly cycle.“ als Antwort.
Um ein LLM also mit eigenen Daten zu nutzen, müssen wir die für die Beantwortung relevanten Informationen mit in unseren Prompt packen. Dieses hat jedoch in der Regel ein Limit an Tokens, vereinfacht gesagt Wörtern, die es akzeptiert. Das liegt daran, dass bei einer größeren Menge an Tokens die Beantwortung mehr Energie benötigt und demnach länger dauert und teurer wird. Deswegen werden auf LLMs basierende Anwendungen aktuell in der Regel in Kombination mit einer Vektor-basierten Datenbank und dem Ansatz der Retrieval Augmented Generation (RAG) entwickelt.
Vereinfacht gesagt werden hierbei sämtliche Informationen, die wir potenziell als Kontext benötigen mithilfe eines Embedding Modells in Vektoren umgewandelt. Diese, mehrdimensionalen Vektoren werden, inklusive den Daten, anschließend in eine auf Vektoren spezialisierte Datenbank gespeichert. Vor jeder Anfrage an das LLM werden nun aus dieser Datenbank nur noch die Informationen geladen, die für die Anfrage relevant sind. Um zu erkennen, was relevant ist, wird auch die Frage, oder Teile von dieser, in einen Vektor umgewandelt und anschließend sind nur noch die Daten relevant, die in der Nähe dieses Vektors liegen.
Dementsprechend bringt Spring AI auch Unterstützung für das Berechnen dieser Embeddings und die Verbindung zu den gängigen Vektordatenbanken mit. Um etwa Fragen zu den deutschen Standorten von INNOQ zu beantworten können wir diese in einer JSON-Datei ablegen (siehe Listing 8).
Diese laden wir jetzt vor unserer Anfrage in einen VectorStore
. Hierfür nutzen
wir in diesem Falle eine einfache arbeitsspeicherbasierte Implementierung (siehe
Listing 9).
Neben dem verwendeten JsonReader
werden auch noch weitere
Implementierungen zur Extraktion von Daten aus anderen Formaten
wie PDF mitgeliefert. Und natürlich können wir hier auch eigene schreiben, indem
wir das DocumentReader
-Interface implementieren.
Zuletzt können wir den befüllten VectorStore
jetzt auch bei der Abfrage des LLM
nutzen und die Dokumente, die zur Anfrage passen, als Kontext mit übergeben
(siehe Listing 10).
Als Ergebnis antwortet unser LLM nun mit „Based on the information provided in
the DOCUMENTS paragraph, the München INNOQ office is located at address
Kreuzstr. 16 in city München and zip code 80331.“. Außerdem ist durch das
vorherige System.out.println
auf der Standardausgabe auch zu sehen, dass nicht
alle, sondern nur die Informationen zu vier Standorten mit in den Kontext
gegeben wurden.
Fazit
In diesem Post haben wir uns gemeinsam angeschaut, wie wir unter Verwendung von Ollama Large Language Models lokal nutzen können und wie sich diese mithilfe von Spring AI in eine Spring Boot basierte Anwendung integrieren lassen.
Spring AI ist ein noch sehr frisches Projekt und hat erst vor wenigen Tagen mit Version 0.8.0 ein erstes Milestone Release veröffentlicht. Da die Entwicklung hier auch auf die sehr dynamische Situation der LLMs reagieren muss, können sich die hier gezeigten Listings natürlich in Zukunft noch stark verändern. Ich gehe jedoch nicht davon aus, dass das generelle Konzept noch einmal komplett umgeworfen wird.
Neben der Anbindung von Ollama bietet uns Spring AI auch andere Möglichkeiten wie Chat GPT oder das Azure Open AI an. Und neben der Unterstützung von Chat basierten LLMs gibt es auch eine Abstraktion für die Generierung von Bildern.
Dieser Post hat sich dabei darauf beschränkt, die grundlegenden Konzepte kurz und vereinfacht zu erklären und die technische Integration in Spring Boot anhand eines kleinen Beispiels zu zeigen. Um in einer realen Anwendung gute Ergebnisse zu erzielen, müssten wir hauptsächlich in die Embeddings und den Prompt noch deutlich mehr Energie investieren. Diese beiden Stellen sind beim Einsatz von Retrieval Augmented Generation, neben der Wahl des richtigen Modells als Basis, existenziell für die Qualität und somit für den gesamten Anwendungsfall.
Ein besonderer Dank geht an die Kolleg:innen, die fleißig Headerbilder generiert oder den Artikel Korrektur gelesen haben.
Technology Briefing#1: Large Language Models und Commodity AI