Chat avec des utilisateurs et des documents basé sur l'IA générative en Java avec PaLM et LangChain4J

1. Introduction

Dernière mise à jour:05/02/2024

Qu'est-ce que l'IA générative ?

L'IA générative ou l'intelligence artificielle générative fait référence à l'utilisation de l'IA pour créer de nouveaux contenus, comme du texte, des images, de la musique, de l'audio et des vidéos.

L'IA générative s'appuie sur des modèles de fondation (grands modèles d'IA) qui peuvent effectuer plusieurs opérations en même temps et réaliser des tâches prêtes à l'emploi, telles que la synthèse, les questions/réponses, la classification, etc. De plus, avec un entraînement minimal, les modèles de fondation peuvent être adaptés à des cas d'utilisation ciblés avec très peu de données d'exemple.

Comment fonctionne l'IA générative ?

L'IA générative s'appuie sur un modèle de ML (machine learning) pour apprendre les modèles et les relations d'un ensemble de données de contenus créés par l'humain. Elle utilise ensuite les schémas appris pour générer de nouveaux contenus.

La méthode la plus courante pour entraîner un modèle d'IA générative consiste à utiliser l'apprentissage supervisé. Le modèle reçoit un ensemble de contenus créés manuellement et les étiquettes correspondantes. Il apprend ensuite à générer du contenu semblable au contenu créé par un humain et doté des mêmes étiquettes.

Quelles sont les applications d'IA générative courantes ?

L'IA générative traite un grand volume de contenus, générant des insights et des réponses sous forme de texte, d'images et de formats conviviaux. Voici des exemple d'utilisations de l'IA générative :

  • Renforcez les interactions avec les clients grâce à des fonctionnalités de chat et de recherche améliorées.
  • Explorer de vastes quantités de données non structurées à l'aide d'interfaces de conversation et de synthèses
  • Accomplissez les tâches répétitives comme répondre aux appels d'offres, localiser le contenu marketing en cinq langues, vérifier la conformité des contrats des clients, etc.

Quelles sont les offres d'IA générative de Google Cloud ?

Vertex AI vous permet d'interagir avec des modèles de fondation, de les personnaliser et de les intégrer dans vos applications, même sans expérience en ML. Accédez aux modèles de fondation sur Model Garden, réglez-les via une UI simple dans Generative AI Studio ou utilisez-les dans un notebook de data science.

Vertex AI Search and Conversation offre aux développeurs le moyen le plus rapide de créer des moteurs de recherche et des chatbots optimisés par l'IA générative.

De plus, Duet AI est votre collaborateur alimenté par l'IA, disponible sur Google Cloud et dans les IDE pour vous aider à gagner en efficacité et en rapidité.

Quel est l'objet de cet atelier de programmation ?

Cet atelier de programmation porte sur le grand modèle de langage (LLM) PaLM 2, hébergé sur Google Cloud Vertex AI et englobe tous les produits et services de machine learning.

Vous utiliserez Java pour interagir avec l'API PaLM, ainsi qu'avec l'orchestrateur de framework LLM LangChain4J. Nous étudierons différents exemples concrets qui vous permettront de tirer parti du LLM pour la réponse à des questions, la génération d'idées, l'extraction d'entités et de contenus structurés, ainsi que pour la synthèse.

Dites-m'en plus sur le framework LangChain4J.

Le framework LangChain4J est une bibliothèque Open Source permettant d'intégrer de grands modèles de langage à vos applications Java en orchestrant divers composants, tels que le LLM lui-même, mais aussi d'autres outils comme les bases de données vectorielles (pour les recherches sémantiques), des chargeurs et des séparateurs de documents (pour analyser les documents et en tirer des enseignements), des analyseurs de sortie, etc.

c6d7f7c3fd0d2951.png

Points abordés

  • Configurer un projet Java pour utiliser PaLM et LangChain4J
  • Comment extraire des informations utiles d'un contenu non structuré (extraction d'entités ou de mots clés, sortie au format JSON)
  • Comment démarrer une conversation avec vos utilisateurs
  • Utiliser le modèle de chat pour poser des questions sur votre propre documentation

Prérequis

  • Connaissance du langage de programmation Java
  • Un projet Google Cloud
  • Un navigateur, tel que Chrome ou Firefox

