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

1. Einführung

Zuletzt 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 lassen sich Foundation Models bei minimalem Trainingsaufwand mit sehr wenigen Beispieldaten für gezielte Anwendungsfälle anpassen.

Wie funktioniert generative KI?

Generative KI nutzt ein ML-Modell (Machine Learning), um die Muster und Beziehungen in einem Dataset mit von Menschen erstellten Inhalten zu erlernen. Anschließend werden anhand der erlernten Muster neue Inhalte generiert.

Die gängigste Methode zum Trainieren eines Generative-AI-Modells ist das überwachte Lernen – das Modell erhält eine Reihe von von Menschen erstellten Inhalten und entsprechenden 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 Generative AI-Anwendungen?

Generative AI verarbeitet umfangreiche Inhalte und stellt über Texte, Bilder und nutzerfreundliche Formate Erkenntnisse und Antworten bereit. Mit generativer KI können Sie:

  • Bessere Kundeninteraktionen durch verbesserte Chat- und Suchfunktionen
  • Große Mengen unstrukturierter Daten über dialogorientierte Benutzeroberflächen und Zusammenfassungen untersuchen
  • Unterstützung bei sich wiederholenden Aufgaben wie dem Beantworten von Angebotsanfragen, der Lokalisierung von Marketinginhalten in fünf Sprachen und der Überprüfung von Kundenverträgen auf Compliance usw.

Welche generative KI-Angebote bietet Google Cloud?

Mit Vertex AI können Sie mit Foundation Models interagieren, sie anpassen und in Ihre Anwendungen einbetten – ohne oder mit nur wenig ML-Kenntnissen. In Model Garden können Sie auf Foundation Models zugreifen, sie über eine einfache Benutzeroberfläche in Generative AI Studio abstimmen oder sie in einem Data-Science-Notebook verwenden.

Vertex AI Search and Conversation bietet Entwicklern die schnellste Möglichkeit, auf generativer KI basierende Suchmaschinen und Chatbots zu erstellen.

Duet-KI ist Ihr KI-gestütztes Tool, mit dem Sie in Google Cloud und IDEs schneller mehr erledigen können.

Worum geht es in diesem Codelab?

Dieses Codelab konzentriert sich auf das PaLM 2 Large Language Model (LLM), das in Google Cloud Vertex AI gehostet wird und alle Produkte und Dienste für maschinelles Lernen umfasst.

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

Ich möchte mehr über das LangChain4J-Framework erfahren.

Das LangChain4J-Framework ist eine Open-Source-Bibliothek zur Integration von Large Language Models in Ihre Java-Anwendungen. Dabei werden verschiedene Komponenten wie das LLM selbst, aber auch andere Tools wie Vektordatenbanken (für semantische Suchen), Dokumentladeprogramme und Aufteilungen (zum Analysieren und Lernen von Dokumenten) sowie Ausgabeparser und mehr orchestriert.

c6d7f7c3fd0d2951.png

Aufgaben in diesem Lab

  • Java-Projekt für PaLM und LangChain4J einrichten
  • Nützliche Informationen aus unstrukturierten Inhalten extrahieren (Extraktion von Entitäten oder Suchbegriffen, Ausgabe im JSON-Format)
  • So erstellen Sie eine Unterhaltung mit Ihren Nutzern
  • Wie Sie mithilfe des Chatmodells Fragen zu Ihrer eigenen Dokumentation stellen

Voraussetzungen

  • Kenntnisse der Programmiersprache Java
  • Ein Google Cloud-Projekt
  • Ein Browser wie Chrome oder Firefox

2. Einrichtung und Anforderungen

Umgebung für das selbstbestimmte 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 Projektteilnehmer. Es handelt sich um eine Zeichenfolge, die von Google APIs nicht verwendet wird. Sie können sie jederzeit aktualisieren.
  • Die Projekt-ID ist für alle Google Cloud-Projekte eindeutig und unveränderlich. Sie kann nach dem Festlegen nicht mehr geändert werden. Die Cloud Console generiert automatisch einen eindeutigen String. ist Ihnen meist egal, was es ist. In den meisten Codelabs musst du auf deine Projekt-ID verweisen, die üblicherweise als PROJECT_ID bezeichnet wird. Wenn Ihnen die generierte ID nicht gefällt, können Sie eine weitere zufällige ID generieren. Alternativ können Sie einen eigenen verwenden und nachsehen, ob er verfügbar ist. Sie kann nach diesem Schritt nicht mehr geändert werden und bleibt für die Dauer des Projekts erhalten.
  • Zur Information gibt es noch einen dritten Wert, die Projektnummer, die von manchen APIs verwendet wird. Weitere Informationen zu allen drei Werten finden Sie in der Dokumentation.
  1. Als Nächstes müssen Sie in der Cloud Console die Abrechnung aktivieren, um Cloud-Ressourcen/APIs verwenden zu können. Dieses Codelab ist kostengünstig. Sie können die von Ihnen erstellten Ressourcen oder das Projekt löschen, um Ressourcen herunterzufahren, um zu vermeiden, dass über diese Anleitung hinaus Kosten anfallen. Neue Google Cloud-Nutzer haben Anspruch auf das kostenlose Testprogramm mit 300$Guthaben.

