1. Einführung
In diesem Codelab geht es um das Large Language Model (LLM) Gemini, das in Vertex AI in Google Cloud gehostet wird. Vertex AI ist eine Plattform, die alle Produkte, Dienste und Modelle für maschinelles Lernen in Google Cloud umfasst.
Sie verwenden Java, um mit der Gemini API über das LangChain4j-Framework zu interagieren. Sie sehen sich konkrete Beispiele an, wie Sie das LLM für die Beantwortung von Fragen, die Generierung von Ideen, die Extraktion von Entitäten und strukturierten Inhalten, Retrieval-Augmented Generation und Funktionsaufrufe nutzen können.
Was ist generative KI?
Generative KI bezieht sich auf den Einsatz von künstlicher Intelligenz, um neue Inhalte wie Text, Bilder, Musik, Audio und Videos zu erstellen.
Die generative KI basiert auf Large Language Models (LLMs), die Multitaskingfähig sind und vordefinierte Aufgaben ausführen, darunter Zusammenfassungen, Fragen-/Antworten-Sessions, Klassifizierung und mehr. Mit nur minimalem Training können Fundamentmodelle an ausgewählte Anwendungsfälle mit sehr wenigen Beispieldaten angepasst werden.
Wie funktioniert generative KI?
Generative KI nutzt ein Modell für maschinelles Lernen (ML), um die Muster und Beziehungen in einem Dataset aus von Menschen erstellten Inhalten zu ermitteln. Anschließend werden anhand der erlernten Muster neue Inhalte generiert.
Die gängigste Methode zum Trainieren eines generativen KI-Modells ist die Verwendung von überwachtem Lernen. Das Modell erhält eine Reihe von Inhalten, die von Menschen erstellt wurden, sowie zugehörige Labels. Anschließend lernt es, Inhalte zu erstellen, die den von Menschen erstellten Inhalten ähneln.
Was sind gängige Anwendungen für generative KI?
Mit generativer KI können Sie:
- Kundeninteraktionen durch erweiterte Chat- und Suchfunktionen verbessern.
- Über Konversationsschnittstellen und Zusammenfassungen große Mengen unstrukturierter Daten untersuchen
- Unterstützung bei sich wiederholenden Aufgaben wie der Beantwortung von Angebotsanfragen, der Lokalisierung von Marketinginhalten in verschiedenen Sprachen, der Prüfung von Kundenverträgen auf Compliance usw.
Welche Angebote für generative KI hat Google Cloud?
Mit Vertex AI können Sie mit Fundamentmodellen interagieren, sie anpassen und in Ihre Anwendungen einbetten – ganz ohne ML-Fachwissen. Sie können über Model Garden auf Foundation Models zugreifen, sie mithilfe einer einfachen Benutzeroberfläche in Vertex AI Studio abstimmen oder sie in einem Data-Science-Notebook verwenden.
Vertex AI Search and Conversation bietet Entwicklern die schnellste Möglichkeit, generative KI-gestützte Suchmaschinen und Chatbots zu erstellen.
Gemini for Google Cloud basiert auf Gemini und ist ein KI-gestütztes Tool, das in Google Cloud und IDEs genutzt werden kann, um in kürzerer Zeit mehr zu erledigen. Gemini Code Assist bietet Codevervollständigung, Codegenerierung und Codeerklärungen. Außerdem können Sie mit dem Tool chatten, um technische Fragen zu stellen.
Was ist Gemini?
Gemini ist eine Reihe von generativen KI-Modellen, die von Google DeepMind entwickelt wurden und auf multimodale Anwendungsfälle ausgelegt sind. Multimodal bedeutet, dass das Modell verschiedene Arten von Inhalten wie Text, Code, Bilder und Audio verarbeiten und generieren kann.

Gemini ist in verschiedenen Varianten und Größen verfügbar:
- Gemini 2.0 Flash: Unsere neuesten Funktionen der nächsten Generation und verbesserte Funktionen.
- Gemini 2.0 Flash-Lite: Ein Gemini 2.0 Flash-Modell, das für Kosteneffizienz und niedrige Latenz optimiert ist.
- Gemini 2.5 Pro: Unser bisher fortschrittlichstes Modell für Schlussfolgerungen.
- Gemini 2.5 Flash: Ein Thinking-Modell mit umfassenden Funktionen. Sie bieten ein ausgewogenes Preis-Leistungs-Verhältnis.
Wichtigste Funktionen:
- Multimodalität: Die Fähigkeit von Gemini, mehrere Informationsformate zu verstehen und zu verarbeiten, ist ein wichtiger Schritt über herkömmliche reine Textmodelle hinaus.
- Leistung: Gemini 2.5 Pro übertrifft den aktuellen Stand der Technik bei vielen Benchmarks und war das erste Modell, das menschliche Experten beim anspruchsvollen MMLU-Benchmark (Massive Multitask Language Understanding) übertroffen hat.
- Flexibilität: Die verschiedenen Gemini-Größen ermöglichen die Anpassung an unterschiedliche Anwendungsfälle, von umfangreichen Recherchen bis hin zur Bereitstellung auf Mobilgeräten.
Wie kann ich in Java mit Gemini in Vertex AI interagieren?
Es stehen zwei Optionen zur Verfügung:
- Die offizielle Vertex AI Java API für Gemini-Bibliothek.
- LangChain4j-Framework.
In diesem Codelab verwenden Sie das Framework LangChain4j.
Was ist das LangChain4j-Framework?
Das Framework LangChain4j ist eine Open-Source-Bibliothek zur Integration von LLMs in Ihre Java-Anwendungen. Dazu werden verschiedene Komponenten wie das LLM selbst, aber auch andere Tools wie Vektordatenbanken (für semantische Suchvorgänge), Dokument-Loader und ‑Splitter (zum Analysieren und Lernen aus Dokumenten) sowie Ausgabeparser orchestriert.
Das Projekt wurde vom Python-Projekt LangChain inspiriert, zielt aber auf Java-Entwickler ab.

Lerninhalte
- Java-Projekt für die Verwendung von Gemini und LangChain4j einrichten
- Ersten Prompt programmatisch an Gemini senden
- Antworten von Gemini streamen
- Unterhaltung zwischen einem Nutzer und Gemini erstellen
- Gemini in einem multimodalen Kontext verwenden, indem Sie sowohl Text als auch Bilder senden
- Nützliche strukturierte Informationen aus unstrukturierten Inhalten extrahieren
- Prompt-Vorlagen bearbeiten
- Textklassifizierung wie Sentimentanalyse durchführen
- Mit eigenen Dokumenten chatten (Retrieval-Augmented Generation)
- Chatbots mit Funktionsaufrufen erweitern
- Gemma lokal mit Ollama und TestContainers verwenden
Voraussetzungen
- Kenntnisse der Programmiersprache Java
- Ein Google Cloud-Projekt
- Ein Browser, z. B. Chrome oder Firefox
2. Einrichtung und Anforderungen
Umgebung zum selbstbestimmten Lernen einrichten
- Melden Sie sich in der Google Cloud Console an und erstellen Sie ein neues Projekt oder verwenden Sie ein vorhandenes. Wenn Sie noch kein Gmail- oder Google Workspace-Konto haben, müssen Sie eines erstellen.



- Der Projektname ist der Anzeigename für die Teilnehmer dieses Projekts. Es handelt sich um einen String, der nicht von Google APIs verwendet wird. Sie können sie jederzeit aktualisieren.
- Die Projekt-ID ist für alle Google Cloud-Projekte eindeutig und unveränderlich (kann nach dem Festlegen nicht mehr geändert werden). In der Cloud Console wird automatisch ein eindeutiger String generiert. Normalerweise ist es nicht wichtig, wie dieser String aussieht. In den meisten Codelabs müssen Sie auf Ihre Projekt-ID verweisen (in der Regel als
PROJECT_IDangegeben). Wenn Ihnen die generierte ID nicht gefällt, können Sie eine andere zufällige ID generieren. Alternativ können Sie es mit einem eigenen Namen versuchen und sehen, ob er verfügbar ist. Sie kann nach diesem Schritt nicht mehr geändert werden und bleibt für die Dauer des Projekts bestehen. - Zur Information: Es gibt einen dritten Wert, die Projektnummer, die von einigen APIs verwendet wird. Weitere Informationen zu diesen drei Werten
- Als Nächstes müssen Sie die Abrechnung in der Cloud Console aktivieren, um Cloud-Ressourcen/-APIs zu verwenden. Die Durchführung dieses Codelabs kostet wenig oder gar nichts. Wenn Sie Ressourcen herunterfahren möchten, um Kosten zu vermeiden, die über diese Anleitung hinausgehen, können Sie die erstellten Ressourcen oder das Projekt löschen. Neue Google Cloud-Nutzer können am kostenlosen Testzeitraum mit einem Guthaben von 300$ teilnehmen.
Cloud Shell starten
Während Sie Google Cloud von Ihrem Laptop aus per Fernzugriff nutzen können, wird in diesem Codelab Cloud Shell verwendet, eine Befehlszeilenumgebung, die in der Cloud ausgeführt wird.
Cloud Shell aktivieren
- Klicken Sie in der Cloud Console auf Cloud Shell aktivieren
.

Wenn Sie die Cloud Shell zum ersten Mal starten, wird ein Fenster mit einer Beschreibung eingeblendet. Klicken Sie in diesem Fall einfach auf Weiter.

Das Herstellen der Verbindung mit der Cloud Shell sollte nur wenige Augenblicke dauern.