2. Préparation

Configuration de l'environnement au rythme de chacun

  1. Connectez-vous à la console Google Cloud, puis créez un projet ou réutilisez un projet existant. (Si vous ne possédez pas encore de compte Gmail ou Google Workspace, vous devez en créer un.)

295004821bab6a87.png

37d264871000675d.png

96d86d3d5655cdbe.png

  • Le nom du projet est le nom à afficher pour les participants au projet. Il s'agit d'une chaîne de caractères non utilisée par les API Google. Vous pourrez toujours le modifier.
  • L'ID du projet est unique parmi tous les projets Google Cloud et non modifiable une fois défini. La console Cloud génère automatiquement une chaîne unique (en général, vous n'y accordez d'importance particulière). Dans la plupart des ateliers de programmation, vous devrez indiquer l'ID de votre projet (généralement identifié par PROJECT_ID). Si l'ID généré ne vous convient pas, vous pouvez en générer un autre de manière aléatoire. Vous pouvez également en spécifier un et voir s'il est disponible. Après cette étape, l'ID n'est plus modifiable et restera donc le même pour toute la durée du projet.
  • Pour information, il existe une troisième valeur (le numéro de projet) que certaines API utilisent. Pour en savoir plus sur ces trois valeurs, consultez la documentation.
  1. Vous devez ensuite activer la facturation dans la console Cloud pour utiliser les ressources/API Cloud. L'exécution de cet atelier de programmation est très peu coûteuse, voire sans frais. Pour désactiver les ressources et éviter ainsi que des frais ne vous soient facturés après ce tutoriel, vous pouvez supprimer le projet ou les ressources que vous avez créées. Les nouveaux utilisateurs de Google Cloud peuvent participer au programme d'essai sans frais pour bénéficier d'un crédit de 300 $.

Démarrer Cloud Shell

Bien que Google Cloud puisse être utilisé à distance depuis votre ordinateur portable, vous allez utiliser Cloud Shell dans cet atelier de programmation, un environnement de ligne de commande exécuté dans le cloud.

Activer Cloud Shell

  1. Dans Cloud Console, cliquez sur Activer Cloud Shell d1264ca30785e435.png.

cb81e7c8e34bc8d.png

Si vous démarrez Cloud Shell pour la première fois, un écran intermédiaire vous explique de quoi il s'agit. Si un écran intermédiaire s'est affiché, cliquez sur Continuer.

d95252b003979716.png

Le provisionnement et la connexion à Cloud Shell ne devraient pas prendre plus de quelques minutes.

7833d5e1c5d18f54.png

Cette machine virtuelle contient tous les outils de développement nécessaires. Elle comprend un répertoire d'accueil persistant de 5 Go et s'exécute dans Google Cloud, ce qui améliore considérablement les performances du réseau et l'authentification. Une grande partie, voire la totalité, de votre travail dans cet atelier de programmation peut être effectué dans un navigateur.

Une fois connecté à Cloud Shell, vous êtes authentifié et le projet est défini sur votre ID de projet.

  1. Exécutez la commande suivante dans Cloud Shell pour vérifier que vous êtes authentifié :
gcloud auth list

Résultat de la commande

 Credentialed Accounts
ACTIVE  ACCOUNT
*       <my_account>@<my_domain.com>

To set the active account, run:
    $ gcloud config set account `ACCOUNT`
  1. Exécutez la commande suivante dans Cloud Shell pour vérifier que la commande gcloud connaît votre projet:
gcloud config list project

Résultat de la commande

[core]
project = <PROJECT_ID>

Si vous obtenez un résultat différent, exécutez cette commande :

gcloud config set project <PROJECT_ID>

Résultat de la commande

Updated property [core/project].

3. Préparer votre environnement de développement

Dans cet atelier de programmation, vous allez utiliser le terminal et l'éditeur de code Cloud Shell pour développer vos programmes Java.