Cloud Shell starten

Sie können Google Cloud zwar von Ihrem Laptop aus der Ferne bedienen, in diesem Codelab verwenden Sie jedoch Cloud Shell, 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 Cloud Shell zum ersten Mal starten, wird ein Zwischenbildschirm mit einer Beschreibung der Funktion angezeigt. Wenn ein Zwischenbildschirm angezeigt wird, klicken Sie auf Weiter.

d95252b003979716.png

Die Bereitstellung und Verbindung mit Cloud Shell dauert nur einen Moment.

7833d5e1c5d18f54.png

Diese virtuelle Maschine verfügt über alle erforderlichen Entwicklertools. Es bietet ein Basisverzeichnis mit 5 GB nichtflüchtigem Speicher und wird in Google Cloud ausgeführt. Dadurch werden die Netzwerkleistung und die Authentifizierung erheblich verbessert. Viele, wenn nicht sogar alle Arbeiten in diesem Codelab können mit einem Browser erledigt werden.

Sobald Sie mit Cloud Shell verbunden sind, sollten Sie sehen, dass Sie authentifiziert sind und das Projekt auf Ihre Projekt-ID eingestellt ist.

  1. Führen Sie in 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 in Cloud Shell den folgenden Befehl aus, um zu prüfen, ob 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, ob der Projektname in der Google Cloud Console oben in der Google Cloud Console angezeigt wird. Ist dies nicht der Fall, klicken Sie auf Projekt auswählen, um die Projektauswahl zu öffnen und das gewünschte Projekt auszuwählen.
  2. Wenn Sie sich nicht im Vertex AI-Bereich der Google Cloud Console befinden, gehen Sie so vor:
  3. Geben Sie unter Suche „Vertex AI“ ein und kehren Sie dann zurück
  4. Klicken Sie in den Suchergebnissen auf Vertex AI. Das Vertex AI-Dashboard wird angezeigt.
  5. Klicken Sie im Vertex AI-Dashboard auf Enable All Recommended APIs (Alle empfohlenen APIs aktivieren).

Dadurch werden mehrere APIs aktiviert. Die wichtigste für das Codelab ist jedoch das aiplatform.googleapis.com. Sie können es auch in der Befehlszeile im Cloud Shell-Terminal aktivieren, indem Sie den folgenden Befehl ausführen:

$ gcloud services enable aiplatform.googleapis.com

Projektstruktur mit Gradle erstellen

Für Java-Codebeispiele verwenden Sie das Build-Tool Gradle und Version 17 von Java. Um Ihr Projekt mit Gradle einzurichten, 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) mit der Java-Sprache (Option 3), ohne Unterprojekte (Option 1), mit der Groovy-Syntax für die Build-Datei (Option 1), verwenden Sie keine neuen Build-Funktionen (Option 4), generieren Sie Tests mit JUnit Jupiter (Option 4) und verwenden Sie für den Projektnamen palm-workshop.Ähnlich können Sie 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
            └── ...

Wir aktualisieren die Datei app/build.gradle, um einige erforderliche Abhängigkeiten hinzuzufügen. Sie können die guava-Abhängigkeit, sofern vorhanden, entfernen und durch die Abhängigkeiten für das LangChain4J-Projekt sowie die Logging-Bibliothek ersetzen, um zu vermeiden, dass bei fehlenden Protokollierungsmeldungen lästige Probleme auftreten:

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'
}

Für LangChain4J gibt es zwei Abhängigkeiten:

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

Um Java 17 zum Kompilieren und Ausführen unserer Programme zu verwenden, fügen Sie den folgenden Block unter dem Block plugins {} ein:

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

Eine weitere Änderung ist erforderlich: Aktualisieren Sie den application-Block von app/build.gradle, damit Nutzer die in der Befehlszeile ausgeführte Hauptklasse überschreiben können, wenn sie das Build-Tool aufrufen:

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

