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, les modèles de fondation ne nécessitent qu'un entraînement minimal et peuvent être adaptés à des cas d'utilisation ciblés avec très peu d'exemples de données.

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 schémas et les relations dans un ensemble de données de contenus créés manuellement. puis utilise ces schémas 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 se voit attribuer un ensemble de contenus créés manuellement et des é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 courantes de l'IA générative ?

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 :

  • Améliorer les interactions client grâce à des fonctionnalités optimisées de chat et de recherche
  • Explorer de grandes quantités de données non structurées grâce à des interfaces conversationnelles et des résumés
  • Faciliter les tâches répétitives en répondant aux appels d'offres, en localisant le contenu marketing dans cinq langues et en vérifiant la conformité des contrats client, etc.

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

Avec Vertex AI, interagissez avec des modèles de fondation, personnalisez-les et intégrez-les à vos applications, sans aucune connaissance du ML. Accédez aux modèles de fondation dans Model Garden, ajustez-les via une UI simple sur Generative AI Studio ou utilisez-les dans un notebook de data science.

La fonctionnalité Search and Conversation de Vertex AI 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.

Duet AI est un outil collaboratif optimisé par l'IA, disponible sur Google Cloud et les IDE, pour vous aider à gagner en efficacité.

Sur quoi porte cet atelier de programmation ?

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

Vous utiliserez Java pour interagir avec l'API PaLM, en association avec l'orchestrateur de framework LLM LangChain4j. Vous passerez en revue différents exemples concrets pour tirer parti du LLM afin de répondre à des questions, générer des idées, extraire des entités et du contenu structuré, et résumer des informations.

Parlez-moi du framework LangChain4j.

Le framework LangChain4J est une bibliothèque Open Source permettant d'intégrer des grands modèles de langage dans vos applications Java. Pour ce faire, il orchestre différents 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), les chargeurs et les séparateurs de documents (pour analyser les documents et en tirer des enseignements), les analyseurs de sortie, etc.

c6d7f7c3fd0d2951.png

Points abordés

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

Prérequis

  • Connaissances 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, nous allons nous servir de Cloud Shell pour 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 s'affiche pour vous expliquer de quoi il s'agit. Si cet écran 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 sur Google Cloud, ce qui améliore nettement les performances du réseau et l'authentification. Vous pouvez réaliser une grande partie, voire la totalité, des activités de cet atelier de programmation dans un navigateur.

Une fois connecté à Cloud Shell, vous êtes en principe authentifié, et le projet est défini avec 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 s'affiche 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 projet, puis sélectionnez le projet de votre choix.
  2. Si vous n'êtes pas dans la partie Vertex AI de la console Google Cloud, procédez comme suit :
  3. Dans le champ Rechercher, saisissez Vertex AI, puis appuyez sur Entrée.
  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 l'atelier de programmation est aiplatform.googleapis.com, que vous pouvez également activer sur 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), sans utiliser de nouvelles fonctionnalités de compilation (option non), en générant des tests avec JUnit Jupiter (option 4), et pour le nom du projet, vous pouvez utiliser palm-workshop, et de même pour le package source, vous pouvez utiliser palm.workshop.

La structure du projet se présente comme suit :

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

Mettez à jour le fichier app/build.gradle pour ajouter les 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 les messages d'erreur liés à l'absence de journaliseur :

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

LangChain4j comporte deux dépendances :

  • un sur le projet principal,
  • et un pour le module Vertex AI dédié.

Pour utiliser Java 17 afin de compiler et d'exécuter nos programmes, ajoutez le bloc suivant sous le bloc plugins {} :

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

Il reste une modification à apporter : mettez à jour le bloc application de app/build.gradle pour permettre aux utilisateurs de remplacer la classe principale à exécuter sur la ligne de commande lors de l'appel de l'outil de compilation :

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

Pour vérifier que votre 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 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. Effectuer votre premier appel au modèle de chat de PaLM

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