Activer les API Vertex AI

  1. Dans la console Google Cloud, assurez-vous que le nom de votre projet est affiché en haut de la console Google Cloud. Si ce n'est pas le cas, cliquez sur Sélectionner un projet pour ouvrir le sélecteur de projets, puis sélectionnez le projet souhaité.
  2. Si vous n'êtes pas dans la section Vertex AI de la console Google Cloud, procédez comme suit:
  3. Dans le champ Rechercher, saisissez "Vertex AI", puis saisissez
  4. Dans les résultats de recherche, cliquez sur "Vertex AI". Le tableau de bord Vertex AI s'affiche.
  5. Cliquez sur Activer toutes les API recommandées dans le tableau de bord Vertex AI.

Cela activera plusieurs API, mais la plus importante pour cet atelier de programmation est aiplatform.googleapis.com, que vous pouvez également activer dans la ligne de commande, dans le terminal Cloud Shell, en exécutant la commande suivante:

$ gcloud services enable aiplatform.googleapis.com

Créer la structure du projet avec Gradle

Pour compiler vos exemples de code Java, vous utiliserez l'outil de compilation Gradle et la version 17 de Java. Pour configurer votre projet avec Gradle, dans le terminal Cloud Shell, créez un répertoire (ici, palm-workshop), puis exécutez la commande gradle init dans ce répertoire:

$ 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

Vous allez créer une application (option 2) en utilisant le langage Java (option 3), sans utiliser de sous-projets (option 1), en utilisant la syntaxe Groovy pour le fichier de compilation (option 1), n'utilisez pas de nouvelles fonctionnalités de compilation (option non), en générant des tests avec JUnit Jupiter (option 4). Vous pouvez également utiliser palm-workshop pour le nom du projet.

La structure du projet se présentera comme suit:

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

Mettons à jour le fichier app/build.gradle pour ajouter certaines dépendances nécessaires. Vous pouvez supprimer la dépendance guava si elle est présente et la remplacer par les dépendances du projet LangChain4J et de la bibliothèque de journalisation pour éviter d'accumuler les messages d'enregistreur manquants:

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

Il existe deux dépendances pour LangChain4J:

  • sur le projet de base,
  • et l'autre pour le module Vertex AI dédié.

Pour compiler et exécuter nos programmes avec Java 17, ajoutez le bloc suivant sous le bloc plugins {}:

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

Autre modification à apporter: mettez à jour le bloc application de app/build.gradle afin de permettre aux utilisateurs de remplacer la classe principale pour qu'ils s'exécutent sur la ligne de commande lorsqu'ils appellent l'outil de compilation:

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

Pour vérifier que le fichier de compilation est prêt à exécuter votre application, vous pouvez exécuter la classe principale par défaut qui affiche un simple message Hello World!:

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

> Task :app:run
Hello World!

BUILD SUCCESSFUL in 3s
2 actionable tasks: 2 executed

Vous êtes maintenant prêt à programmer avec le grand modèle de langage textuel PaLM à l'aide du projet LangChain4J.

Pour référence, voici à quoi devrait maintenant ressembler le fichier de compilation app/build.gradle complet:

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. Vous appelez le modèle de chat de PaLM pour la première fois

Maintenant que le projet est correctement configuré, il est temps d'appeler l'API PaLM.

Créez une classe appelée ChatPrompts.java dans le répertoire app/src/main/java/palm/workshop (avec la classe App.java par défaut), puis saisissez le contenu suivant:

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

Dans ce premier exemple, vous devez importer la classe VertexAiChatModel et le ConversationalChain de LangChain4J pour faciliter la gestion de l'aspect multitour des conversations.

Ensuite, dans la méthode main, vous allez configurer le modèle de langage du chat à l'aide du compilateur pour VertexAiChatModel afin de spécifier les éléments suivants:

  • le point de terminaison,
  • le projet,
  • la région,
  • l'éditeur,
  • et le nom du modèle (chat-bison@001).

Maintenant que le modèle de langage est prêt, vous pouvez préparer un ConversationalChain. Il s'agit d'une abstraction de niveau supérieur proposée par LangChain4J pour configurer différents composants afin de gérer une conversation, comme le modèle de langage du chat lui-même, mais éventuellement d'autres composants pour gérer l'historique de la conversation par chat, ou pour connecter d'autres outils tels que des récupérateurs afin d'extraire des informations de bases de données vectorielles. Mais ne vous inquiétez pas, nous y reviendrons plus tard dans cet atelier de programmation.