Auf dieser virtuellen Maschine sind alle erforderlichen Entwicklungstools installiert. Sie bietet ein Basisverzeichnis mit 5 GB nichtflüchtigem Speicher und läuft in Google Cloud, was die Netzwerkleistung und Authentifizierung erheblich verbessert. Die meisten, wenn nicht sogar alle Aufgaben in diesem Codelab können mit einem Browser erledigt werden.
Sobald die Verbindung mit der Cloud Shell hergestellt ist, sehen Sie, dass Sie authentifiziert sind und für das Projekt Ihre Projekt-ID eingestellt ist.
- Führen Sie in der Cloud Shell den folgenden Befehl aus, um zu prüfen, ob Sie authentifiziert sind:
gcloud auth list
Befehlsausgabe
Credentialed Accounts
ACTIVE ACCOUNT
* <my_account>@<my_domain.com>
To set the active account, run:
$ gcloud config set account `ACCOUNT`
- Führen Sie den folgenden Befehl in Cloud Shell aus, um zu bestätigen, dass der gcloud-Befehl Ihr Projekt kennt:
gcloud config list project
Befehlsausgabe
[core] project = <PROJECT_ID>
Ist dies nicht der Fall, können Sie die Einstellung mit diesem Befehl vornehmen:
gcloud config set project <PROJECT_ID>
Befehlsausgabe
Updated property [core/project].
3. Entwicklungsumgebung vorbereiten
In diesem Codelab verwenden Sie das Cloud Shell-Terminal und den Cloud Shell-Editor, um Ihre Java-Programme zu entwickeln.
Vertex AI APIs aktivieren
Prüfen Sie in der Google Cloud Console, ob Ihr Projektname oben in der Google Cloud Console angezeigt wird. Falls nicht, klicken Sie auf Projekt auswählen, um die Projektauswahl zu öffnen, und wählen Sie das gewünschte Projekt aus.
Sie können Vertex AI-APIs entweder im Vertex AI-Bereich der Google Cloud Console oder im Cloud Shell-Terminal aktivieren.
So aktivieren Sie die Funktion über die Google Cloud Console: Rufen Sie zuerst im Menü der Google Cloud Console den Bereich „Vertex AI“ auf:

Klicken Sie im Vertex AI-Dashboard auf Alle empfohlenen APIs aktivieren.
Dadurch werden mehrere APIs aktiviert. Die wichtigste für das Codelab ist jedoch die aiplatform.googleapis.com.
Alternativ können Sie diese API auch über das Cloud Shell-Terminal mit dem folgenden Befehl aktivieren:
gcloud services enable aiplatform.googleapis.com
Klonen Sie das GitHub-Repository.
Klonen Sie im Cloud Shell-Terminal das Repository für dieses Codelab:
git clone https://github.com/glaforge/gemini-workshop-for-java-developers.git
Wenn Sie prüfen möchten, ob das Projekt bereit für die Ausführung ist, können Sie versuchen, das „Hello World“-Programm auszuführen.
Achten Sie darauf, dass Sie sich im Ordner der obersten Ebene befinden:
cd gemini-workshop-for-java-developers/
Gradle-Wrapper erstellen:
gradle wrapper
Mit gradlew ausführen:
./gradlew run
Es sollte folgende Ausgabe angezeigt werden:
.. > Task :app:run Hello World!
Cloud Editor öffnen und einrichten
Öffnen Sie den Code mit dem Cloud Code-Editor über Cloud Shell:

Öffnen Sie im Cloud Code-Editor den Quellordner des Codelabs, indem Sie File -> Open Folder auswählen und auf den Quellordner des Codelabs verweisen (z. B. /home/username/gemini-workshop-for-java-developers/) verwenden.
Umgebungsvariablen einrichten
Öffnen Sie ein neues Terminal im Cloud Code-Editor, indem Sie Terminal -> New Terminal auswählen. Richten Sie zwei Umgebungsvariablen ein, die zum Ausführen der Codebeispiele erforderlich sind:
- PROJECT_ID: Ihre Google Cloud-Projekt-ID
- LOCATION: Die Region, in der das Gemini-Modell bereitgestellt wird.
Exportieren Sie die Variablen so:
export PROJECT_ID=$(gcloud config get-value project) export LOCATION=us-central1
4. Erster Aufruf des Gemini-Modells
Nachdem das Projekt richtig eingerichtet ist, können Sie die Gemini API aufrufen.
Sehen Sie sich QA.java im Verzeichnis app/src/main/java/gemini/workshop an:
package gemini.workshop;
import dev.langchain4j.model.vertexai.VertexAiGeminiChatModel;
import dev.langchain4j.model.chat.ChatLanguageModel;
public class QA {
public static void main(String[] args) {
ChatLanguageModel model = VertexAiGeminiChatModel.builder()
.project(System.getenv("PROJECT_ID"))
.location(System.getenv("LOCATION"))
.modelName("gemini-2.0-flash")
.build();
System.out.println(model.generate("Why is the sky blue?"));
}
}
In diesem ersten Beispiel müssen Sie die Klasse VertexAiGeminiChatModel importieren, die die Schnittstelle ChatModel implementiert.
In der Methode main konfigurieren Sie das Chat-Sprachmodell mit dem Builder für VertexAiGeminiChatModel und geben Folgendes an:
- Projekt
- Standort
- Modellname (
gemini-2.0-flash)
Das Sprachmodell ist jetzt bereit. Sie können die Methode generate() aufrufen und Ihren Prompt, Ihre Frage oder Ihre Anweisungen übergeben, die an das LLM gesendet werden sollen. Hier stellen Sie eine einfache Frage dazu, warum der Himmel blau ist.
Sie können diesen Prompt ändern, um verschiedene Fragen oder Aufgaben auszuprobieren.
Führen Sie das Beispiel im Stammverzeichnis des Quellcodes aus:
./gradlew run -q -DjavaMainClass=gemini.workshop.QA
Die Ausgabe sollte in etwa so aussehen:
The sky appears blue because of a phenomenon called Rayleigh scattering. When sunlight enters the atmosphere, it is made up of a mixture of different wavelengths of light, each with a different color. The different wavelengths of light interact with the molecules and particles in the atmosphere in different ways. The shorter wavelengths of light, such as those corresponding to blue and violet light, are more likely to be scattered in all directions by these particles than the longer wavelengths of light, such as those corresponding to red and orange light. This is because the shorter wavelengths of light have a smaller wavelength and are able to bend around the particles more easily. As a result of Rayleigh scattering, the blue light from the sun is scattered in all directions, and it is this scattered blue light that we see when we look up at the sky. The blue light from the sun is not actually scattered in a single direction, so the color of the sky can vary depending on the position of the sun in the sky and the amount of dust and water droplets in the atmosphere.
Herzlichen Glückwunsch, Sie haben Ihren ersten Aufruf an Gemini gestartet!
Streamingantwort
Haben Sie bemerkt, dass die Antwort nach einigen Sekunden in einem Durchgang gegeben wurde? Dank der Streaming-Antwortvariante ist es auch möglich, die Antwort schrittweise zu erhalten. Beim Streaming gibt das Modell die Antwort nach und nach zurück, sobald sie verfügbar ist.
In diesem Codelab verwenden wir die Nicht-Streamingantwort. Sehen wir uns aber die Streamingantwort an, um zu sehen, wie sie funktioniert.
In StreamQA.java im Verzeichnis app/src/main/java/gemini/workshop sehen Sie die Streaming-Antwort in Aktion:
package gemini.workshop;
import dev.langchain4j.model.chat.StreamingChatLanguageModel;
import dev.langchain4j.model.vertexai.VertexAiGeminiStreamingChatModel;
import static dev.langchain4j.model.LambdaStreamingResponseHandler.onNext;
public class StreamQA {
public static void main(String[] args) {
StreamingChatLanguageModel model = VertexAiGeminiStreamingChatModel.builder()
.project(System.getenv("PROJECT_ID"))
.location(System.getenv("LOCATION"))
.modelName("gemini-2.0-flash")
.maxOutputTokens(4000)
.build();
model.generate("Why is the sky blue?", onNext(System.out::println));
}
}
Dieses Mal importieren wir die Streaming-Klassenvarianten VertexAiGeminiStreamingChatModel, die die StreamingChatLanguageModel-Schnittstelle implementieren. Außerdem müssen Sie LambdaStreamingResponseHandler.onNext statisch importieren. Das ist eine Hilfsmethode, die StreamingResponseHandler bereitstellt, um einen Streaming-Handler mit Java-Lambda-Ausdrücken zu erstellen.
Dieses Mal unterscheidet sich die Signatur der Methode generate() etwas. Anstatt einen String zurückzugeben, ist der Rückgabetyp „void“. Zusätzlich zum Prompt müssen Sie einen Streaming-Antworthandler übergeben. Dank des oben erwähnten statischen Imports können wir hier einen Lambda-Ausdruck definieren, den Sie an die Methode onNext() übergeben. Der Lambda-Ausdruck wird jedes Mal aufgerufen, wenn ein neuer Teil der Antwort verfügbar ist. Der zweite Callback wird nur aufgerufen, wenn ein Fehler auftritt.
Ausführen:
./gradlew run -q -DjavaMainClass=gemini.workshop.StreamQA
Sie erhalten eine ähnliche Antwort wie bei der vorherigen Klasse. Dieses Mal wird die Antwort jedoch nach und nach in der Shell angezeigt, anstatt auf die vollständige Antwort zu warten.
Zusätzliche Konfiguration
Für die Konfiguration haben wir nur das Projekt, den Standort und den Modellnamen definiert. Es gibt jedoch weitere Parameter, die Sie für das Modell angeben können:
temperature(Float temp): Damit legen Sie fest, wie kreativ die Antwort sein soll. Bei 0 ist die Kreativität gering und die Antwort ist oft sachlicher, während bei 2 kreativere Ausgaben erzielt werden.topP(Float topP)– um die möglichen Wörter auszuwählen, deren Gesamtwahrscheinlichkeit dieser Gleitkommazahl (zwischen 0 und 1) entspricht.topK(Integer topK): Hiermit wird ein Wort aus einer maximalen Anzahl wahrscheinlicher Wörter für die Textvervollständigung zufällig ausgewählt (von 1 bis 40).maxOutputTokens(Integer max)– damit wird die maximale Länge der Antwort des Modells angegeben (in der Regel entsprechen 4 Tokens etwa 3 Wörtern).maxRetries(Integer retries): Wenn Sie das Kontingent für Anfragen pro Zeitraum überschreiten oder auf der Plattform ein technisches Problem auftritt, kann das Modell den Aufruf dreimal wiederholen.
Bisher haben Sie Gemini nur eine Frage gestellt. Sie können aber auch eine wechselseitige Konversation führen. Das ist das Thema des nächsten Abschnitts.
5. Mit Gemini chatten
Im vorherigen Schritt haben Sie eine einzelne Frage gestellt. Jetzt ist es an der Zeit für eine echte Unterhaltung zwischen einem Nutzer und dem LLM. Jede Frage und Antwort kann auf den vorherigen aufbauen, um eine echte Diskussion zu ermöglichen.
Sehen Sie sich Conversation.java im Ordner app/src/main/java/gemini/workshop an:
package gemini.workshop;
import dev.langchain4j.model.chat.ChatLanguageModel;
import dev.langchain4j.model.vertexai.VertexAiGeminiChatModel;
import dev.langchain4j.memory.chat.MessageWindowChatMemory;
import dev.langchain4j.service.AiServices;
import java.util.List;
public class Conversation {
public static void main(String[] args) {
ChatLanguageModel model = VertexAiGeminiChatModel.builder()
.project(System.getenv("PROJECT_ID"))
.location(System.getenv("LOCATION"))
.modelName("gemini-2.0-flash")
.build();
MessageWindowChatMemory chatMemory = MessageWindowChatMemory.builder()
.maxMessages(20)
.build();
interface ConversationService {
String chat(String message);
}
ConversationService conversation =
AiServices.builder(ConversationService.class)
.chatLanguageModel(model)
.chatMemory(chatMemory)
.build();
List.of(
"Hello!",
"What is the country where the Eiffel tower is situated?",
"How many inhabitants are there in that country?"
).forEach( message -> {
System.out.println("\nUser: " + message);
System.out.println("Gemini: " + conversation.chat(message));
});
}
}
In dieser Klasse gibt es einige neue interessante Importe:
MessageWindowChatMemory– eine Klasse, die den Aspekt der Unterhaltung mit mehreren Runden verarbeitet und die vorherigen Fragen und Antworten im lokalen Speicher behält.AiServices: Eine Abstraktionsklasse auf höherer Ebene, die das Chatmodell und den Chat-Speicher miteinander verbindet.
In der Hauptmethode richten Sie das Modell, den Chat-Speicher und den KI-Dienst ein. Das Modell wird wie gewohnt mit den Informationen zu Projekt, Standort und Modellname konfiguriert.
Für das Chat-Gedächtnis verwenden wir den MessageWindowChatMemory-Builder, um ein Gedächtnis zu erstellen, in dem die letzten 20 ausgetauschten Nachrichten gespeichert werden. Es handelt sich um ein gleitendes Fenster für das Gespräch, dessen Kontext lokal in unserem Java-Klassenclient gespeichert wird.
Anschließend erstellen Sie AI service, das das Chatmodell mit dem Chat-Speicher verknüpft.
Der KI-Dienst verwendet eine benutzerdefinierte ConversationService-Schnittstelle, die wir definiert haben, die von LangChain4j implementiert wird und die eine String-Anfrage entgegennimmt und eine String-Antwort zurückgibt.
Jetzt ist es an der Zeit, sich mit Gemini zu unterhalten. Zuerst wird eine einfache Begrüßung gesendet, dann eine erste Frage zum Eiffelturm, um zu erfahren, in welchem Land er sich befindet. Beachten Sie, dass sich der letzte Satz auf die Antwort auf die erste Frage bezieht, da Sie sich fragen, wie viele Einwohner das Land hat, in dem sich der Eiffelturm befindet, ohne das Land, das in der vorherigen Antwort angegeben wurde, explizit zu erwähnen. Daraus geht hervor, dass frühere Fragen und Antworten mit jedem Prompt gesendet werden.
So führen Sie das Beispiel aus:
./gradlew run -q -DjavaMainClass=gemini.workshop.Conversation
Sie sollten drei Antworten ähnlich den folgenden sehen:
User: Hello! Gemini: Hi there! How can I assist you today? User: What is the country where the Eiffel tower is situated? Gemini: France User: How many inhabitants are there in that country? Gemini: As of 2023, the population of France is estimated to be around 67.8 million.
Sie können Gemini einzelne Fragen stellen oder sich mit Gemini unterhalten. Bisher war die Eingabe jedoch nur Text. Was ist mit Bildern? Im nächsten Schritt sehen wir uns Bilder an.
6. Multimodalität mit Gemini
Gemini ist ein multimodales Modell. Sie akzeptiert nicht nur Text, sondern auch Bilder oder sogar Videos als Eingabe. In diesem Abschnitt sehen Sie ein Beispiel für die Kombination von Text und Bildern.
Glaubst du, dass Gemini diese Katze erkennen wird?