Wenn Sie prüfen möchten, ob die Build-Datei zur Ausführung Ihrer Anwendung bereit ist, können Sie die Standardhauptklasse ausführen, die eine einfache Hello World!-Nachricht ausgibt:

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

> Task :app:run
Hello World!

BUILD SUCCESSFUL in 3s
2 actionable tasks: 2 executed

Jetzt sind Sie bereit, mit dem Large Language Model „PaLM“ mithilfe des LangChain4J-Projekts zu programmieren.

So sollte die vollständige app/build.gradle-Build-Datei jetzt 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. Das Chatmodell von PaLM zum ersten Mal aufrufen

Nachdem das Projekt nun ordnungsgemäß 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 den Multiturn-Aspekt von Konversationen zu erleichtern.

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,
  • den Herausgeber,
  • und Name des Modells (chat-bison@001).

Da das Sprachmodell jetzt bereit ist, können Sie ein ConversationalChain vorbereiten. Dies ist eine übergeordnete Abstraktionsebene, die von LangChain4J angeboten wird, um verschiedene Komponenten zusammen zu konfigurieren, um eine Unterhaltung zu verarbeiten, wie das Chat-Sprachmodell selbst, aber möglicherweise auch andere Komponenten, um den Verlauf der Chat-Unterhaltung zu verarbeiten oder andere Tools wie Retriever zum Abrufen von Informationen aus Vektordatenbanken einzusetzen. Aber keine Sorge, wir kommen später in diesem Codelab darauf zurück.

Anschließend führen Sie ein Gespräch mit dem Chatmodell und stellen mehrere zusammenhängende Fragen. Zuerst fragen Sie sich über LLMs, dann fragen Sie, was Sie mit ihnen machen können und was sind einige Beispiele dafür. Sie müssen sich nicht wiederholen. Das LLM weiß, bezeichnet im Kontext dieser Unterhaltung LLMs.

Wenn Sie diese mehrsprachige Unterhaltung übernehmen möchten, rufen Sie einfach die Methode execute() in der Kette auf. Diese fügt sie dem Kontext der Unterhaltung hinzu. Das Chatmodell generiert eine Antwort und fügt sie dem Chatprotokoll hinzu.

Führen Sie zum Ausführen dieser Klasse den folgenden Befehl im Cloud Shell-Terminal 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 deine drei Fragen beantwortet!

Mit dem VertexAIChatModel-Builder können Sie optionale Parameter definieren, die bereits einige Standardwerte haben, die Sie überschreiben können. Beispiele:

  • .temperature(0.2), um zu definieren, wie kreativ die Antwort sein soll. 0 steht für wenig Creative und ist oft sachlicher, während 1 für mehr kreative Ergebnisse steht.
  • .maxOutputTokens(50): In diesem Beispiel wurden 400 Tokens angefordert (3 Tokens entsprechen in etwa vier Wörtern), je nachdem, wie lang die generierte Antwort sein soll.
  • .topK(20) – zur zufälligen Auswahl eines Wortes aus einer maximalen Anzahl wahrscheinlicher Wörter für die Textvervollständigung (1 bis 40)
  • .topP(0.95) – zur Auswahl der möglichen Wörter, deren Gesamtwahrscheinlichkeit diese Gleitkommazahl (zwischen 0 und 1) ergibt
  • .maxRetries(3): Falls das Kontingent für die Anfrage pro Zeit überschritten wird, kann das Modell den Aufruf dreimal wiederholen lassen, z. B.

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

Im vorherigen Abschnitt haben Sie sofort damit begonnen, dem LLM-Chatbot Fragen zu stellen, ohne ihm einen bestimmten Kontext zu geben. Sie können einen solchen Chatbot jedoch spezialisieren, um für eine bestimmte Aufgabe oder ein bestimmtes Thema Experte zu werden.

Wie gehen Sie vor? Vorbereiten des LLM: die Aufgabe, den Kontext, vielleicht ein paar Beispiele dafür, was es zu tun hat, welche Persona es haben sollte, in welchem Format Sie Antworten erhalten möchten und möglicherweise auch einen Ton, wenn sich der Chatbot auf eine bestimmte Weise verhalten soll.

Dieser Artikel zum Erstellen von Prompts veranschaulicht diesen Ansatz an dieser Grafik gut:

8a4c67679dcbd085.png

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

Lassen Sie uns zur Veranschaulichung dieses Punkts von den Websites prompts.chat inspirieren. Dort finden Sie eine Vielzahl toller und unterhaltsamer Ideen für benutzerdefinierte, maßgeschneiderte Chatbots, die sie als:

Es gibt ein Beispiel, wie Sie einen LLM-Chatbot in einen Schachspieler verwandeln können! Implementieren wir das!

Aktualisieren Sie die ChatPrompts-Klasse 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 einmal genauer an:

  • Es sind einige neue Importe erforderlich, um die Speicherkapazität des Chats zu verarbeiten.
  • Sie instanziieren das Chatmodell, aber mit einer kleinen Anzahl maximaler Tokens (hier 7), da wir nur den nächsten Zug erzeugen möchten und nicht eine ganze Dissertation über Schach!
  • Als Nächstes erstellen Sie einen Chatspeicherspeicher, um die Chatunterhaltungen zu speichern.
  • Sie erstellen einen tatsächlichen Chatspeicher im Fenstermodus, um die letzten Züge beizubehalten.
  • Im Chatspeicher fügen Sie ein „System“ , die dem Chat-Modell anweist, wer es sein soll (z. B. ein erfahrener Schachspieler). Das „System“ Inhalt fügt Kontext hinzu, während „Nutzer“ und „KI“ Nachrichten die eigentliche Diskussion.
  • Sie erstellen eine Unterhaltungskette, die den Arbeitsspeicher und das Chatmodell kombiniert.
  • Dann haben wir eine Liste mit Moves für Weiß, über die ihr iteriert. Die Kette wird jedes Mal mit dem nächsten weißen Zug ausgeführt und das Chatmodell antwortet mit dem nächstbesten Zug.

Wenn Sie diese Klasse mit diesen Zügen ausführen, sollten Sie die folgende Ausgabe sehen:

$ ./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 

Oh! PaLM weiß, wie man Schach spielt? Nun, nicht ganz. Aber während des Trainings muss das Modell einige Schachkommentare oder sogar die PGN-Dateien (Portable Game Notation) früherer Spiele gesehen haben. Der Chatbot wird allerdings wahrscheinlich nicht gegen AlphaZero (die KI, die die besten Spieler von Go, Shogi und Schach besiegt) gewinnen und die Unterhaltung im weiteren Verlauf könnte zum Ende führen, da sich das Modell nicht wirklich an den tatsächlichen Spielstand erinnern kann.

Chatmodelle sind sehr leistungsfähig und können umfassende Interaktionen mit Ihren Nutzern ermöglichen. Außerdem können sie verschiedene kontextbezogene Aufgaben erledigen. Im nächsten Abschnitt geht es um das Extrahieren strukturierter Daten aus Text.

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 aber 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 Large Language Model anweisen, mit einem geschickt abgestimmten Prompt (in der Regel als „Prompt Engineering“) JSON-Datenstrukturen zu generieren.

So aktualisieren Sie die ChatPrompts-Klasse:

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 ist definiert, um die Details zur Beschreibung 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.
  • extractPerson() wird mit einer @UserMessage-Anmerkung versehen, die eine Aufforderung mit ihr verknüpft. Das ist der Prompt, mit dem das Modell die Informationen extrahiert und die Details in Form eines JSON-Dokuments zurückgibt. Dieses wird für Sie geparst und in eine Person-Instanz demarshalliert.

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

  • Das Chatmodell wird instanziiert.
  • Mithilfe der Klasse AiServices von LangChain4J wird ein PersonExtractor-Objekt 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! Hier ist Anna, sie ist 23!

Besonders interessant bei 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 Personenklasse zur Darstellung der extrahierten personenbezogenen Daten und Sie haben eine PersonExtractor-Klasse mit einer extractPerson()-Methode, die eine Personeninstanz zurückgibt. Das Konzept von LLM ist abstrahiert, und als Java-Entwickler bearbeiten Sie nur normale Klassen und Objekte.

7. Retrieval Augmented Generation: mit Dokumenten chatten

Zurück zu den Gesprächen. Dieses Mal können Sie Fragen zu Ihren Dokumenten stellen. Sie erstellen einen Chatbot, der in der Lage ist, relevante Informationen aus einer Datenbank mit Extrakten Ihrer Dokumente abzurufen. Diese Informationen werden vom Modell verwendet, um die Antworten zu grundlegen, anstatt zu versuchen, Antworten aus dem Training zu generieren. Dieses Muster wird RAG oder Retrieval Augmented Generation (Retrieval Augmented Generation) genannt.

Retrieval Augmented Generation besteht aus zwei Phasen:

  1. Aufnahmephase: Dokumente werden geladen, in kleinere Blöcke aufgeteilt und in einer Vektordatenbank, die für semantische Suchen in der Lage ist, eine vektorielle Darstellung (eine „Vektoreinbettung“) gespeichert.