Ensuite, vous engagerez une conversation multitour avec le modèle de chat afin de poser plusieurs questions interdépendantes. Vous vous interrogez d'abord sur les LLM, puis vous demandez ce que vous pouvez en faire et quels sont des exemples. Comme vous n'avez pas besoin de vous répéter, le LLM sait que "les" signifie LLM, dans le contexte de cette conversation.

Pour démarrer cette conversation multitour, il vous suffit d'appeler la méthode execute() au niveau de la chaîne. Elle l'ajoute au contexte de la conversation, le modèle de chat génère une réponse et l'ajoute également à l'historique des discussions.

Pour ce faire, exécutez la commande suivante dans le terminal Cloud Shell:

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

Un résultat semblable à celui-ci doit s'afficher:

$ ./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 a répondu à vos trois questions associées.

L'outil de création VertexAIChatModel vous permet de définir des paramètres facultatifs comportant déjà des valeurs par défaut que vous pouvez remplacer. Voici quelques exemples :

  • .temperature(0.2) : pour définir le niveau de création souhaité pour la réponse (0 signifiant une création peu créative et souvent plus factuelle, 1 signifiant un plus grand nombre de créations)
  • .maxOutputTokens(50) : dans cet exemple, 400 jetons ont été demandés (3 jetons équivalent à peu près à 4 mots), en fonction de la durée souhaitée pour la réponse générée.
  • .topK(20) : permet de sélectionner aléatoirement un mot parmi un nombre maximal de mots probables pour compléter le texte (de 1 à 40)
  • .topP(0.95) : pour sélectionner les mots possibles dont la probabilité totale s'additionne à ce nombre à virgule flottante (entre 0 et 1)
  • .maxRetries(3) : si vous dépassez le quota de requêtes par temps, vous pouvez demander au modèle de relancer l'appel trois fois, par exemple

5. Un chatbot utile avec une personnalité !

Dans la section précédente, vous avez commencé à poser immédiatement des questions au chatbot LLM, sans lui donner de contexte particulier. Mais vous pouvez le spécialiser pour devenir un expert dans une tâche particulière ou sur un sujet particulier.

Comment procéder ? En préparant le terrain, vous devez expliquer au LLM la tâche à accomplir, le contexte, en donnant peut-être quelques exemples de ce qu'il doit faire, quel persona il doit avoir, dans quel format vous souhaitez obtenir des réponses et éventuellement un ton si vous voulez que le chatbot se comporte d'une certaine manière.

Cet article sur la création de requêtes illustre bien cette approche avec ce graphique:

8a4c67679dcbd085.png

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

Pour illustrer ce point, prenons l'inspiration sur les sites Web prompts.chat, qui listent de nombreuses idées intéressantes et amusantes de chatbots personnalisés pour agir en tant que:

Voici un exemple permettant de transformer un chatbot LLM en joueur d'échecs. Implémentons cela !

Mettez à jour la classe ChatPrompts comme suit :

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

Voyons cela en détail:

  • De nouvelles importations sont nécessaires pour gérer la mémoire du chat.
  • Vous instanciez le modèle de chat, mais avec un petit nombre maximum de jetons (ici, 7), car nous voulons simplement générer le prochain coup, et non une dissertation complète sur les échecs !
  • Ensuite, créez un magasin de mémoire de chat pour enregistrer les conversations de chat.
  • Vous créez une mémoire de chat réelle pour conserver les derniers mouvements.
  • Dans la mémoire du chat, vous ajoutez un "système" qui indique au modèle de chat qui il est censé être un joueur d'échecs expert. Le "système" ajoute du contexte, tandis que le terme "utilisateur" et "IA" les messages sont la discussion proprement dite.
  • Vous créez une chaîne de conversation qui combine la mémoire et le modèle de chat.
  • Ensuite, nous avons une liste de coups pour le blanc, que vous itérez. La chaîne est exécutée avec le coup blanc suivant à chaque fois, et le modèle de chat répond avec le meilleur coup suivant.

Lorsque vous exécutez cette classe avec ces déplacements, vous devriez obtenir le résultat suivant:

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