Bild einer Katze im Schnee, aufgenommen von Wikipedia
Sehen Sie sich Multimodal.java im Verzeichnis app/src/main/java/gemini/workshop an:
package gemini.workshop;
import dev.langchain4j.model.chat.ChatLanguageModel;
import dev.langchain4j.model.vertexai.VertexAiGeminiChatModel;
import dev.langchain4j.data.message.UserMessage;
import dev.langchain4j.data.message.AiMessage;
import dev.langchain4j.model.output.Response;
import dev.langchain4j.data.message.ImageContent;
import dev.langchain4j.data.message.TextContent;
public class Multimodal {
static final String CAT_IMAGE_URL =
"https://upload.wikimedia.org/wikipedia/" +
"commons/b/b6/Felis_catus-cat_on_snow.jpg";
public static void main(String[] args) {
ChatLanguageModel model = VertexAiGeminiChatModel.builder()
.project(System.getenv("PROJECT_ID"))
.location(System.getenv("LOCATION"))
.modelName("gemini-2.0-flash")
.build();
UserMessage userMessage = UserMessage.from(
ImageContent.from(CAT_IMAGE_URL),
TextContent.from("Describe the picture")
);
Response<AiMessage> response = model.generate(userMessage);
System.out.println(response.content().text());
}
}
Bei den Importen wird zwischen verschiedenen Arten von Nachrichten und Inhalten unterschieden. Ein UserMessage kann sowohl ein TextContent- als auch ein ImageContent-Objekt enthalten. Hier kommt die Multimodalität ins Spiel: Text und Bilder werden kombiniert. Wir senden nicht nur einen einfachen String-Prompt, sondern ein strukturierteres Objekt, das eine Nutzernachricht darstellt und aus einem Bild- und einem Textinhalt besteht. Das Modell sendet ein Response zurück, das ein AiMessage enthält.
Anschließend rufen Sie den AiMessage aus der Antwort über content() und dann den Text der Nachricht über text() ab.
So führen Sie das Beispiel aus:
./gradlew run -q -DjavaMainClass=gemini.workshop.Multimodal
Der Name des Bildes hat Ihnen sicher einen Hinweis darauf gegeben, was auf dem Bild zu sehen ist, aber die Gemini-Ausgabe sieht in etwa so aus:
A cat with brown fur is walking in the snow. The cat has a white patch of fur on its chest and white paws. The cat is looking at the camera.
Die Kombination von Bild- und Text-Prompts eröffnet interessante Anwendungsfälle. Sie können Anwendungen erstellen, die Folgendes können:
- Text in Bildern erkennen
- Prüfen, ob ein Bild sicher angezeigt werden kann.
- Bildunterschriften erstellen
- Mit einfachen Textbeschreibungen in einer Bilddatenbank suchen
Sie können nicht nur Informationen aus Bildern, sondern auch aus unstrukturiertem Text extrahieren. Das erfahren Sie im nächsten Abschnitt.
7. Strukturierte Informationen aus unstrukturiertem Text extrahieren
Es gibt viele Situationen, in denen wichtige Informationen in Berichten, E‑Mails oder anderen Texten im Langformat unstrukturiert angegeben werden. Im Idealfall möchten Sie die wichtigsten Details aus dem unstrukturierten Text in Form von strukturierten Objekten extrahieren. Sehen wir uns an, wie das funktioniert.
Angenommen, Sie möchten den Namen und das Alter einer Person aus einer Biografie, einem Lebenslauf oder einer Beschreibung dieser Person extrahieren. Sie können das LLM anweisen, JSON aus unstrukturiertem Text zu extrahieren, indem Sie einen entsprechend angepassten Prompt verwenden (dies wird häufig als Prompt-Engineering bezeichnet).
Im folgenden Beispiel erstellen wir jedoch keinen Prompt, der die JSON-Ausgabe beschreibt, sondern verwenden eine leistungsstarke Funktion von Gemini namens strukturierte Ausgabe oder manchmal auch eingeschränkte Generierung. Dadurch wird das Modell gezwungen, nur gültige JSON-Inhalte auszugeben, die einem angegebenen JSON-Schema entsprechen.
Sehen Sie sich ExtractData.java in app/src/main/java/gemini/workshop an:
package gemini.workshop;
import dev.langchain4j.model.chat.ChatLanguageModel;
import dev.langchain4j.model.vertexai.VertexAiGeminiChatModel;
import dev.langchain4j.service.AiServices;
import dev.langchain4j.service.SystemMessage;
import static dev.langchain4j.model.vertexai.SchemaHelper.fromClass;
public class ExtractData {
record Person(String name, int age) { }
interface PersonExtractor {
@SystemMessage("""
Your role is to extract the name and age
of the person described in the biography.
""")
Person extractPerson(String biography);
}
public static void main(String[] args) {
ChatLanguageModel model = VertexAiGeminiChatModel.builder()
.project(System.getenv("PROJECT_ID"))
.location(System.getenv("LOCATION"))
.modelName("gemini-2.0-flash")
.responseMimeType("application/json")
.responseSchema(fromClass(Person.class))
.build();
PersonExtractor extractor = AiServices.create(PersonExtractor.class, model);
String bio = """
Anna is a 23 year old artist based in Brooklyn, New York. She was born and
raised in the suburbs of Chicago, where she developed a love for art at a
young age. She attended the School of the Art Institute of Chicago, where
she studied painting and drawing. After graduating, she moved to New York
City to pursue her art career. Anna's work is inspired by her personal
experiences and observations of the world around her. She often uses bright
colors and bold lines to create vibrant and energetic paintings. Her work
has been exhibited in galleries and museums in New York City and Chicago.
""";
Person person = extractor.extractPerson(bio);
System.out.println(person.name()); // Anna
System.out.println(person.age()); // 23
}
}
Sehen wir uns die verschiedenen Schritte in dieser Datei an:
- Ein
Person-Datensatz wird definiert, um die Details einer Person (Name und Alter) darzustellen. - Die
PersonExtractor-Schnittstelle wird mit einer Methode definiert, die bei einem unstrukturierten Textstring einePerson-Instanz zurückgibt. - Die
extractPerson()ist mit einer@SystemMessage-Anmerkung versehen, die einen Anleitungsprompt damit verknüpft. Das ist der Prompt, den das Modell verwendet, um die Informationen zu extrahieren und die Details in Form eines JSON-Dokuments zurückzugeben. Dieses wird für Sie geparst und in einePerson-Instanz unmarshalled.
Sehen wir uns nun den Inhalt der Methode main() an:
- Das Chatmodell wird konfiguriert und instanziiert. Wir verwenden zwei neue Methoden der Modell-Builder-Klasse:
responseMimeType()undresponseSchema(). Der erste weist Gemini an, gültiges JSON in der Ausgabe zu generieren. Die zweite Methode definiert das Schema des JSON-Objekts, das zurückgegeben werden soll. Außerdem wird an eine Hilfsmethode delegiert, die eine Java-Klasse oder einen Java-Datensatz in ein gültiges JSON-Schema konvertieren kann. - Ein
PersonExtractor-Objekt wird mit der KlasseAiServicesvon LangChain4j erstellt. - Anschließend können Sie einfach
Person person = extractor.extractPerson(...)aufrufen, um die Details der Person aus dem unstrukturierten Text zu extrahieren und einePerson-Instanz mit dem Namen und Alter zurückzugeben.
So führen Sie das Beispiel aus:
./gradlew run -q -DjavaMainClass=gemini.workshop.ExtractData
Es sollte folgende Ausgabe angezeigt werden:
Anna 23
Ja, das ist Anna und sie ist 23 Jahre alt.
Bei diesem AiServices-Ansatz arbeiten Sie mit stark typisierten Objekten. Sie interagieren nicht direkt mit dem LLM. Stattdessen arbeiten Sie mit konkreten Klassen wie dem Person-Datensatz, um die extrahierten personenbezogenen Daten darzustellen. Außerdem haben Sie ein PersonExtractor-Objekt mit einer extractPerson()-Methode, die eine Person-Instanz zurückgibt. Das Konzept von LLM wird abstrahiert. Als Java-Entwickler arbeiten Sie mit normalen Klassen und Objekten, wenn Sie diese PersonExtractor-Schnittstelle verwenden.
8. Prompts mit Prompt-Vorlagen strukturieren
Wenn Sie mit einem LLM interagieren und dabei eine Reihe von Anweisungen oder Fragen verwenden, gibt es einen Teil des Prompts, der sich nie ändert, während andere Teile die Daten enthalten. Wenn Sie beispielsweise Rezepte erstellen möchten, können Sie einen Prompt wie „Du bist ein talentierter Koch. Bitte erstelle ein Rezept mit den folgenden Zutaten: …“ verwenden und dann die Zutaten an das Ende dieses Texts anhängen. Dafür sind Prompt-Vorlagen da – ähnlich wie interpolierte Strings in Programmiersprachen. Eine Promptvorlage enthält Platzhalter, die Sie durch die richtigen Daten für einen bestimmten Aufruf des LLM ersetzen können.
Sehen wir uns TemplatePrompt.java im Verzeichnis app/src/main/java/gemini/workshop genauer an:
package gemini.workshop;
import dev.langchain4j.model.chat.ChatLanguageModel;
import dev.langchain4j.model.vertexai.VertexAiGeminiChatModel;
import dev.langchain4j.data.message.AiMessage;
import dev.langchain4j.model.input.Prompt;
import dev.langchain4j.model.input.PromptTemplate;
import dev.langchain4j.model.output.Response;
import java.util.HashMap;
import java.util.Map;
public class TemplatePrompt {
public static void main(String[] args) {
ChatLanguageModel model = VertexAiGeminiChatModel.builder()
.project(System.getenv("PROJECT_ID"))
.location(System.getenv("LOCATION"))
.modelName("gemini-2.0-flash")
.maxOutputTokens(500)
.temperature(1.0f)
.topK(40)
.topP(0.95f)
.maxRetries(3)
.build();
PromptTemplate promptTemplate = PromptTemplate.from("""
You're a friendly chef with a lot of cooking experience.
Create a recipe for a {{dish}} with the following ingredients: \
{{ingredients}}, and give it a name.
"""
);
Map<String, Object> variables = new HashMap<>();
variables.put("dish", "dessert");
variables.put("ingredients", "strawberries, chocolate, and whipped cream");
Prompt prompt = promptTemplate.apply(variables);
Response<AiMessage> response = model.generate(prompt.toUserMessage());
System.out.println(response.content().text());
}
}
Wie gewohnt konfigurieren Sie das VertexAiGeminiChatModel-Modell mit einem hohen Maß an Kreativität durch eine hohe Temperatur sowie hohe TopP- und TopK-Werte. Anschließend erstellen Sie mit der statischen Methode from() ein PromptTemplate, indem Sie den String unseres Prompts übergeben und die Platzhaltervariablen mit doppelten geschweiften Klammern verwenden: {{dish}} und {{ingredients}}.
Sie erstellen den endgültigen Prompt, indem Sie apply() aufrufen. Diese Funktion verwendet eine Zuordnung von Schlüssel/Wert-Paaren, die den Namen des Platzhalters und den Stringwert darstellen, durch den er ersetzt werden soll.
Zuletzt rufen Sie die generate()-Methode des Gemini-Modells auf, indem Sie mit der prompt.toUserMessage()-Anweisung eine Nutzernachricht aus diesem Prompt erstellen.
So führen Sie das Beispiel aus:
./gradlew run -q -DjavaMainClass=gemini.workshop.TemplatePrompt
Die generierte Ausgabe sollte in etwa so aussehen:
**Strawberry Shortcake** Ingredients: * 1 pint strawberries, hulled and sliced * 1/2 cup sugar * 1/4 cup cornstarch * 1/4 cup water * 1 tablespoon lemon juice * 1/2 cup heavy cream, whipped * 1/4 cup confectioners' sugar * 1/4 teaspoon vanilla extract * 6 graham cracker squares, crushed Instructions: 1. In a medium saucepan, combine the strawberries, sugar, cornstarch, water, and lemon juice. Bring to a boil over medium heat, stirring constantly. Reduce heat and simmer for 5 minutes, or until the sauce has thickened. 2. Remove from heat and let cool slightly. 3. In a large bowl, combine the whipped cream, confectioners' sugar, and vanilla extract. Beat until soft peaks form. 4. To assemble the shortcakes, place a graham cracker square on each of 6 dessert plates. Top with a scoop of whipped cream, then a spoonful of strawberry sauce. Repeat layers, ending with a graham cracker square. 5. Serve immediately. **Tips:** * For a more elegant presentation, you can use fresh strawberries instead of sliced strawberries. * If you don't have time to make your own whipped cream, you can use store-bought whipped cream.
Sie können die Werte von dish und ingredients in der Karte ändern, die Temperatur, topK und tokP anpassen und den Code noch einmal ausführen. So können Sie die Auswirkungen der Änderung dieser Parameter auf das LLM beobachten.
Prompt-Vorlagen sind eine gute Möglichkeit, wiederverwendbare und parametrisierbare Anweisungen für LLM-Aufrufe zu erstellen. Sie können Daten übergeben und Prompts für verschiedene Werte anpassen, die von Ihren Nutzern bereitgestellt werden.
9. Textklassifizierung mit Few-Shot-Prompting
LLMs können Text recht gut in verschiedene Kategorien einteilen. Sie können ein LLM bei dieser Aufgabe unterstützen, indem Sie einige Beispiele für Texte und die zugehörigen Kategorien angeben. Dieser Ansatz wird oft als Few-Shot-Prompting bezeichnet.
Öffnen wir TextClassification.java im Verzeichnis app/src/main/java/gemini/workshop, um eine bestimmte Art der Textklassifizierung durchzuführen: die Sentimentanalyse.
package gemini.workshop;
import com.google.cloud.vertexai.api.Schema;
import com.google.cloud.vertexai.api.Type;
import dev.langchain4j.data.message.UserMessage;
import dev.langchain4j.memory.chat.MessageWindowChatMemory;
import dev.langchain4j.model.chat.ChatLanguageModel;
import dev.langchain4j.model.vertexai.VertexAiGeminiChatModel;
import dev.langchain4j.data.message.AiMessage;
import dev.langchain4j.service.AiServices;
import dev.langchain4j.service.SystemMessage;
import java.util.List;
public class TextClassification {
enum Sentiment { POSITIVE, NEUTRAL, NEGATIVE }
public static void main(String[] args) {
ChatLanguageModel model = VertexAiGeminiChatModel.builder()
.project(System.getenv("PROJECT_ID"))
.location(System.getenv("LOCATION"))
.modelName("gemini-2.0-flash")
.maxOutputTokens(10)
.maxRetries(3)
.responseSchema(Schema.newBuilder()
.setType(Type.STRING)
.addAllEnum(List.of("POSITIVE", "NEUTRAL", "NEGATIVE"))
.build())
.build();
interface SentimentAnalysis {
@SystemMessage("""
Analyze the sentiment of the text below.
Respond only with one word to describe the sentiment.
""")
Sentiment analyze(String text);
}
MessageWindowChatMemory memory = MessageWindowChatMemory.withMaxMessages(10);
memory.add(UserMessage.from("This is fantastic news!"));
memory.add(AiMessage.from(Sentiment.POSITIVE.name()));
memory.add(UserMessage.from("Pi is roughly equal to 3.14"));
memory.add(AiMessage.from(Sentiment.NEUTRAL.name()));
memory.add(UserMessage.from("I really disliked the pizza. Who would use pineapples as a pizza topping?"));
memory.add(AiMessage.from(Sentiment.NEGATIVE.name()));
SentimentAnalysis sentimentAnalysis =
AiServices.builder(SentimentAnalysis.class)
.chatLanguageModel(model)
.chatMemory(memory)
.build();
System.out.println(sentimentAnalysis.analyze("I love strawberries!"));
}
}
Eine Sentiment-Enumeration listet die verschiedenen Werte für eine Stimmung auf: negativ, neutral oder positiv.
Bei der Methode main() erstellen Sie das Gemini-Chatmodell wie gewohnt, aber mit einer geringen maximalen Anzahl von Ausgabetokens, da Sie nur eine kurze Antwort wünschen: Der Text ist POSITIVE, NEGATIVE oder NEUTRAL. Wenn Sie das Modell darauf beschränken möchten, nur diese Werte zurückzugeben, können Sie die Unterstützung strukturierter Ausgaben nutzen, die Sie im Abschnitt zur Datenextraktion kennengelernt haben. Deshalb wird die Methode responseSchema() verwendet. Dieses Mal verwenden Sie nicht die praktische Methode aus SchemaHelper, um die Schemadefinition abzuleiten, sondern den Schema-Builder, um zu sehen, wie eine Schemadefinition aussieht.
Nachdem das Modell konfiguriert wurde, erstellen Sie eine SentimentAnalysis-Schnittstelle, die von AiServices von LangChain4j mithilfe des LLM implementiert wird. Diese Schnittstelle enthält eine Methode: analyze(). Es wird der zu analysierende Text als Eingabe verwendet und ein Sentiment-Enum-Wert zurückgegeben. Sie bearbeiten also nur ein stark typisiertes Objekt, das die erkannte Stimmungsart darstellt.
Um dem Modell „Few-Shot-Beispiele“ zu geben, damit es die Klassifizierung durchführt, erstellen Sie einen Chat-Speicher, um Paare von Nutzernachrichten und KI-Antworten zu übergeben, die den Text und die damit verbundene Stimmung repräsentieren.
Binden wir alles mit der Methode AiServices.builder() zusammen, indem wir unsere SentimentAnalysis-Schnittstelle, das zu verwendende Modell und den Chat-Speicher mit den Few-Shot-Beispielen übergeben. Rufen Sie schließlich die Methode analyze() mit dem zu analysierenden Text auf.
So führen Sie das Beispiel aus:
./gradlew run -q -DjavaMainClass=gemini.workshop.TextClassification
Es sollte ein einzelnes Wort angezeigt werden:
POSITIVE
„Erdbeeren lieben“ ist anscheinend eine positive Aussage.
10. Retrieval-Augmented Generation
LLMs werden mit einer großen Menge an Text trainiert. Das Wissen von Gemini umfasst jedoch nur Informationen, die für das Training verwendet wurden. Wenn nach dem Stichtag für das Modelltraining neue Informationen veröffentlicht werden, stehen diese dem Modell nicht zur Verfügung. Das Modell kann also keine Fragen zu Informationen beantworten, die ihm nicht bekannt sind.
Deshalb können Ansätze wie Retrieval-Augmented Generation (RAG), die in diesem Abschnitt behandelt werden, zusätzliche Informationen liefern, die ein LLM möglicherweise benötigt, um die Anfragen seiner Nutzer zu erfüllen und mit Informationen zu antworten, die aktueller sind oder private Informationen enthalten, auf die zum Zeitpunkt des Trainings nicht zugegriffen werden konnte.
Kommen wir noch einmal auf Unterhaltungen zurück. Dieses Mal können Sie Fragen zu Ihren Dokumenten stellen. Sie erstellen einen Chatbot, der relevante Informationen aus einer Datenbank mit Ihren in kleinere Teile („Chunks“) aufgeteilten Dokumenten abrufen kann. Diese Informationen werden vom Modell verwendet, um seine Antworten zu fundieren, anstatt sich nur auf das Wissen zu verlassen, das in seinem Training enthalten ist.
RAG besteht aus zwei Phasen:
- Aufnahmephase: Dokumente werden in den Arbeitsspeicher geladen, in kleinere Chunks aufgeteilt und Vektoreinbettungen (eine hochdimensionale Vektordarstellung der Chunks) werden berechnet und in einer Vektordatenbank gespeichert, die semantische Suchvorgänge ausführen kann. Diese Phase wird normalerweise einmal durchlaufen, wenn dem Dokumentkorpus neue Dokumente hinzugefügt werden müssen.