Créez une classe nommée ChatPrompts.java dans le répertoire app/src/main/java/palm/workshop (à côté de 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 ConversationalChain LangChain4J pour faciliter la gestion de l'aspect multitours des conversations.

Ensuite, dans la méthode main, vous allez configurer le modèle de langage de chat en utilisant le générateur pour VertexAiChatModel afin de spécifier :

  • 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 linguistique 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 ensemble différents composants afin de gérer une conversation, comme le modèle de langage de chat lui-même, mais potentiellement d'autres composants pour gérer l'historique de la conversation de chat ou pour brancher d'autres outils comme des récupérateurs pour extraire des informations à partir de bases de données vectorielles. Mais ne vous inquiétez pas, nous y reviendrons plus tard dans cet atelier de programmation.

Vous allez ensuite avoir une conversation multitour avec le modèle de chat pour poser plusieurs questions liées. Vous vous demandez d'abord ce que sont les LLM, puis ce que vous pouvez faire avec eux et quels en sont quelques exemples. Remarquez que vous n'avez pas besoin de vous répéter. Le LLM sait que "eux" désigne les LLM dans le contexte de cette conversation.

Pour effectuer cette conversation multitour, il vous suffit d'appeler la méthode execute() sur la chaîne. Elle sera ajoutée au contexte de la conversation, le modèle de chat générera une réponse et l'ajoutera également à l'historique du chat.

Pour exécuter cette classe, exécutez la commande suivante dans le terminal Cloud Shell :

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

Un résultat semblable aux lignes suivantes 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.

Le générateur VertexAIChatModel vous permet de définir des paramètres facultatifs qui ont déjà des valeurs par défaut que vous pouvez remplacer. Voici quelques exemples :

  • .temperature(0.2) : pour définir le degré de créativité de la réponse (0 correspond à un faible degré de créativité et souvent à une réponse plus factuelle, tandis que 1 correspond à des résultats plus créatifs)
  • .maxOutputTokens(50) : dans l'exemple, 400 jetons ont été demandés (3 jetons équivalent à environ 4 mots), en fonction de la longueur souhaitée pour la réponse générée.
  • .topK(20) : pour sélectionner un mot au hasard parmi un nombre maximal de mots probables pour la complétion de texte (de 1 à 40)
  • .topP(0.95) : pour sélectionner les mots possibles dont la probabilité totale correspond à ce nombre à virgule flottante (entre 0 et 1)
  • .maxRetries(3) : si vous dépassez le quota de demandes par période, vous pouvez demander au modèle de réessayer l'appel trois fois, par exemple.

5. Un chatbot utile et avec de la personnalité !

Dans la section précédente, vous avez commencé à poser des questions au chatbot LLM sans lui fournir de contexte particulier. Mais vous pouvez spécialiser un tel chatbot pour qu'il devienne un expert dans une tâche ou un domaine spécifiques.

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

L'article sur la création de requêtes illustre bien cette approche avec le graphique suivant :

8a4c67679dcbd085.png

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

Pour illustrer ce point, inspirons-nous des sites Web prompts.chat, qui proposent de nombreuses idées intéressantes et amusantes de chatbots personnalisés pour leur permettre d'agir comme :

Vous trouverez un exemple pour 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);
        }
    }
}

