Créer des sessions de chat persistantes

Beaucoup de vos utilisateurs interagiront avec des grands modèles de langage pour la première fois via des chatbots. Bien que les LLM soient capables de bien plus que de simuler des conversations, il reste un style d'interaction familier et utile. Même si vos utilisateurs n'interagissent pas directement avec le modèle de cette manière, le style conversationnel des requêtes est un moyen efficace d'influencer la sortie générée par un modèle d'IA.

Pour prendre en charge ce style d'interaction, Genkit fournit un ensemble d'interfaces et d'abstractions qui vous permettent de créer plus facilement des applications LLM basées sur le chat.

Avant de commencer

Avant de lire cette page, vous devez connaître le contenu de la page Générer du contenu avec des modèles d'IA.

Si vous souhaitez exécuter les exemples de code de cette page, suivez d'abord les étapes du guide Premiers pas. Tous les exemples supposent que vous avez déjà installé Genkit en tant que dépendance dans votre projet.

Principes de base des sessions Chat

Voici une application de chatbot minimale basée sur la console:

import { genkit } from "genkit";
import { googleAI, gemini15Flash } from "@genkit-ai/googleai";

import { createInterface } from "node:readline/promises";

const ai = genkit({
  plugins: [googleAI()],
  model: gemini15Flash,
});

(async () => {
  const chat = ai.chat();
  console.log("You're chatting with Gemini. Ctrl-C to quit.\n");
  const readline = createInterface(process.stdin, process.stdout);
  while (true) {
    const userInput = await readline.question("> ");
    const { text } = await chat.send(userInput);
    console.log(text);
  }
})();

Une session de chat avec ce programme se présente comme suit:

You're chatting with Gemini. Ctrl-C to quit.

> hi
Hi there! How can I help you today? 

> my name is pavel
Nice to meet you, Pavel! What can I do for you today? 

> what's my name?
Your name is Pavel! I remembered it from our previous interaction. 

Is there anything else I can help you with?

Comme vous pouvez le constater dans cette brève interaction, lorsque vous envoyez un message à une session de chat, le modèle peut utiliser la session jusqu'à présent dans ses réponses. Cela est possible, car Genkit effectue plusieurs tâches en arrière-plan:

  • Récupère l'historique des discussions, le cas échéant, à partir du stockage (plus d'informations sur la persistance et le stockage plus tard)
  • Envoie la requête au modèle, comme avec generate(), mais inclut automatiquement l'historique de chat.
  • Enregistre la réponse du modèle dans l'historique des discussions

Configuration de modèle

La méthode chat() accepte la plupart des mêmes options de configuration que generate(). Pour transmettre des options de configuration au modèle:

const chat = ai.chat({
  model: gemini15Pro,
  system:
    "You're a pirate first mate. Address the user as Captain and assist " +
    "them however you can.",
  config: {
    temperature: 1.3,
  },
});

Sessions de chat avec état

En plus de conserver l'historique des messages d'une session de chat, vous pouvez également conserver n'importe quel objet JavaScript arbitraire. Cela peut vous permettre de gérer l'état de manière plus structurée que de vous appuyer uniquement sur les informations de l'historique des messages.

Pour inclure un état dans une session, vous devez instancier une session explicitement:

interface MyState {
  userName: string;
}

const session = ai.createSession<MyState>({
  initialState: {
    userName: 'Pavel',
  },
});

Vous pouvez ensuite démarrer une discussion dans la session:

const chat = session.chat();

Pour modifier l'état de la session en fonction du déroulement de la discussion, définissez des outils et incluez-les dans vos requêtes:

const changeUserName = ai.defineTool(
  {
    name: 'changeUserName',
    description: 'can be used to change user name',
    inputSchema: z.object({
      newUserName: z.string(),
    }),
  },
  async (input) => {
    await ai.currentSession<MyState>().updateState({
      userName: input.newUserName,
    });
    return 'changed username to ${input.newUserName}';
  }
);
const chat = session.chat({
  model: gemini15Pro,
  tools: [changeUserName],
});
await chat.send('change user name to Kevin');

Sessions multithread

Une même session peut contenir plusieurs fils de discussion. Chaque thread possède son propre historique des messages, mais ils partagent un seul état de session.

const lawyerChat = session.chat('lawyerThread', {
  system: 'talk like a lawyer',
});
const pirateChat = session.chat('pirateThread', {
  system: 'talk like a pirate',
});

Persistance de session (Expérimental)

Lorsque vous initialisez une nouvelle discussion ou une nouvelle session, elle est configurée par défaut pour stocker la session en mémoire uniquement. Cette approche est adaptée lorsque la session ne doit persister que pendant la durée d'une seule invocation de votre programme, comme dans l'exemple de chatbot au début de cette page. Toutefois, lorsque vous intégrez le chat LLM dans une application, vous déployez généralement votre logique de génération de contenu en tant que points de terminaison d'API Web sans état. Pour que les chats persistants fonctionnent avec cette configuration, vous devez implémenter un type de stockage de session pouvant conserver l'état entre les invocations de vos points de terminaison.

Pour ajouter de la persistance à une session de chat, vous devez implémenter l'interface SessionStore de Genkit. Voici un exemple d'implémentation qui enregistre l'état de la session dans des fichiers JSON individuels:

class JsonSessionStore<S = any> implements SessionStore<S> {
  async get(sessionId: string): Promise<SessionData<S> | undefined> {
    try {
      const s = await readFile(`${sessionId}.json`, { encoding: 'utf8' });
      const data = JSON.parse(s);
      return data;
    } catch {
      return undefined;
    }
  }

  async save(sessionId: string, sessionData: SessionData<S>): Promise<void> {
    const s = JSON.stringify(sessionData);
    await writeFile(`${sessionId}.json`, s, { encoding: 'utf8' });
  }
}

Cette implémentation n'est probablement pas adaptée aux déploiements pratiques, mais elle illustre qu'une implémentation de stockage de session ne doit accomplir que deux tâches:

  • Obtenir un objet de session à partir de l'espace de stockage à l'aide de son ID de session
  • Enregistrer un objet de session donné, indexé par son ID de session

Une fois que vous avez implémenté l'interface de votre backend de stockage, transmettez une instance de votre implémentation aux constructeurs de session:

// To create a new session:
const session = ai.createSession({
  store: new JsonSessionStore(),
});

// Save session.id so you can restore the session the next time the
// user makes a request.
// If the user has a session ID saved, load the session instead of creating
// a new one:
const session = await ai.loadSession(sessionId, {
    store: new JsonSessionStore(),
});