Ouah ! PaLM sait jouer aux échecs ? Pas exactement, mais pendant son entraînement, le modèle a dû voir des commentaires sur les échecs, voire les fichiers PGN (Portable Game Notation) de parties précédentes. Toutefois, ce chatbot ne l'emportera probablement pas face à AlphaZero (l'IA qui bat les meilleurs joueurs de Go, de Shogi et d'échecs). La conversation pourrait dérailler plus tard, le modèle ne se souvenant pas vraiment de l'état réel du jeu.

Les modèles de chat sont très puissants. Ils permettent de créer des interactions riches avec vos utilisateurs et de gérer diverses tâches contextuelles. Dans la section suivante, nous allons nous pencher sur une tâche utile: extraire des données structurées d'un texte.

6. Extraire des informations d'un texte non structuré

Dans la section précédente, vous avez créé des conversations entre un utilisateur et un modèle de langage de chat. Avec LangChain4J, vous pouvez également utiliser un modèle de chat pour extraire des informations structurées d'un texte non structuré.

Supposons que vous souhaitiez extraire le nom et l'âge d'une personne à partir d'une biographie ou d'une description de cette personne. Vous pouvez demander au grand modèle de langage de générer des structures de données JSON avec une requête astucieusement modifiée (c'est ce que l'on appelle communément "ingénierie de requête").

Vous allez mettre à jour la classe ChatPrompts comme suit:

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

Examinons les différentes étapes de ce fichier:

  • Une classe Person est définie pour représenter les détails décrivant une personne (son nom et son âge).
  • L'interface PersonExtractor est créée à l'aide d'une méthode qui, à partir d'une chaîne de texte non structurée, renvoie une instance Person instanciée.
  • extractPerson() est annoté avec une annotation @UserMessage qui lui associe une requête. Il s'agit de l'invite que le modèle utilisera pour extraire les informations et les renvoyer sous la forme d'un document JSON, qui sera analysé pour vous et non assemblé dans une instance Person.

Examinons maintenant le contenu de la méthode main():

  • Le modèle de chat est instancié.
  • Un objet PersonExtractor est créé grâce à la classe AiServices de LangChain4J.
  • Ensuite, il vous suffit d'appeler Person person = extractor.extractPerson(...) pour extraire les informations sur la personne du texte non structuré, puis de récupérer une instance Person avec le nom et l'âge.

Exécutez maintenant cette classe à l'aide de la commande suivante:

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

> Task :app:run
Anna
23

Oui. Voici Anna, elle a 23 ans !

Cette approche AiServices présente un intérêt particulier, car vous utilisez des objets fortement typés. Vous n'interagissez pas directement avec le LLM de chat. À la place, vous travaillez avec des classes concrètes, comme la classe Person pour représenter les informations personnelles extraites, et vous disposez d'une classe PersonExtractor avec une méthode extractPerson() qui renvoie une instance Person. Le concept de LLM a disparu. En tant que développeur Java, vous ne faites que manipuler des classes et des objets normaux.

7. Récupération de la Génération augmentée: discuter avec vos documents

Revenons aux conversations. Cette fois, vous pourrez poser des questions sur vos documents. Vous allez créer un chatbot capable de récupérer des informations pertinentes à partir d'une base de données d'extraits de vos documents. Ces informations seront utilisées par le modèle pour "ancrer" ses réponses, au lieu d'essayer de générer des réponses à partir de son entraînement. Ce modèle est appelé RAG, ou Génération augmentée de récupération.

La génération augmentée de récupération se déroule en deux phases:

  1. Phase d'ingestion : les documents sont chargés, divisés en fragments plus petits, puis une représentation vectorielle de ceux-ci (représentation vectorielle continue) est stockée dans une base de données vectorielle capable d'effectuer des recherches sémantiques.

6c5bb5cb2e3b8088.png

  1. Phase de requête : les utilisateurs peuvent désormais poser des questions à votre chatbot sur la documentation. La question sera également transformée en vecteur, et comparée à tous les autres vecteurs de la base de données. Les vecteurs les plus similaires sont généralement liés sémantiquement et sont renvoyés par la base de données vectorielle. Ensuite, le LLM reçoit le contexte de la conversation, les extraits de texte correspondant aux vecteurs renvoyés par la base de données, et il est invité à baser sa réponse en examinant ces extraits.