Voici les étapes à suivre :

  • 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 de jetons maximum (ici 7), car nous voulons simplement générer le prochain coup, et non une dissertation complète sur les échecs !
  • Ensuite, vous créez un magasin de mémoire de chat pour enregistrer les conversations.
  • Vous créez une mémoire de chat fenêtrée pour conserver les derniers mouvements.
  • Dans la mémoire du chat, vous ajoutez un message "système" qui indique au modèle de chat qui il est censé être (par exemple, un joueur d'échecs expert). Le message "system" ajoute du contexte, tandis que les messages "user" et "AI" correspondent à 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 les blancs, sur laquelle vous itérez. La chaîne est exécutée à chaque fois avec le prochain coup des Blancs, et le modèle de chat répond avec le meilleur coup suivant.

Lorsque vous exécutez cette classe avec ces mouvements, 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 

Oh ! PaLM sait-il jouer aux échecs ? Pas exactement, mais lors de son entraînement, le modèle a dû voir des commentaires de parties d'échecs, voire les fichiers PGN (Portable Game Notation) de parties passées. Toutefois, ce chatbot ne gagnera probablement pas contre AlphaZero (l'IA qui bat les meilleurs joueurs de Go, de Shogi et d'échecs). De plus, la conversation risque de dérailler plus tard, car le modèle ne se souviendra pas vraiment de l'état actuel du jeu.

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

6. Extraire des informations à partir de 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. Toutefois, avec LangChain4j, vous pouvez également utiliser un modèle de chat pour extraire des informations structurées à partir de texte non structuré.

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

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 informations décrivant une personne (son nom et son âge).
  • L'interface PersonExtractor est créée avec une méthode qui, à partir d'une chaîne de texte non structurée, renvoie une instance Person instanciée.
  • Le extractPerson() est annoté avec une annotation @UserMessage qui lui associe une invite. Il s'agit de la requête que le modèle utilisera pour extraire les informations et renvoyer les détails sous la forme d'un document JSON qui sera analysé pour vous et désérialisé 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.
  • Vous pouvez ensuite simplement appeler Person person = extractor.extractPerson(...) pour extraire les informations sur la personne à partir du texte non structuré et obtenir une instance Person avec le nom et l'âge.

Exécutez maintenant cette classe avec la commande suivante :

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

> Task :app:run
Anna
23

Oui. Voici Anna, elle a 23 ans !

L'intérêt particulier de cette approche AiServices est que vous travaillez avec des objets fortement typés. Vous n'interagissez pas directement avec le LLM de chat. Au lieu de cela, 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. La notion de LLM est abstraite. En tant que développeur Java, vous ne faites que manipuler des classes et des objets normaux.

7. Génération augmentée par récupération : 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. Le modèle utilisera ces informations pour ancrer ses réponses, plutôt que d'essayer de générer des réponses issues de son entraînement. Ce modèle est appelé RAG, ou génération augmentée par récupération.

En résumé, la génération augmentée par récupération comporte deux phases :

  1. Phase d'ingestion : les documents sont chargés, divisés en plus petits blocs, et une représentation vectorielle (un embedding vectoriel) 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. Le LLM reçoit ensuite le contexte de la conversation et les extraits de texte correspondant aux vecteurs renvoyés par la base de données. Il est invité à ancrer sa réponse en examinant ces extraits.

2c279c506d7606cd.png

Préparer vos documents

Pour cette nouvelle démo, vous allez poser des questions sur l'architecture de réseau neuronal Transformer, lancée par Google, qui est la façon dont tous les grands modèles de langage modernes sont implémentés aujourd'hui.

Vous pouvez récupérer le document de recherche décrivant cette architecture ("Attention is all you need") en utilisant la commande wget pour télécharger le PDF depuis 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

Explorons, étape par étape, comment créer l'approche en deux phases, d'abord avec l'ingestion de documents, puis avec le temps de 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 à localiser le fichier PDF que nous téléchargeons et à préparer un 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 d'embedding. Il s'agit d'un modèle et d'un point de terminaison spécifiques dont le rôle est de créer des représentations vectorielles de fragments 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 classes pour collaborer :

  • Chargez et divisez le document PDF en blocs.
  • Créez des embeddings vectoriels pour tous ces blocs.
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 embeddings vectoriels.

Le document est divisé en blocs grâce à la classe DocumentSplitters. Le texte du fichier PDF sera divisé en extraits de 500 caractères, avec un chevauchement de 100 caractères (avec le bloc suivant, pour éviter de couper les mots ou les phrases).

L'ingestor du magasin relie le séparateur de documents, le modèle d'embedding pour calculer les vecteurs et la base de données vectorielle en mémoire. La méthode ingest() se chargera ensuite de l'ingestion.

La première phase est maintenant terminée. Le document a été transformé en blocs de texte avec leurs embeddings vectoriels associés et stocké dans la base de données vectorielle.

Poser des questions

Il est temps de vous préparer à poser des questions ! Le modèle de chat habituel peut être créé pour démarrer la conversation :

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 aurez également besoin d'une classe "retriever" qui reliera la base de données vectorielle (dans la variable embeddingStore) et le modèle d'embedding. Son rôle est d'interroger la base de données vectorielle en calculant un embedding vectoriel 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 autre nom pour le modèle de génération augmentée par 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" associe :

  • Modèle de langue de chat que vous avez configuré précédemment.
  • Le récupérateur compare une requête d'embedding vectoriel 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 les extraits pertinents de la documentation dont l'embedding vectoriel est semblable au vecteur de la question de l'utilisateur).

Vous êtes enfin 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 le contenu complet 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 venez de créer votre première application de chat d'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 langage conversationnels sont très puissants et capables de gérer diverses tâches telles que les questions/réponses, même sur votre propre documentation, l'extraction de données et, dans une certaine mesure, qu'ils étaient même capables de jouer aux échecs !

Et ensuite ?

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

Complément d'informations

Documents de référence