Auf generativer KI basierender Chat mit Nutzern und Dokumenten in Java über PaLM und LangChain4J

1. Einführung

Aktualisiert: 05.02.2024

Was ist generative KI?

Bei der generativen KI (der generativen künstlichen Intelligenz) wird KI verwendet, um neue Inhalte wie Text, Bilder, Musik, Audio und Videos zu erstellen.

Die Generative AI basiert auf Fundamentmodellen (große KI-Modelle), die Multitaskingfähig sind und vordefinierte Aufgaben ausführen, darunter Zusammenfassungen, Fragen-/Antworten-Sessions, Klassifizierung und mehr. Außerdem können mit nur minimalem Training Fundamentmodelle an ausgewählte Anwendungsfälle mit sehr wenigen Beispieldaten angepasst werden.

Wie funktioniert generative KI?

Generative KI nutzt ein ML-Modell (Machine Learning), 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 und mit denselben Labels gekennzeichnet sind.

Was sind gängige Anwendungen für generative KI?

Generative AI verarbeitet umfangreiche Inhalte und stellt über Texte, Bilder und nutzerfreundliche Formate Erkenntnisse und Antworten bereit. 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 erhalten, wie der Beantwortung von Angebotsanfragen, der Lokalisierung von Marketinginhalten in fünf Sprachen, der Prüfung von Kundenverträgen auf Compliance, etc.

Welche Angebote für generative KI hat Google Cloud?

Mit Vertex AI können Sie Grundmodelle verwenden, anpassen und in Ihre Anwendungen einbetten – ganz ohne ML-Fachwissen. Greifen Sie über Model Garden auf Fundamentmodelle zu, passen Sie die Modelle über eine einfache Benutzeroberfläche in Generative AI Studio an oder verwenden Sie Modelle in einem Data-Science-Notebook.

Vertex AI Search and Conversation bietet Entwicklern die schnellste Möglichkeit, generative KI-gestützte Suchmaschinen und Chatbots zu erstellen.

Duet AI ist ein KI-gestütztes Tool, das in Google Cloud und IDEs genutzt werden kann, um in kürzerer Zeit mehr zu erledigen.

Worum geht es in diesem Codelab?

In diesem Codelab geht es um das Large Language Model (LLM) PaLM 2, das in Google Cloud Vertex AI gehostet wird und alle Produkte und Dienste für maschinelles Lernen umfasst.

Sie verwenden Java, um mit der PaLM API zu interagieren, in Verbindung mit dem LangChain4J LLM-Framework-Orchestrator. Sie sehen sich verschiedene konkrete Beispiele an, um das LLM für die Beantwortung von Fragen, die Ideengenerierung, die Extraktion von Entitäten und strukturierten Inhalten sowie die Zusammenfassung zu nutzen.

Erzähl mir mehr über das LangChain4J-Framework.

Das Framework LangChain4J ist eine Open-Source-Bibliothek zum Einbinden von Large Language Models in Ihre Java-Anwendungen. Dazu werden verschiedene Komponenten wie das LLM selbst, aber auch andere Tools wie Vektordatenbanken (für semantische Suchanfragen), Dokumentlader und ‑splitter (zum Analysieren und Lernen aus Dokumenten) sowie Ausgabeparser orchestriert.

c6d7f7c3fd0d2951.png

Lerninhalte

  • Ein Java-Projekt für die Verwendung von PaLM und LangChain4J einrichten
  • Nützliche Informationen aus unstrukturierten Inhalten extrahieren (Entitäts- oder Keyword-Extraktion, Ausgabe in JSON)
  • Unterhaltungen mit Nutzern erstellen
  • Chatmodell verwenden, um Fragen zu Ihrer eigenen Dokumentation zu stellen

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

  1. Melden Sie sich in der Google Cloud Console an und erstellen Sie ein neues Projekt oder verwenden Sie ein vorhandenes Projekt. Wenn Sie noch kein Gmail- oder Google Workspace-Konto haben, müssen Sie eines erstellen.

295004821bab6a87.png

37d264871000675d.png

96d86d3d5655cdbe.png

  • 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_ID angegeben). 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
  1. 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

  1. Klicken Sie in der Cloud Console auf Cloud Shell aktivieren d1264ca30785e435.png.

cb81e7c8e34bc8d.png

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

d95252b003979716.png

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