- Anfragephase: Nutzer können jetzt Fragen zu den Dokumenten stellen. Die Frage wird ebenfalls in einen Vektor umgewandelt und mit allen anderen Vektoren in der Datenbank verglichen. Die ähnlichsten Vektoren sind in der Regel semantisch verwandt und werden von der Vektordatenbank zurückgegeben. Das LLM erhält dann den Kontext des Gesprächs und die Textblöcke, die den von der Datenbank zurückgegebenen Vektoren entsprechen. Es wird aufgefordert, seine Antwort auf diese Blöcke zu stützen.

Dokumente vorbereiten
In diesem neuen Beispiel stellen Sie Fragen zu einem fiktiven Automodell eines ebenfalls fiktiven Automobilherstellers: dem Cymbal Starlight. Ein Dokument über ein fiktives Auto sollte nicht Teil des Wissens des Modells sein. Wenn Gemini Fragen zu diesem Auto richtig beantworten kann, bedeutet das, dass der RAG-Ansatz funktioniert: Gemini kann Ihr Dokument durchsuchen.
Chatbot implementieren
Sehen wir uns an, wie der zweiphasige Ansatz erstellt wird: zuerst mit der Dokumentaufnahme und dann mit der Abfragezeit (auch „Abrufphase“ genannt), wenn Nutzer Fragen zum Dokument stellen.
In diesem Beispiel werden beide Phasen in derselben Klasse implementiert. Normalerweise haben Sie eine Anwendung, die sich um die Aufnahme kümmert, und eine andere Anwendung, die Ihren Nutzern die Chatbot-Oberfläche bietet.
Außerdem verwenden wir in diesem Beispiel eine In-Memory-Vektordatenbank. In einem echten Produktionsszenario wären die Aufnahme- und die Abfragephasen in zwei separaten Anwendungen unterteilt und die Vektoren würden in einer eigenständigen Datenbank gespeichert.
Dokumentaufnahme
Der erste Schritt der Dokumentaufnahmephase besteht darin, die PDF-Datei zu unserem fiktiven Auto zu finden und eine PdfParser vorzubereiten, um sie zu lesen:
URL url = new URI("https://raw.githubusercontent.com/meteatamel/genai-beyond-basics/main/samples/grounding/vertexai-search/cymbal-starlight-2024.pdf").toURL();
ApachePdfBoxDocumentParser pdfParser = new ApachePdfBoxDocumentParser();
Document document = pdfParser.parse(url.openStream());
Anstatt zuerst das übliche Chat-Sprachmodell zu erstellen, erstellen Sie eine Instanz eines Embedding-Modells. Dieses spezielle Modell erstellt Vektordarstellungen von Textfragmenten (Wörtern, Sätzen oder sogar Absätzen). Es werden Vektoren von Gleitkommazahlen zurückgegeben, nicht Textantworten.
VertexAiEmbeddingModel embeddingModel = VertexAiEmbeddingModel.builder()
.endpoint(System.getenv("LOCATION") + "-aiplatform.googleapis.com:443")
.project(System.getenv("PROJECT_ID"))
.location(System.getenv("LOCATION"))
.publisher("google")
.modelName("text-embedding-005")
.maxRetries(3)
.build();
Als Nächstes benötigen Sie einige Klassen, um zusammenzuarbeiten:
- Laden Sie das PDF-Dokument und teilen Sie es in Blöcke auf.
- Erstellen Sie Vektoreinbettungen für alle diese Chunks.
InMemoryEmbeddingStore<TextSegment> embeddingStore =
new InMemoryEmbeddingStore<>();
EmbeddingStoreIngestor storeIngestor = EmbeddingStoreIngestor.builder()
.documentSplitter(DocumentSplitters.recursive(500, 100))
.embeddingModel(embeddingModel)
.embeddingStore(embeddingStore)
.build();
storeIngestor.ingest(document);
Eine Instanz von InMemoryEmbeddingStore, einer In-Memory-Vektordatenbank, wird erstellt, um die Vektoreinbettungen zu speichern.
Das Dokument wird dank der Klasse DocumentSplitters in Blöcke unterteilt. Der Text der PDF-Datei wird in Snippets mit 500 Zeichen aufgeteilt, mit einer Überschneidung von 100 Zeichen (mit dem folgenden Chunk, um zu vermeiden, dass Wörter oder Sätze in Teile zerlegt werden).
Der Store Ingestor verknüpft den Dokument-Splitter, das Einbettungsmodell zum Berechnen der Vektoren und die In-Memory-Vektordatenbank. Die Methode ingest() übernimmt dann die Aufnahme.
Die erste Phase ist nun abgeschlossen. Das Dokument wurde in Textblöcke mit den zugehörigen Vektoreinbettungen umgewandelt und in der Vektordatenbank gespeichert.
Fragen stellen
Jetzt ist es an der Zeit, Fragen zu stellen. So erstellen Sie ein Chatmodell, um die Unterhaltung zu starten:
ChatLanguageModel model = VertexAiGeminiChatModel.builder()
.project(System.getenv("PROJECT_ID"))
.location(System.getenv("LOCATION"))
.modelName("gemini-2.0-flash")
.maxOutputTokens(1000)
.build();
Außerdem benötigen Sie eine Retriever-Klasse, um die Vektordatenbank (in der Variablen embeddingStore) mit dem Einbettungsmodell zu verknüpfen. Die Aufgabe besteht darin, die Vektordatenbank abzufragen, indem eine Vektoreinbettung für die Nutzeranfrage berechnet wird, um ähnliche Vektoren in der Datenbank zu finden:
EmbeddingStoreContentRetriever retriever =
new EmbeddingStoreContentRetriever(embeddingStore, embeddingModel);
Erstellen Sie eine Schnittstelle, die einen Autoexperten-Assistenten darstellt. Diese Schnittstelle wird von der Klasse AiServices implementiert, damit Sie mit dem Modell interagieren können:
interface CarExpert {
Result<String> ask(String question);
}
Die CarExpert-Schnittstelle gibt eine String-Antwort zurück, die in der Result-Klasse von LangChain4j umschlossen ist. Warum sollte dieser Wrapper verwendet werden? Sie erhalten nicht nur die Antwort, sondern können auch die Chunks aus der Datenbank untersuchen, die vom Content-Retriever zurückgegeben wurden. So können Sie dem Nutzer die Quellen der Dokumente anzeigen, die zur Fundierung der endgültigen Antwort verwendet werden.
An diesem Punkt können Sie einen neuen KI-Dienst konfigurieren:
CarExpert expert = AiServices.builder(CarExpert.class)
.chatLanguageModel(model)
.chatMemory(MessageWindowChatMemory.withMaxMessages(10))
.contentRetriever(retriever)
.build();
Dieser Dienst umfasst:
- Das Modell für die Chatsprache, das Sie zuvor konfiguriert haben.
- Ein Chat-Gedächtnis, um den Überblick über die Unterhaltung zu behalten.
- Der Retriever vergleicht eine Vektoreinbettungsanfrage mit den Vektoren in der Datenbank.
.retrievalAugmentor(DefaultRetrievalAugmentor.builder()
.contentInjector(DefaultContentInjector.builder()
.promptTemplate(PromptTemplate.from("""
You are an expert in car automotive, and you answer concisely.
Here is the question: {{userMessage}}
Answer using the following information:
{{contents}}
"""))
.build())
.contentRetriever(retriever)
.build())
Jetzt können Sie endlich Ihre Fragen stellen.
List.of(
"What is the cargo capacity of Cymbal Starlight?",
"What's the emergency roadside assistance phone number?",
"Are there some special kits available on that car?"
).forEach(query -> {
Result<String> response = expert.ask(query);
System.out.printf("%n=== %s === %n%n %s %n%n", query, response.content());
System.out.println("SOURCE: " + response.sources().getFirst().textSegment().text());
});
Der vollständige Quellcode befindet sich im Verzeichnis app/src/main/java/gemini/workshop unter RAG.java.
So führen Sie das Beispiel aus:
./gradlew -q run -DjavaMainClass=gemini.workshop.RAG
In der Ausgabe sollten Antworten auf Ihre Fragen angezeigt werden:
=== What is the cargo capacity of Cymbal Starlight? === The Cymbal Starlight 2024 has a cargo capacity of 13.5 cubic feet. SOURCE: Cargo The Cymbal Starlight 2024 has a cargo capacity of 13.5 cubic feet. The cargo area is located in the trunk of the vehicle. To access the cargo area, open the trunk lid using the trunk release lever located in the driver's footwell. When loading cargo into the trunk, be sure to distribute the weight evenly. Do not overload the trunk, as this could affect the vehicle's handling and stability. Luggage === What's the emergency roadside assistance phone number? === The emergency roadside assistance phone number is 1-800-555-1212. SOURCE: Chapter 18: Emergencies Roadside Assistance If you experience a roadside emergency, such as a flat tire or a dead battery, you can call roadside assistance for help. Roadside assistance is available 24 hours a day, 7 days a week. To call roadside assistance, dial the following number: 1-800-555-1212 When you call roadside assistance, be prepared to provide the following information: Your name and contact information Your vehicle's make, model, and year Your vehicle's location === Are there some special kits available on that car? === Yes, the Cymbal Starlight comes with a tire repair kit. SOURCE: Lane keeping assist: This feature helps to keep you in your lane by gently steering the vehicle back into the lane if you start to drift. Adaptive cruise control: This feature automatically adjusts your speed to maintain a safe following distance from the vehicle in front of you. Forward collision warning: This feature warns you if you are approaching another vehicle too quickly. Automatic emergency braking: This feature can automatically apply the brakes to avoid a collision.
11. Funktionsaufrufe
Es gibt Situationen, in denen Sie möchten, dass ein LLM Zugriff auf externe Systeme hat, z. B. auf eine Remote-Web-API, die Informationen abruft oder eine Aktion ausführt, oder auf Dienste, die eine Art von Berechnung durchführen. Beispiel:
Remote-Web-APIs:
- Kundenbestellungen verfolgen und aktualisieren
- Suchen oder erstellen Sie ein Ticket in einem Issue-Tracker.
- Echtzeitdaten wie Aktienkurse oder IoT-Sensormessungen abrufen
- E‑Mails senden
Berechnungstools:
- Ein Rechner für komplexere mathematische Probleme.
- Code-Interpretation zum Ausführen von Code, wenn LLMs eine Logik für die Schlussfolgerung benötigen.
- Anfragen in natürlicher Sprache in SQL-Abfragen umwandeln, damit ein LLM eine Datenbank abfragen kann.
Mit Funktionsaufrufen (manchmal auch als „Tools“ oder „Tool-Nutzung“ bezeichnet) kann das Modell in Ihrem Namen einen oder mehrere Funktionsaufrufe anfordern, damit es einen Nutzer-Prompt mit aktuellen Daten beantworten kann.
Wenn ein Nutzer einen bestimmten Prompt eingibt und das LLM über vorhandene Funktionen informiert ist, die für diesen Kontext relevant sein können, kann es mit einer Funktionsaufrufanfrage antworten. Die Anwendung, in die das LLM integriert ist, kann die Funktion dann in ihrem Namen aufrufen und dem LLM mit einer Antwort antworten. Das LLM interpretiert die Antwort dann, indem es mit einer Textantwort antwortet.
Vier Schritte beim Funktionsaufruf
Sehen wir uns ein Beispiel für Funktionsaufrufe an: Informationen zur Wettervorhersage abrufen.
Wenn Sie Gemini oder ein anderes LLM nach dem Wetter in Paris fragen, antwortet es, dass es keine Informationen zur aktuellen Wettervorhersage hat. Wenn das LLM Echtzeitzugriff auf die Wetterdaten haben soll, müssen Sie einige Funktionen definieren, die es anfordern kann.
Sehen Sie sich das folgende Diagramm an:

1️⃣ Zuerst fragt ein Nutzer nach dem Wetter in Paris. Die Chatbot-App (mit LangChain4j) weiß, dass es eine oder mehrere Funktionen gibt, die dem LLM zur Verfügung stehen, um die Anfrage zu bearbeiten. Der Chatbot sendet sowohl den ursprünglichen Prompt als auch die Liste der Funktionen, die aufgerufen werden können. Hier sehen Sie eine Funktion namens getWeather(), die einen Stringparameter für den Standort verwendet.

Da das LLM keine Wettervorhersagen kennt, sendet es anstelle einer Textantwort eine Anfrage zur Ausführung einer Funktion zurück. Der Chatbot muss die Funktion getWeather() mit "Paris" als Standortparameter aufrufen.
2️⃣ Der Chatbot ruft diese Funktion im Namen des LLM auf und ruft die Funktionsantwort ab. Hier stellen wir uns vor, dass die Antwort {"forecast": "sunny"} ist.

3️⃣ Die Chatbot-App sendet die JSON-Antwort zurück an das LLM.

4️⃣ Das LLM sieht sich die JSON-Antwort an, interpretiert die Informationen und antwortet schließlich mit dem Text, dass das Wetter in Paris sonnig ist.

Jeder Schritt als Code
Zuerst konfigurieren Sie das Gemini-Modell wie gewohnt:
ChatLanguageModel model = VertexAiGeminiChatModel.builder()
.project(System.getenv("PROJECT_ID"))
.location(System.getenv("LOCATION"))
.modelName("gemini-2.0-flash")
.maxOutputTokens(100)
.build();
Sie definieren eine Tool-Spezifikation, die die Funktion beschreibt, die aufgerufen werden kann:
ToolSpecification weatherToolSpec = ToolSpecification.builder()
.name("getWeather")
.description("Get the weather forecast for a given location or city")
.parameters(JsonObjectSchema.builder()
.addStringProperty(
"location",
"the location or city to get the weather forecast for")
.build())
.build();
Der Name der Funktion sowie der Name und der Typ des Parameters werden definiert. Beachten Sie, dass sowohl die Funktion als auch die Parameter beschrieben werden. Beschreibungen sind sehr wichtig und helfen dem LLM, die Funktion einer Funktion zu verstehen und so zu beurteilen, ob diese Funktion im Kontext der Unterhaltung aufgerufen werden muss.
Beginnen wir mit Schritt 1, indem wir die erste Frage zum Wetter in Paris senden:
List<ChatMessage> allMessages = new ArrayList<>();
// 1) Ask the question about the weather
UserMessage weatherQuestion = UserMessage.from("What is the weather in Paris?");
allMessages.add(weatherQuestion);
In Schritt 2 übergeben wir das Tool, das das Modell verwenden soll, und das Modell antwortet mit einer Tool-Ausführungsanfrage:
// 2) The model replies with a function call request
Response<AiMessage> messageResponse = model.generate(allMessages, weatherToolSpec);
ToolExecutionRequest toolExecutionRequest = messageResponse.content().toolExecutionRequests().getFirst();
System.out.println("Tool execution request: " + toolExecutionRequest);
allMessages.add(messageResponse.content());
Schritt 3: An diesem Punkt wissen wir, welche Funktion das LLM aufrufen möchte. Im Code wird kein echter Aufruf an eine externe API gesendet, sondern nur eine hypothetische Wettervorhersage zurückgegeben:
// 3) We send back the result of the function call
ToolExecutionResultMessage toolExecResMsg = ToolExecutionResultMessage.from(toolExecutionRequest,
"{\"location\":\"Paris\",\"forecast\":\"sunny\", \"temperature\": 20}");
allMessages.add(toolExecResMsg);
In Schritt 4 erfährt das LLM das Ergebnis der Funktionsausführung und kann dann eine Textantwort erstellen:
// 4) The model answers with a sentence describing the weather
Response<AiMessage> weatherResponse = model.generate(allMessages);
System.out.println("Answer: " + weatherResponse.content().text());
Der vollständige Quellcode befindet sich im Verzeichnis app/src/main/java/gemini/workshop unter FunctionCalling.java.
So führen Sie das Beispiel aus:
./gradlew run -q -DjavaMainClass=gemini.workshop.FunctionCalling
Die Ausgabe sollte in etwa so aussehen:
Tool execution request: ToolExecutionRequest { id = null, name = "getWeatherForecast", arguments = "{"location":"Paris"}" }
Answer: The weather in Paris is sunny with a temperature of 20 degrees Celsius.
In der Ausgabe oben sehen Sie die Anfrage zur Ausführung des Tools sowie die Antwort.
12. LangChain4j übernimmt Funktionsaufrufe
Im vorherigen Schritt haben Sie gesehen, wie die normalen Frage-/Antwort- und Funktionsanfrage-/Antwort-Interaktionen verschachtelt sind. Dazwischen haben Sie die angeforderte Funktionsantwort direkt angegeben, ohne eine echte Funktion aufzurufen.
LangChain4j bietet jedoch auch eine Abstraktion auf höherer Ebene, die die Funktionsaufrufe transparent für Sie verarbeiten kann, während die Konversation wie gewohnt abläuft.
Einzelner Funktionsaufruf
Sehen wir uns FunctionCallingAssistant.java einmal genauer an.
Zuerst erstellen Sie einen Datensatz, der die Datenstruktur der Antwort der Funktion darstellt:
record WeatherForecast(String location, String forecast, int temperature) {}
Die Antwort enthält Informationen zum Standort, zur Vorhersage und zur Temperatur.
Anschließend erstellen Sie eine Klasse, die die eigentliche Funktion enthält, die Sie dem Modell zur Verfügung stellen möchten:
static class WeatherForecastService {
@Tool("Get the weather forecast for a location")
WeatherForecast getForecast(@P("Location to get the forecast for") String location) {
if (location.equals("Paris")) {
return new WeatherForecast("Paris", "Sunny", 20);
} else if (location.equals("London")) {
return new WeatherForecast("London", "Rainy", 15);
} else {
return new WeatherForecast("Unknown", "Unknown", 0);
}
}
}
Diese Klasse enthält nur eine Funktion, sie ist aber mit der Annotation @Tool versehen, die der Beschreibung der Funktion entspricht, die das Modell aufrufen kann.
Die Parameter der Funktion (hier nur einer) werden ebenfalls mit dieser kurzen @P-Anmerkung versehen, die auch eine Beschreibung des Parameters enthält. Sie konnten beliebig viele Funktionen hinzufügen, um sie dem Modell für komplexere Szenarien zur Verfügung zu stellen.
In dieser Klasse geben Sie einige vorgefertigte Antworten zurück. Wenn Sie jedoch einen echten externen Wettervorhersagedienst aufrufen möchten, erfolgt der Aufruf dieses Dienstes im Hauptteil dieser Methode.
Wie wir beim Erstellen von ToolSpecification im vorherigen Ansatz gesehen haben, ist es wichtig, zu dokumentieren, was eine Funktion tut, und zu beschreiben, wofür die Parameter stehen. So kann das Modell nachvollziehen, wie und wann diese Funktion verwendet werden kann.
Als Nächstes können Sie mit LangChain4j eine Schnittstelle bereitstellen, die dem Vertrag entspricht, den Sie für die Interaktion mit dem Modell verwenden möchten. Hier ist es eine einfache Schnittstelle, die einen String mit der Nutzernachricht entgegennimmt und einen String mit der Antwort des Modells zurückgibt:
interface WeatherAssistant {
String chat(String userMessage);
}
Es ist auch möglich, komplexere Signaturen zu verwenden, die UserMessage (für eine Nutzernachricht) oder AiMessage (für eine Modellantwort) von LangChain4j oder sogar ein TokenStream enthalten, wenn Sie komplexere Situationen bewältigen möchten, da diese komplizierteren Objekte auch zusätzliche Informationen wie die Anzahl der verbrauchten Tokens usw. enthalten. Der Einfachheit halber verwenden wir jedoch nur String als Eingabe und String als Ausgabe.
Zum Schluss sehen wir uns die Methode main() an, die alle Teile miteinander verbindet:
public static void main(String[] args) {
ChatLanguageModel model = VertexAiGeminiChatModel.builder()
.project(System.getenv("PROJECT_ID"))
.location(System.getenv("LOCATION"))
.modelName("gemini-2.0-flash")
.build();
WeatherForecastService weatherForecastService = new WeatherForecastService();
WeatherAssistant assistant = AiServices.builder(WeatherAssistant.class)
.chatLanguageModel(model)
.chatMemory(MessageWindowChatMemory.withMaxMessages(10))
.tools(weatherForecastService)
.build();
System.out.println(assistant.chat("What is the weather in Paris?"));
System.out.println(assistant.chat("Is it warmer in London or in Paris?"));
}
Wie gewohnt konfigurieren Sie das Gemini-Chatmodell. Anschließend instanziieren Sie den Wettervorhersagedienst, der die „Funktion“ enthält, die das Modell von uns aufrufen wird.
Jetzt verwenden Sie die AiServices-Klasse noch einmal, um das Chatmodell, den Chat-Speicher und das Tool (d. h. den Wettervorhersagedienst mit seiner Funktion) zu binden. AiServices gibt ein Objekt zurück, das die von Ihnen definierte WeatherAssistant-Schnittstelle implementiert. Jetzt müssen Sie nur noch die Methode chat() dieses Assistenten aufrufen. Beim Aufrufen werden nur die Textantworten angezeigt. Die Anfragen und Antworten für Funktionsaufrufe sind für den Entwickler nicht sichtbar. Diese Anfragen werden automatisch und transparent verarbeitet. Wenn Gemini der Meinung ist, dass eine Funktion aufgerufen werden sollte, antwortet es mit der Funktionsaufrufanfrage. LangChain4j ruft dann die lokale Funktion in Ihrem Namen auf.
So führen Sie das Beispiel aus:
./gradlew run -q -DjavaMainClass=gemini.workshop.FunctionCallingAssistant
Die Ausgabe sollte in etwa so aussehen:
OK. The weather in Paris is sunny with a temperature of 20 degrees.
It is warmer in Paris (20 degrees) than in London (15 degrees).
Das war ein Beispiel für eine einzelne Funktion.
Mehrere Funktionsaufrufe
Sie können auch mehrere Funktionen haben und LangChain4j mehrere Funktionsaufrufe für Sie ausführen lassen. Ein Beispiel mit mehreren Funktionen finden Sie unter MultiFunctionCallingAssistant.java.
Sie hat eine Funktion zum Umrechnen von Währungen:
@Tool("Convert amounts between two currencies")
double convertCurrency(
@P("Currency to convert from") String fromCurrency,
@P("Currency to convert to") String toCurrency,
@P("Amount to convert") double amount) {
double result = amount;
if (fromCurrency.equals("USD") && toCurrency.equals("EUR")) {
result = amount * 0.93;
} else if (fromCurrency.equals("USD") && toCurrency.equals("GBP")) {
result = amount * 0.79;
}
System.out.println(
"convertCurrency(fromCurrency = " + fromCurrency +
", toCurrency = " + toCurrency +
", amount = " + amount + ") == " + result);
return result;
}
Eine weitere Funktion zum Abrufen des Werts einer Aktie:
@Tool("Get the current value of a stock in US dollars")
double getStockPrice(@P("Stock symbol") String symbol) {
double result = 170.0 + 10 * new Random().nextDouble();
System.out.println("getStockPrice(symbol = " + symbol + ") == " + result);
return result;
}
Eine weitere Funktion zum Anwenden eines Prozentsatzes auf einen bestimmten Betrag:
@Tool("Apply a percentage to a given amount")
double applyPercentage(@P("Initial amount") double amount, @P("Percentage between 0-100 to apply") double percentage) {
double result = amount * (percentage / 100);
System.out.println("applyPercentage(amount = " + amount + ", percentage = " + percentage + ") == " + result);
return result;
}
Sie können dann alle diese Funktionen und eine MultiTools-Klasse kombinieren und Fragen wie „Was sind 10% des AAPL-Aktienkurses, umgerechnet von USD in EUR?“ stellen.
public static void main(String[] args) {
ChatLanguageModel model = VertexAiGeminiChatModel.builder()
.project(System.getenv("PROJECT_ID"))
.location(System.getenv("LOCATION"))
.modelName("gemini-2.0-flash")
.maxOutputTokens(100)
.build();
MultiTools multiTools = new MultiTools();
MultiToolsAssistant assistant = AiServices.builder(MultiToolsAssistant.class)
.chatLanguageModel(model)
.chatMemory(withMaxMessages(10))
.tools(multiTools)
.build();
System.out.println(assistant.chat(
"What is 10% of the AAPL stock price converted from USD to EUR?"));
}
Führen Sie den Befehl so aus:
./gradlew run -q -DjavaMainClass=gemini.workshop.MultiFunctionCallingAssistant
Sie sollten sehen, dass die verschiedenen Funktionen aufgerufen werden:
getStockPrice(symbol = AAPL) == 172.8022224055534 convertCurrency(fromCurrency = USD, toCurrency = EUR, amount = 172.8022224055534) == 160.70606683716468 applyPercentage(amount = 160.70606683716468, percentage = 10.0) == 16.07060668371647 10% of the AAPL stock price converted from USD to EUR is 16.07060668371647 EUR.
Für Agents
Funktionsaufrufe sind ein hervorragender Erweiterungsmechanismus für Large Language Models wie Gemini. So können wir komplexere Systeme entwickeln, die oft als „KI-Assistenten“ oder „KI-Agenten“ bezeichnet werden. Diese KI-Agenten können über externe APIs mit der Außenwelt interagieren und Dienste nutzen, die Nebenwirkungen auf die externe Umgebung haben können, z. B. E‑Mails senden oder Tickets erstellen.
Wenn Sie solche leistungsstarken Agents erstellen, sollten Sie dies verantwortungsbewusst tun. Sie sollten eine manuelle Überprüfung in Betracht ziehen, bevor Sie automatische Aktionen ausführen. Bei der Entwicklung von LLM-basierten Agents, die mit der Außenwelt interagieren, ist es wichtig, die Sicherheit im Blick zu behalten.
13. Gemma mit Ollama und TestContainers ausführen
Bisher haben wir Gemini verwendet. Es gibt aber auch Gemma, das kleinere Schwestermodell.
Gemma ist eine Familie leichtgewichtiger, hochmoderner offener Modelle, die auf derselben Forschung und Technologie basieren, die auch für die Erstellung der Gemini-Modelle verwendet werden. Das neueste Gemma-Modell ist Gemma3, das in vier Größen verfügbar ist: 1B (nur Text), 4B, 12B und 27B. Die Gewichte sind frei verfügbar und die geringe Größe ermöglicht die Ausführung auf Ihrem eigenen Gerät, auch auf Ihrem Laptop oder in Cloud Shell.
Wie wird Gemma ausgeführt?
Es gibt viele Möglichkeiten, Gemma auszuführen: in der Cloud über Vertex AI mit einem Klick oder in GKE mit einigen GPUs. Sie können Gemma aber auch lokal ausführen.
Eine gute Option, Gemma lokal auszuführen, ist Ollama. Mit diesem Tool können Sie kleine Modelle wie Llama, Mistral und viele andere auf Ihrem lokalen Computer ausführen. Es ähnelt Docker, ist aber für LLMs konzipiert.
Installieren Sie Ollama gemäß der Anleitung für Ihr Betriebssystem.
Wenn Sie eine Linux-Umgebung verwenden, müssen Sie Ollama nach der Installation zuerst aktivieren.
ollama serve > /dev/null 2>&1 &
Nach der lokalen Installation können Sie Befehle ausführen, um ein Modell abzurufen:
ollama pull gemma3:1b
Warten Sie, bis das Modell abgerufen wurde. Das kann eine Weile dauern.
Modell ausführen:
ollama run gemma3:1b
Sie können jetzt mit dem Modell interagieren:
>>> Hello! Hello! It's nice to hear from you. What can I do for you today?
Drücken Sie Strg + D, um die Eingabeaufforderung zu beenden.
Gemma in Ollama auf TestContainers ausführen
Anstatt Ollama lokal installieren und ausführen zu müssen, können Sie Ollama in einem Container verwenden, der von TestContainers verwaltet wird.
TestContainers ist nicht nur für Tests nützlich, sondern kann auch zum Ausführen von Containern verwendet werden. Es gibt sogar ein spezielles OllamaContainer, das Sie nutzen können.
Hier ist das Gesamtbild:

Implementierung
Sehen wir uns GemmaWithOllamaContainer.java einmal genauer an.
Zuerst müssen Sie einen abgeleiteten Ollama-Container erstellen, in dem das Gemma-Modell abgerufen wird. Dieses Image ist entweder bereits aus einem vorherigen Lauf vorhanden oder wird erstellt. Wenn das Image bereits vorhanden ist, teilen Sie TestContainers einfach mit, dass Sie das Standard-Ollama-Image durch Ihre Gemma-basierte Variante ersetzen möchten:
private static final String TC_OLLAMA_GEMMA3 = "tc-ollama-gemma3-1b";
public static final String GEMMA_3 = "gemma3:1b";
// Creating an Ollama container with Gemma 3 if it doesn't exist.
private static OllamaContainer createGemmaOllamaContainer() throws IOException, InterruptedException {
// Check if the custom Gemma Ollama image exists already
List<Image> listImagesCmd = DockerClientFactory.lazyClient()
.listImagesCmd()
.withImageNameFilter(TC_OLLAMA_GEMMA3)
.exec();
if (listImagesCmd.isEmpty()) {
System.out.println("Creating a new Ollama container with Gemma 3 image...");
OllamaContainer ollama = new OllamaContainer("ollama/ollama:0.7.1");
System.out.println("Starting Ollama...");
ollama.start();
System.out.println("Pulling model...");
ollama.execInContainer("ollama", "pull", GEMMA_3);
System.out.println("Committing to image...");
ollama.commitToImage(TC_OLLAMA_GEMMA3);
return ollama;
}
System.out.println("Ollama image substitution...");
// Substitute the default Ollama image with our Gemma variant
return new OllamaContainer(
DockerImageName.parse(TC_OLLAMA_GEMMA3)
.asCompatibleSubstituteFor("ollama/ollama"));
}
Als Nächstes erstellen und starten Sie einen Ollama-Testcontainer und erstellen dann ein Ollama-Chatmodell, indem Sie auf die Adresse und den Port des Containers mit dem Modell verweisen, das Sie verwenden möchten. Rufen Sie abschließend model.generate(yourPrompt) wie gewohnt auf:
public static void main(String[] args) throws IOException, InterruptedException {
OllamaContainer ollama = createGemmaOllamaContainer();
ollama.start();
ChatLanguageModel model = OllamaChatModel.builder()
.baseUrl(String.format("http://%s:%d", ollama.getHost(), ollama.getFirstMappedPort()))
.modelName(GEMMA_3)
.build();
String response = model.generate("Why is the sky blue?");
System.out.println(response);
}
Führen Sie den Befehl so aus:
./gradlew run -q -DjavaMainClass=gemini.workshop.GemmaWithOllamaContainer
Beim ersten Ausführen dauert es eine Weile, bis der Container erstellt und ausgeführt wird. Danach sollte Gemma antworten:
INFO: Container ollama/ollama:0.7.1 started in PT7.228339916S
The sky appears blue due to Rayleigh scattering. Rayleigh scattering is a phenomenon that occurs when sunlight interacts with molecules in the Earth's atmosphere.
* **Scattering particles:** The main scattering particles in the atmosphere are molecules of nitrogen (N2) and oxygen (O2).
* **Wavelength of light:** Blue light has a shorter wavelength than other colors of light, such as red and yellow.
* **Scattering process:** When blue light interacts with these molecules, it is scattered in all directions.
* **Human eyes:** Our eyes are more sensitive to blue light than other colors, so we perceive the sky as blue.
This scattering process results in a blue appearance for the sky, even though the sun is actually emitting light of all colors.
In addition to Rayleigh scattering, other atmospheric factors can also influence the color of the sky, such as dust particles, aerosols, and clouds.
Sie haben Gemma in Cloud Shell ausgeführt.
14. Glückwunsch
Sie haben Ihre erste generative KI-Chatanwendung in Java mit LangChain4j und der Gemini API erstellt. Sie haben festgestellt, dass multimodale Large Language Models sehr leistungsstark sind und verschiedene Aufgaben wie Fragen und Antworten, auch in Ihrer eigenen Dokumentation, Datenextraktion und Interaktion mit externen APIs bewältigen können.
Nächste Schritte
Jetzt sind Sie an der Reihe, Ihre Anwendungen mit leistungsstarken LLM-Integrationen zu optimieren.
Weitere Informationen
- Häufige Anwendungsfälle für generative KI
- Schulungsressourcen zu generativer KI
- Mit Gemini über Generative AI Studio interagieren
- Verantwortungsbewusste Anwendung von KI