2c279c506d7606cd.png

Préparer vos documents

Pour cette nouvelle démonstration, vous allez poser des questions sur l'architecture de réseau de neurones transformer, mise au point par Google, et c'est ainsi que sont implémentés aujourd'hui tous les grands modèles de langage modernes.

Vous pouvez récupérer l'étude qui décrit cette architecture ("Attention is all you need") à l'aide de la commande wget pour télécharger le PDF sur Internet:

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

Implémenter une chaîne de récupération conversationnelle

Découvrons, petit à petit, comment créer une approche en deux phases, d'abord avec l'ingestion du document, puis l'heure de la requête lorsque les utilisateurs posent des questions sur le document.

Ingestion de documents

La toute première étape de la phase d'ingestion de documents consiste à rechercher le fichier PDF que nous avons téléchargé et à préparer un fichier PdfParser pour le lire:

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

Au lieu de créer le modèle de langage de chat habituel, vous allez d'abord créer une instance d'un modèle "embedding". Il s'agit d'un modèle et d'un point de terminaison particuliers dont le rôle est de créer des représentations vectorielles d'éléments de texte (mots, phrases ou même paragraphes).

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

Ensuite, vous aurez besoin de quelques cours pour collaborer afin de:

  • Chargez et scindez le document PDF en plusieurs morceaux.
  • Créer des représentations vectorielles continues pour tous ces fragments
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);

Une instance de InMemoryEmbeddingStore, une base de données vectorielle en mémoire, est créée pour stocker les représentations vectorielles continues.

Le document est divisé en fragments grâce à la classe DocumentSplitters. Il va diviser le texte du fichier PDF en extraits de 500 caractères, avec un chevauchement de 100 caractères (avec le morceau suivant, pour éviter de couper des mots ou des phrases, en petits morceaux).

Le magasin "ingestor" associe le séparateur de documents, le modèle de représentation vectorielle continue permettant de calculer les vecteurs et la base de données vectorielle en mémoire. La méthode ingest() se charge ensuite de l'ingestion.

Maintenant, la première phase est terminée. Le document a été transformé en morceaux de texte avec les représentations vectorielles continues associées, puis stocké dans la base de données vectorielle.

Poser des questions

Il est temps de vous préparer à poser des questions ! Pour démarrer la conversation, vous pouvez créer le modèle de chat habituel:

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

Vous avez également besoin d'une classe "retriever" qui va lier la base de données vectorielle (dans la variable embeddingStore) et le modèle de représentation vectorielle continue. Son rôle est d'interroger la base de données vectorielles en calculant une représentation vectorielle continue pour la requête de l'utilisateur, afin de trouver des vecteurs similaires dans la base de données:

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

À ce stade, vous pouvez instancier la classe ConversationalRetrievalChain (il s'agit simplement d'un nom différent pour le modèle de génération augmentée de récupération):

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

Cette "chaîne" se lie:

  • Le modèle de langage de chat que vous avez configuré précédemment.
  • Le extracteur compare une requête de représentation vectorielle continue de vecteur aux vecteurs de la base de données.
  • Un modèle de requête indique explicitement que le modèle de chat doit répondre en se basant sur les informations fournies (c'est-à-dire sur les extraits pertinents de la documentation dont la représentation vectorielle continue de vecteur est semblable au vecteur de la question de l'utilisateur).

Vous êtes maintenant prêt à poser vos questions !

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

Exécutez le programme avec la commande suivante:

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

Dans le résultat, vous devriez voir la réponse à vos questions:

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.

Solution complète

Pour faciliter le copier-coller, voici l'intégralité du contenu de la classe ChatPrompts:

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. Félicitations

Félicitations, vous avez créé votre première application de chat par IA générative en Java à l'aide de LangChain4J et de l'API PaLM. Vous avez découvert en cours de route que les grands modèles de chat de langage sont assez puissants et capables de gérer diverses tâches comme les questions/réponses, même avec votre propre documentation, l'extraction de données et, dans une certaine mesure, il était même capable de jouer aux échecs !

Et ensuite ?

Consultez certains des ateliers de programmation suivants pour aller plus loin avec PaLM en Java:

Complément d'informations

Documents de référence