7833d5e1c5d18f54.png

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.

  1. 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`
  1. 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 Code-Editor, um Ihre Java-Programme zu entwickeln.

Vertex AI APIs aktivieren

  1. 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.
  2. Wenn Sie sich nicht im Bereich „Vertex AI“ der Google Cloud Console befinden, gehen Sie so vor:
  3. Geben Sie unter Suche Vertex AI ein und drücken Sie die Eingabetaste.
  4. Klicken Sie in den Suchergebnissen auf Vertex AI. Das Vertex AI-Dashboard wird angezeigt.
  5. 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, die Sie auch über die Befehlszeile im Cloud Shell-Terminal mit dem folgenden Befehl aktivieren können:

$ gcloud services enable aiplatform.googleapis.com

Projektstruktur mit Gradle erstellen

Zum Erstellen Ihrer Java-Codebeispiele verwenden Sie das Build-Tool Gradle und Java 17. Wenn Sie Ihr Projekt mit Gradle einrichten möchten, erstellen Sie im Cloud Shell-Terminal ein Verzeichnis (hier palm-workshop) und führen Sie den Befehl gradle init in diesem Verzeichnis aus:

$ mkdir palm-workshop
$ cd palm-workshop

$ gradle init

Select type of project to generate:
  1: basic
  2: application
  3: library
  4: Gradle plugin
Enter selection (default: basic) [1..4] 2

Select implementation language:
  1: C++
  2: Groovy
  3: Java
  4: Kotlin
  5: Scala
  6: Swift
Enter selection (default: Java) [1..6] 3

Split functionality across multiple subprojects?:
  1: no - only one application project
  2: yes - application and library projects
Enter selection (default: no - only one application project) [1..2] 1

Select build script DSL:
  1: Groovy
  2: Kotlin
Enter selection (default: Groovy) [1..2] 1

Generate build using new APIs and behavior (some features may change in the next minor release)? (default: no) [yes, no] 

Select test framework:
  1: JUnit 4
  2: TestNG
  3: Spock
  4: JUnit Jupiter
Enter selection (default: JUnit Jupiter) [1..4] 4

Project name (default: palm-workshop): 
Source package (default: palm.workshop): 

> Task :init
Get more help with your project: https://docs.gradle.org/7.4/samples/sample_building_java_applications.html

BUILD SUCCESSFUL in 51s
2 actionable tasks: 2 executed

Sie erstellen eine Anwendung (Option 2) in der Java-Sprache (Option 3) ohne Unterprojekte (Option 1) mit der Groovy-Syntax für die Build-Datei (Option 1). Sie verwenden keine neuen Build-Funktionen (Option 0) und generieren Tests mit JUnit Jupiter (Option 4). Als Projektname können Sie palm-workshop und als Quellpaket palm.workshop verwenden.

Die Projektstruktur sieht so aus:

├── gradle 
│   └── ...
├── gradlew 
├── gradlew.bat 
├── settings.gradle 
└── app
    ├── build.gradle 
    └── src
        ├── main
        │   └── java 
        │       └── palm
        │           └── workshop
        │               └── App.java
        └── test
            └── ...

Aktualisieren wir die Datei app/build.gradle, um einige erforderliche Abhängigkeiten hinzuzufügen. Sie können die guava-Abhängigkeit entfernen, falls sie vorhanden ist, und durch die Abhängigkeiten für das LangChain4J-Projekt und die Logging-Bibliothek ersetzen, um fehlende Logger-Meldungen zu vermeiden:

dependencies {
    // Use JUnit Jupiter for testing.
    testImplementation 'org.junit.jupiter:junit-jupiter:5.8.1'

    // Logging library
    implementation 'org.slf4j:slf4j-jdk14:2.0.9'

    // This dependency is used by the application.
    implementation 'dev.langchain4j:langchain4j-vertex-ai:0.24.0'
    implementation 'dev.langchain4j:langchain4j:0.24.0'
}

Es gibt zwei Abhängigkeiten für LangChain4J:

  • eines für das Kernprojekt,
  • und eines für das dedizierte Vertex AI-Modul.

Wenn Sie Java 17 zum Kompilieren und Ausführen Ihrer Programme verwenden möchten, fügen Sie den folgenden Block unter dem Block plugins {} ein:

java {
    toolchain {
        languageVersion = JavaLanguageVersion.of(17)
    }
}

Eine weitere Änderung: Aktualisieren Sie den application-Block von app/build.gradle, damit Nutzer die Hauptklasse überschreiben können, die in der Befehlszeile ausgeführt werden soll, wenn das Build-Tool aufgerufen wird:

application {
    mainClass = providers.systemProperty('javaMainClass')
                         .orElse('palm.workshop.App')
}

Um zu prüfen, ob Ihre Build-Datei bereit ist, Ihre Anwendung auszuführen, können Sie die Standard-Hauptklasse ausführen, die eine einfache Hello World!-Meldung ausgibt:

$ ./gradlew run -DjavaMainClass=palm.workshop.App

> Task :app:run
Hello World!

BUILD SUCCESSFUL in 3s
2 actionable tasks: 2 executed

Jetzt können Sie mit dem PaLM Large Language Text Model programmieren, indem Sie das LangChain4J-Projekt verwenden.

Die vollständige app/build.gradle-Build-Datei sollte jetzt so aussehen:

plugins {
    // Apply the application plugin to add support for building a CLI application in Java.
    id 'application'
}

java {
    toolchain {
        // Ensure we compile and run on Java 17
        languageVersion = JavaLanguageVersion.of(17)
    }
}

repositories {
    // Use Maven Central for resolving dependencies.
    mavenCentral()
}

dependencies {
    // Use JUnit Jupiter for testing.
    testImplementation 'org.junit.jupiter:junit-jupiter:5.8.1'

    // This dependency is used by the application.
    implementation 'dev.langchain4j:langchain4j-vertex-ai:0.24.0'
    implementation 'dev.langchain4j:langchain4j:0.24.0'
    implementation 'org.slf4j:slf4j-jdk14:2.0.9'
}

application {
    mainClass = providers.systemProperty('javaMainClass').orElse('palm.workshop.App')
}

tasks.named('test') {
    // Use JUnit Platform for unit tests.
    useJUnitPlatform()
}

4. Ersten Aufruf des Chatmodells von PaLM erstellen

Nachdem das Projekt richtig eingerichtet ist, können Sie die PaLM API aufrufen.

Erstellen Sie im Verzeichnis app/src/main/java/palm/workshop (neben der Standardklasse App.java) eine neue Klasse mit dem Namen ChatPrompts.java und geben Sie den folgenden Inhalt ein:

package palm.workshop;

import dev.langchain4j.model.vertexai.VertexAiChatModel;
import dev.langchain4j.chain.ConversationalChain;

public class ChatPrompts {
    public static void main(String[] args) {
        VertexAiChatModel model = VertexAiChatModel.builder()
            .endpoint("us-central1-aiplatform.googleapis.com:443")
            .project("YOUR_PROJECT_ID")
            .location("us-central1")
            .publisher("google")
            .modelName("chat-bison@001")
            .maxOutputTokens(400)
            .maxRetries(3)
            .build();

        ConversationalChain chain = ConversationalChain.builder()
            .chatLanguageModel(model)
            .build();

        String message = "What are large language models?";
        String answer = chain.execute(message);
        System.out.println(answer);

        System.out.println("---------------------------");

        message = "What can you do with them?";
        answer = chain.execute(message);
        System.out.println(answer);

        System.out.println("---------------------------");

        message = "Can you name some of them?";
        answer = chain.execute(message);
        System.out.println(answer);
    }
}

In diesem ersten Beispiel müssen Sie die Klasse VertexAiChatModel und die LangChain4J-Klasse ConversationalChain importieren, um die Multiturn-Aspekte von Unterhaltungen zu vereinfachen.

Als Nächstes konfigurieren Sie in der Methode main das Chat-Sprachmodell. Dazu verwenden Sie den Builder für VertexAiChatModel, um Folgendes anzugeben:

  • den Endpunkt,
  • das Projekt,
  • die Region,
  • der Verlag,
  • und den Namen des Modells (chat-bison@001).

Nachdem das Sprachmodell bereit ist, können Sie ein ConversationalChain vorbereiten. Dies ist eine Abstraktion auf höherer Ebene, die von LangChain4j angeboten wird, um verschiedene Komponenten für die Verarbeitung einer Konversation zu konfigurieren, z. B. das Chat-Sprachmodell selbst, aber möglicherweise auch andere Komponenten für die Verarbeitung des Verlaufs der Chat-Konversation oder zum Einbinden anderer Tools wie Retriever, um Informationen aus Vektordatenbanken abzurufen. Aber keine Sorge, wir kommen später in diesem Codelab darauf zurück.

Anschließend führen Sie eine Multi-Turn Conversation mit dem Chatmodell durch, um mehrere zusammenhängende Fragen zu stellen. Zuerst fragen Sie sich, was LLMs sind, dann, was Sie damit tun können, und schließlich, was einige Beispiele dafür sind. Sie müssen sich nicht wiederholen. Das LLM weiß, dass sich „sie“ im Kontext dieser Unterhaltung auf LLMs bezieht.

Um diese wechselseitige Unterhaltung zu führen, rufen Sie einfach die Methode execute() für die Kette auf. Sie wird dem Kontext der Unterhaltung hinzugefügt, das Chatmodell generiert eine Antwort und fügt sie ebenfalls dem Chatverlauf hinzu.

Führen Sie zum Ausführen dieser Klasse im Cloud Shell-Terminal den folgenden Befehl aus:

./gradlew run -DjavaMainClass=palm.workshop.ChatPrompts

Die Ausgabe sollte in etwa so aussehen:

$ ./gradlew run -DjavaMainClass=palm.workshop.ChatPrompts
Starting a Gradle Daemon, 2 incompatible and 2 stopped Daemons could not be reused, use --status for details

> Task :app:run
Large language models (LLMs) are artificial neural networks that are trained on massive datasets of text and code. They are designed to understand and generate human language, and they can be used for a variety of tasks, such as machine translation, question answering, and text summarization.
---------------------------
LLMs can be used for a variety of tasks, such as:

* Machine translation: LLMs can be used to translate text from one language to another.
* Question answering: LLMs can be used to answer questions posed in natural language.
* Text summarization: LLMs can be used to summarize text into a shorter, more concise form.
* Code generation: LLMs can be used to generate code, such as Python or Java code.
* Creative writing: LLMs can be used to generate creative text, such as poems, stories, and scripts.

LLMs are still under development, but they have the potential to revolutionize a wide range of industries. For example, LLMs could be used to improve customer service, create more personalized marketing campaigns, and develop new products and services.
---------------------------
Some of the most well-known LLMs include:

* GPT-3: Developed by OpenAI, GPT-3 is a large language model that can generate text, translate languages, write different kinds of creative content, and answer your questions in an informative way.
* LaMDA: Developed by Google, LaMDA is a large language model that can chat with you in an open-ended way, answering your questions, telling stories, and providing different kinds of creative content.
* PaLM 2: Developed by Google, PaLM 2 is a large language model that can perform a wide range of tasks, including machine translation, question answering, and text summarization.
* T5: Developed by Google, T5 is a large language model that can be used for a variety of tasks, including text summarization, question answering, and code generation.

These are just a few examples of the many LLMs that are currently being developed. As LLMs continue to improve, they are likely to play an increasingly important role in our lives.

BUILD SUCCESSFUL in 25s
2 actionable tasks: 2 executed

PaLM hat auf Ihre drei ähnlichen Fragen geantwortet.

Mit dem VertexAIChatModel-Builder können Sie optionale Parameter definieren, für die bereits Standardwerte festgelegt sind, die Sie überschreiben können. Hier einige Beispiele:

  • .temperature(0.2): 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 1 kreativere Ausgaben erzielt werden.
  • .maxOutputTokens(50) – im Beispiel wurden 400 Tokens angefordert (3 Tokens entsprechen etwa 4 Wörtern), je nachdem, wie lang die generierte Antwort sein soll.
  • .topK(20): Damit wird ein Wort aus einer maximalen Anzahl wahrscheinlicher Wörter für die Vervollständigung des Texts zufällig ausgewählt (1 bis 40).
  • .topP(0.95) – um die möglichen Wörter auszuwählen, deren Gesamtwahrscheinlichkeit dieser Gleitkommazahl (zwischen 0 und 1) entspricht.
  • .maxRetries(3): Wenn Sie das Kontingent für Anfragen pro Zeit überschreiten, kann das Modell den Aufruf beispielsweise dreimal wiederholen.

5. Ein nützlicher Chatbot mit Persönlichkeit!

Im vorherigen Abschnitt haben Sie dem LLM-Chatbot direkt Fragen gestellt, ohne ihm einen bestimmten Kontext zu geben. Sie können einen solchen Chatbot jedoch so spezialisieren, dass er ein Experte für eine bestimmte Aufgabe oder ein bestimmtes Thema wird.

Wie gehen Sie dabei vor? Indem Sie die Aufgabe erläutern, den Kontext beschreiben, vielleicht einige Beispiele für die Aufgabe geben, die Rolle des Modells festlegen, das Format der Antworten angeben und möglicherweise einen Ton vorgeben, wenn der Chatbot sich auf eine bestimmte Weise verhalten soll.

Dieser Ansatz wird in diesem Artikel zum Erstellen von Prompts mit diesem Bild veranschaulicht:

8a4c67679dcbd085.png

https://medium.com/@eldatero/master-the-perfect-chatgpt-prompt-formula-c776adae8f19

Zur Veranschaulichung dieses Punktes können wir uns von den Websites prompts.chat inspirieren lassen, auf denen viele tolle und lustige Ideen für benutzerdefinierte Chatbots aufgeführt sind, die als Folgendes fungieren können:

Hier ist ein Beispiel dafür, wie Sie einen LLM-Chatbot in einen Schachspieler verwandeln können. Lass uns das umsetzen!

Aktualisieren Sie die Klasse ChatPrompts so:

package palm.workshop;

import dev.langchain4j.chain.ConversationalChain;
import dev.langchain4j.data.message.SystemMessage;
import dev.langchain4j.memory.chat.MessageWindowChatMemory;
import dev.langchain4j.model.vertexai.VertexAiChatModel;
import dev.langchain4j.store.memory.chat.InMemoryChatMemoryStore;

public class ChatPrompts {
    public static void main(String[] args) {
        VertexAiChatModel model = VertexAiChatModel.builder()
            .endpoint("us-central1-aiplatform.googleapis.com:443")
            .project("YOUR_PROJECT_ID")
            .location("us-central1")
            .publisher("google")
            .modelName("chat-bison@001")
            .maxOutputTokens(7)
            .maxRetries(3)
            .build();

        InMemoryChatMemoryStore chatMemoryStore = new InMemoryChatMemoryStore();

        MessageWindowChatMemory chatMemory = MessageWindowChatMemory.builder()
            .chatMemoryStore(chatMemoryStore)
            .maxMessages(200)
            .build();

        chatMemory.add(SystemMessage.from("""
            You're an expert chess player with a high ELO ranking.
            Use the PGN chess notation to reply with the best next possible move.
            """
        ));


        ConversationalChain chain = ConversationalChain.builder()
            .chatLanguageModel(model)
            .chatMemory(chatMemory)
            .build();

        String pgn = "";
        String[] whiteMoves = { "Nf3", "c4", "Nc3", "e3", "Dc2", "Cd5"};
        for (int i = 0; i < whiteMoves.length; i++) {
            pgn += " " + (i+1) + ". " + whiteMoves[i];
            System.out.println("Playing " + whiteMoves[i]);
            pgn = chain.execute(pgn);
            System.out.println(pgn);
        }
    }
}

Sehen wir uns das mal Schritt für Schritt an:

  • Es sind einige neue Importe erforderlich, um den Speicher des Chats zu verwalten.
  • Sie instanziieren das Chatmodell, aber mit einer geringen Anzahl von maximalen Tokens (hier 7), da wir nur den nächsten Zug generieren möchten, nicht eine ganze Abhandlung über Schach.
  • Als Nächstes erstellen Sie einen Chat-Speicher, um die Chatunterhaltungen zu speichern.
  • Sie erstellen einen tatsächlichen Chat-Speicher mit Fenstern, um die letzten Züge beizubehalten.
  • Im Chat-Gedächtnis fügen Sie eine „System“-Nachricht hinzu, in der Sie dem Chatmodell mitteilen, wer es sein soll, z. B. ein Schachprofi. Die „System“-Nachricht fügt etwas Kontext hinzu, während „Nutzer“- und „KI“-Nachrichten die eigentliche Diskussion darstellen.
  • Sie erstellen eine Unterhaltungskette, in der das Gedächtnis und das Chatmodell kombiniert werden.
  • Dann haben wir eine Liste mit Zügen für Weiß, die Sie durchlaufen. Die Kette wird jedes Mal mit dem nächsten Zug von Weiß ausgeführt und das Chatmodell antwortet mit dem nächstbesten Zug.

Wenn Sie diese Klasse mit diesen Zügen ausführen, sollte die folgende Ausgabe angezeigt werden:

$ ./gradlew run -DjavaMainClass=palm.workshop.ChatPrompts
Starting a Gradle Daemon (subsequent builds will be faster)

> Task :app:run
Playing Nf3
1... e5
Playing c4
2... Nc6
Playing Nc3
3... Nf6
Playing e3
4... Bb4
Playing Dc2
5... O-O
Playing Cd5
6... exd5 

Wow! Kann PaLM Schach spielen? Nicht ganz, aber während des Trainings muss das Modell einige Schachspielkommentare oder sogar die PGN-Dateien (Portable Game Notation) vergangener Spiele gesehen haben. Dieser Chatbot wird wahrscheinlich nicht gegen AlphaZero gewinnen (die KI, die die besten Go-, Shogi- und Schachspieler besiegt), und die Unterhaltung könnte im weiteren Verlauf entgleisen, da sich das Modell nicht wirklich an den tatsächlichen Zustand des Spiels erinnert.

Chatmodelle sind sehr leistungsstark und können umfangreiche Interaktionen mit Ihren Nutzern ermöglichen und verschiedene kontextbezogene Aufgaben erledigen. Im nächsten Abschnitt sehen wir uns eine nützliche Aufgabe an: strukturierte Daten aus Text extrahieren.

6. Informationen aus unstrukturiertem Text extrahieren

Im vorherigen Abschnitt haben Sie Unterhaltungen zwischen einem Nutzer und einem Chat-Sprachmodell erstellt. Mit LangChain4J können Sie jedoch auch ein Chatmodell verwenden, um strukturierte Informationen aus unstrukturiertem Text zu extrahieren.

Angenommen, Sie möchten den Namen und das Alter einer Person anhand einer Biografie oder Beschreibung dieser Person extrahieren. Sie können das große Sprachmodell anweisen, JSON-Datenstrukturen mit einem geschickt formulierten Prompt zu generieren. Dies wird häufig als Prompt-Engineering bezeichnet.

Sie aktualisieren die Klasse ChatPrompts so:

package palm.workshop;

import dev.langchain4j.model.vertexai.VertexAiChatModel;
import dev.langchain4j.service.AiServices;
import dev.langchain4j.service.UserMessage;

public class ChatPrompts {

    static class Person {
        String name;
        int age;
    }

    interface PersonExtractor {
        @UserMessage("""
            Extract the name and age of the person described below.
            Return a JSON document with a "name" and an "age" property, \
            following this structure: {"name": "John Doe", "age": 34}
            Return only JSON, without any markdown markup surrounding it.
            Here is the document describing the person:
            ---
            {{it}}
            ---
            JSON: 
            """)
        Person extractPerson(String text);
    }

    public static void main(String[] args) {
        VertexAiChatModel model = VertexAiChatModel.builder()
            .endpoint("us-central1-aiplatform.googleapis.com:443")
            .project("YOUR_PROJECT_ID")
            .location("us-central1")
            .publisher("google")
            .modelName("chat-bison@001")
            .maxOutputTokens(300)
            .build();
        
        PersonExtractor extractor = AiServices.create(PersonExtractor.class, model);

        Person person = extractor.extractPerson("""
            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.    
            """
        );

        System.out.println(person.name);
        System.out.println(person.age);
    }
}

Sehen wir uns die verschiedenen Schritte in dieser Datei an:

  • Eine Person-Klasse wird definiert, um die Details einer Person (Name und Alter) darzustellen.
  • Die PersonExtractor-Schnittstelle wird mit einer Methode erstellt, die bei einem unstrukturierten Textstring eine instanziierte Person-Instanz zurückgibt.
  • Die extractPerson() ist mit einer @UserMessage-Anmerkung versehen, die einen Prompt 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, das für Sie geparst und in eine Person-Instanz entpackt wird.

Sehen wir uns nun den Inhalt der Methode main() an:

  • Das Chatmodell wird instanziiert.
  • Ein PersonExtractor-Objekt wird mit der Klasse AiServices von 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 eine Person-Instanz mit dem Namen und Alter zurückzugeben.

Führen Sie diese Klasse nun mit dem folgenden Befehl aus:

$ ./gradlew run -DjavaMainClass=palm.workshop.ChatPrompts

> Task :app:run
Anna
23

Ja. Das ist Anna, sie ist 23 Jahre alt.

Besonders interessant an diesem AiServices-Ansatz ist, dass Sie mit stark typisierten Objekten arbeiten. Sie interagieren nicht direkt mit dem Chat-LLM. Stattdessen arbeiten Sie mit konkreten Klassen wie der Klasse „Person“, um die extrahierten personenbezogenen Daten darzustellen. Außerdem haben Sie eine PersonExtractor-Klasse mit einer extractPerson()-Methode, die eine „Person“-Instanz zurückgibt. Das Konzept von LLM wird abstrahiert und als Java-Entwickler bearbeiten Sie nur normale Klassen und Objekte.

7. Retrieval-Augmented Generation: Mit Ihren Dokumenten chatten

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 Auszügen Ihrer Dokumente abrufen kann. Diese Informationen werden vom Modell verwendet, um seine Antworten zu fundieren, anstatt Antworten aus seinem Training zu generieren. Dieses Muster wird als RAG oder Retrieval-Augmented Generation bezeichnet.

Bei Retrieval Augmented Generation gibt es im Wesentlichen zwei Phasen:

  1. Aufnahmephase: Dokumente werden geladen, in kleinere Blöcke aufgeteilt und eine vektorielle Darstellung (eine Vektoreinbettung) wird in einer Vektordatenbank gespeichert, die semantische Suchvorgänge ausführen kann.

6c5bb5cb2e3b8088.png

  1. Anfragephase: Nutzer können Ihrem Chatbot jetzt Fragen zur Dokumentation 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 der Unterhaltung und die Textausschnitte, die den von der Datenbank zurückgegebenen Vektoren entsprechen. Es wird aufgefordert, seine Antwort auf diese Ausschnitte zu stützen.

2c279c506d7606cd.png

Dokumente vorbereiten

In dieser neuen Demo stellen Sie Fragen zur Transformer-Architektur für neuronale Netze, die von Google entwickelt wurde und auf der alle modernen Large Language Models basieren.

Sie können das Paper, in dem diese Architektur beschrieben wird („Attention is all you need“), mit dem Befehl wget als PDF aus dem Internet herunterladen:

wget -O attention-is-all-you-need.pdf \
    https://proceedings.neurips.cc/paper_files/paper/2017/file/3f5ee243547dee91fbd053c1c4a845aa-Paper.pdf

Konversationelle Abrufkette implementieren

Sehen wir uns Schritt für Schritt an, wie der zweiphasige Ansatz aufgebaut wird – zuerst mit der Dokumentaufnahme und dann mit der Abfragezeit, wenn Nutzer Fragen zum Dokument stellen.

Dokumentaufnahme

Der erste Schritt der Dokumentaufnahmephase besteht darin, die PDF-Datei zu finden, die wir herunterladen möchten, und ein PdfParser vorzubereiten, um sie zu lesen:

PdfDocumentParser pdfParser = new PdfDocumentParser();
Document document = pdfParser.parse(
    new FileInputStream(new File("/home/YOUR_USER_NAME/palm-workshop/attention-is-all-you-need.pdf")));

Anstatt das übliche Chat-Sprachmodell zu erstellen, erstellen Sie zuerst eine Instanz eines Einbettungsmodells. Dies ist ein bestimmtes Modell und ein bestimmter Endpunkt, dessen Aufgabe es ist, Vektordarstellungen von Textfragmenten (Wörtern, Sätzen oder sogar Absätzen) zu erstellen.

VertexAiEmbeddingModel embeddingModel = VertexAiEmbeddingModel.builder()
    .endpoint("us-central1-aiplatform.googleapis.com:443")
    .project("YOUR_PROJECT_ID")
    .location("us-central1")
    .publisher("google")
    .modelName("textembedding-gecko@001")
    .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);

EmbeddingStoreRetriever retriever = EmbeddingStoreRetriever.from(embeddingStore, embeddingModel);

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 „Ingestor“ des Speichers 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. Das übliche Chatmodell kann erstellt werden, um die Unterhaltung zu starten:

VertexAiChatModel model = VertexAiChatModel.builder()
    .endpoint("us-central1-aiplatform.googleapis.com:443")
    .project("YOUR_PROJECT_ID")
    .location("us-central1")
    .publisher("google")
    .modelName("chat-bison@001")
    .maxOutputTokens(1000)
    .build();

Sie benötigen auch eine Retriever-Klasse, die die Vektordatenbank (in der Variablen embeddingStore) und das Einbettungsmodell verknüpft. Die Aufgabe besteht darin, die Vektordatenbank abzufragen, indem eine Vektoreinbettung für die Nutzeranfrage berechnet wird, um ähnliche Vektoren in der Datenbank zu finden:

EmbeddingStoreRetriever retriever = 
    EmbeddingStoreRetriever.from(embeddingStore, embeddingModel);

An diesem Punkt können Sie die ConversationalRetrievalChain-Klasse instanziieren. Das ist nur ein anderer Name für das Retrieval Augmented Generation-Muster:

ConversationalRetrievalChain rag = ConversationalRetrievalChain.builder()
    .chatLanguageModel(model)
    .retriever(retriever)
    .promptTemplate(PromptTemplate.from("""
        Answer to the following query the best as you can: {{question}}
        Base your answer on the information provided below:
        {{information}}
        """
    ))
    .build();

Diese „Kette“ verbindet:

  • Das Modell für die Chatsprache, das Sie zuvor konfiguriert haben.
  • Der Retriever vergleicht eine Vektoreinbettungsanfrage mit den Vektoren in der Datenbank.
  • In einer Prompt-Vorlage wird explizit angegeben, dass das Chatmodell seine Antwort auf die bereitgestellten Informationen stützen soll, d.h. auf die relevanten Auszüge der Dokumentation, deren Vektoreinbettung dem Vektor der Frage des Nutzers ähnelt.

Jetzt können Sie endlich Ihre Fragen stellen.

String result = rag.execute("What neural network architecture can be used for language models?");
System.out.println(result);
System.out.println("------------");

result = rag.execute("What are the different components of a transformer neural network?");
System.out.println(result);
System.out.println("------------");

result = rag.execute("What is attention in large language models?");
System.out.println(result);
System.out.println("------------");

result = rag.execute("What is the name of the process that transforms text into vectors?");
System.out.println(result);

Führen Sie das Programm mit folgendem Befehl aus:

$ ./gradlew run -DjavaMainClass=palm.workshop.ChatPrompts

In der Ausgabe sollte die Antwort auf Ihre Fragen angezeigt werden:

The Transformer is a neural network architecture that can be used for 
language models. It is based solely on attention mechanisms, dispensing 
with recurrence and convolutions. The Transformer has been shown to 
outperform recurrent neural networks and convolutional neural networks on 
a variety of language modeling tasks.
------------
The Transformer is a neural network architecture that can be used for 
language models. It is based solely on attention mechanisms, dispensing 
with recurrence and convolutions. The Transformer has been shown to 
outperform recurrent neural networks and convolutional neural networks on a 
variety of language modeling tasks. The Transformer consists of an encoder 
and a decoder. The encoder is responsible for encoding the input sequence 
into a fixed-length vector representation. The decoder is responsible for 
decoding the output sequence from the input sequence. The decoder uses the 
attention mechanism to attend to different parts of the input sequence when 
generating the output sequence.
------------
Attention is a mechanism that allows a neural network to focus on specific 
parts of an input sequence. In the context of large language models, 
attention is used to allow the model to focus on specific words or phrases 
in a sentence when generating output. This allows the model to generate 
more relevant and informative output.
------------
The process of transforming text into vectors is called word embedding. 
Word embedding is a technique that represents words as vectors in a 
high-dimensional space. The vectors are typically learned from a large 
corpus of text, and they capture the semantic and syntactic relationships 
between words. Word embedding has been shown to be effective for a variety 
of natural language processing tasks, such as machine translation, question 
answering, and sentiment analysis.

Die Komplettlösung

Damit das Kopieren und Einfügen einfacher ist, finden Sie hier den vollständigen Inhalt der ChatPrompts-Klasse:

package palm.workshop;

import dev.langchain4j.chain.ConversationalRetrievalChain;
import dev.langchain4j.data.document.Document;
import dev.langchain4j.data.document.parser.PdfDocumentParser;
import dev.langchain4j.data.document.splitter.DocumentSplitters;
import dev.langchain4j.data.segment.TextSegment; 
import dev.langchain4j.model.input.PromptTemplate;
import dev.langchain4j.model.vertexai.VertexAiChatModel;
import dev.langchain4j.model.vertexai.VertexAiEmbeddingModel;
import dev.langchain4j.retriever.EmbeddingStoreRetriever;
import dev.langchain4j.store.embedding.EmbeddingStoreIngestor;
import dev.langchain4j.store.embedding.inmemory.InMemoryEmbeddingStore;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;

public class ChatPrompts {
    public static void main(String[] args) throws IOException {
        PdfDocumentParser pdfParser = new PdfDocumentParser();
        Document document = pdfParser.parse(new FileInputStream(new File("/ABSOLUTE_PATH/attention-is-all-you-need.pdf")));

        VertexAiEmbeddingModel embeddingModel = VertexAiEmbeddingModel.builder()
            .endpoint("us-central1-aiplatform.googleapis.com:443")
            .project("YOUR_PROJECT_ID")
            .location("us-central1")
            .publisher("google")
            .modelName("textembedding-gecko@001")
            .maxRetries(3)
            .build();

        InMemoryEmbeddingStore<TextSegment> embeddingStore = 
            new InMemoryEmbeddingStore<>();

        EmbeddingStoreIngestor storeIngestor = EmbeddingStoreIngestor.builder()
            .documentSplitter(DocumentSplitters.recursive(500, 100))
            .embeddingModel(embeddingModel)
            .embeddingStore(embeddingStore)
            .build();
        storeIngestor.ingest(document);

        EmbeddingStoreRetriever retriever = EmbeddingStoreRetriever.from(embeddingStore, embeddingModel);

        VertexAiChatModel model = VertexAiChatModel.builder()
            .endpoint("us-central1-aiplatform.googleapis.com:443")
            .project("genai-java-demos")
            .location("us-central1")
            .publisher("google")
            .modelName("chat-bison@001")
            .maxOutputTokens(1000)
            .build();

        ConversationalRetrievalChain rag = ConversationalRetrievalChain.builder()
            .chatLanguageModel(model)
            .retriever(retriever)
            .promptTemplate(PromptTemplate.from("""
                Answer to the following query the best as you can: {{question}}
                Base your answer on the information provided below:
                {{information}}
                """
            ))
            .build();

        String result = rag.execute("What neural network architecture can be used for language models?");
        System.out.println(result);
        System.out.println("------------");

        result = rag.execute("What are the different components of a transformer neural network?");
        System.out.println(result);
        System.out.println("------------");

        result = rag.execute("What is attention in large language models?");
        System.out.println(result);
        System.out.println("------------");

        result = rag.execute("What is the name of the process that transforms text into vectors?");
        System.out.println(result);
    }
}

8. Glückwunsch

Herzlichen Glückwunsch! Sie haben Ihre erste Chat-Anwendung mit generativer KI in Java mit LangChain4J und der PaLM API erstellt. Sie haben festgestellt, dass große Chatmodelle für Sprachen sehr leistungsstark sind und verschiedene Aufgaben wie Fragen/Antworten, auch in Ihrer eigenen Dokumentation, Datenextraktion und zum Teil sogar Schach spielen können.

Nächste Schritte

In den folgenden Codelabs finden Sie weitere Informationen zu PaLM in Java:

Weitere Informationen

Referenzdokumente