6c5bb5cb2e3b8088.png

  1. Abfragephase: 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. Dann erhält das LLM den Kontext der Unterhaltung, die Textausschnitte, die den von der Datenbank zurückgegebenen Vektoren entsprechen, und wird aufgefordert, seine Antwort anhand dieser Snippets zu begründen.

2c279c506d7606cd.png

Dokumente werden vorbereitet

In dieser neuen Demo stellen Sie sich Fragen zur von Google entwickelten neuronalen Netzwerkarchitektur "transformer", mit der heutzutage alle modernen Large Language Models implementiert werden.

Sie können das Forschungspapier abrufen, in dem diese Architektur beschrieben wird („Attention is all you need“), indem Sie die PDF-Datei mit dem Befehl wget aus dem Internet herunterladen:

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

Gesprächsabrufkette implementieren

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

Dokumentaufnahme

Der erste Schritt in der Phase der Dokumentaufnahme besteht darin, die heruntergeladene PDF-Datei zu finden und ein PdfParser zum Lesen vorzubereiten:

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

Anstelle des üblichen Chat-Sprachmodells erstellen Sie vorher eine Instanz eines "embedding"-Modells. Dies ist ein bestimmtes Modell und ein bestimmter Endpunkt, dessen Aufgabe es ist, Vektordarstellungen von Textelementen (Wörter, Sätze oder sogar Absätze) 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 mehrere Kurse, die gemeinsam für Folgendes gelten:

  • Laden Sie das PDF-Dokument und teilen Sie es in mehrere Teile auf.
  • Erstellen Sie Vektoreinbettungen für alle diese Blöcke.
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);

Zum Speichern der Vektoreinbettungen wird eine Instanz der speicherinternen Vektordatenbank InMemoryEmbeddingStore erstellt.

Das Dokument wird dank der Klasse DocumentSplitters in mehrere Abschnitte unterteilt. Der Text der PDF-Datei wird in Snippets mit je 500 Zeichen aufgeteilt, die sich jeweils 100 Zeichen überschneiden. Der folgende Block wird verwendet, um zu vermeiden, dass Wörter oder Sätze stückweise geschnitten werden.

„Aufnahme“ des Geschäfts verbindet den Dokumentaufteilunger, das Einbettungsmodell zur Berechnung der Vektoren und die speicherinterne Vektordatenbank. Die Aufnahme erfolgt dann über die Methode ingest().

Jetzt ist die erste Phase vorbei. 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. Sie können das übliche Chatmodell erstellen, um die Unterhaltung zu beginnen:

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();

Außerdem benötigen Sie die Klasse "retriever", die die Vektordatenbank (in der Variablen embeddingStore) und das Einbettungsmodell verknüpft. Seine Aufgabe besteht darin, die Vektordatenbank abzufragen, indem eine Vektoreinbettung für die Nutzerabfrage berechnet wird, um ähnliche Vektoren in der Datenbank zu finden:

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

An dieser Stelle können Sie die Klasse ConversationalRetrievalChain instanziieren (dies ist nur ein anderer Name für das Muster „Retrieval Augmented Generation“):

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 Chat-Sprachmodell, das Sie zuvor konfiguriert haben
  • Der Retriever vergleicht eine Abfrage zur Vektoreinbettung mit den Vektoren in der Datenbank.
  • In einer Prompt-Vorlage heißt es explizit, dass das Chatmodell antworten soll, indem die Antwort auf den bereitgestellten Informationen basiert, d.h. auf relevanten Auszügen der Dokumentation, deren Vektoreinbettung dem Vektor der Frage des Nutzers ähnelt.

Jetzt kannst du endlich 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 aus mit:

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

In der Ausgabe sollten Sie die Antwort auf Ihre Fragen sehen:

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.

Komplettlösung

Hier finden Sie den vollständigen Inhalt des Kurses „ChatPrompts“, um das Kopieren und Einfügen zu erleichtern:

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 mithilfe von LangChain4J und der PaLM API Ihre erste Chatanwendung basierend auf generativer KI in Java erstellt. Dabei hast du erfahren, dass Large Language Models ziemlich leistungsstark sind und in der Lage sind, verschiedene Aufgaben wie Fragen und Antworten zu bewältigen, selbst bei der eigenen Dokumentation oder der Datenextraktion. In gewissem Maße konnte man sogar Schach spielen!

Was liegt als Nächstes an?

Sehen Sie sich einige der folgenden Codelabs an, um mit PaLM in Java noch besser zu werden:

Weitere Informationen

Referenzdokumente