Que fait genai) ?
Problème principal résolu par cette compétence
De nombreux assistants IA traitent accidentellement chaque requête comme une demande sans état — ils téléchargent à nouveau l'intégralité du document, du code ou de l'état de l'espace de travail à chaque question de suivi ou modification séquentielle, ou aplatissent les tours précédents en un seul bloc de texte. Cela gaspille des tokens, augmente la latence et dégrade la qualité du raisonnement.
Au lieu de cela, maintenez une conversation multi-tour avec état où :
- Le contexte initial (code, document ou état du projet) est établi au tour 1.
- Les tours suivants continuent la même conversation (
previous_interaction_idou toursuser/modelstructurés danscontents) sans renvoyer l'état inchangé. - Le code ou l'état du document complet n'est renvoyé que lorsque l'utilisateur modifie manuellement l'espace de travail en dehors du flux de l'IA (détecté par comparaison de hachage SHA-256).
Patron 1 : État multi-tour géré par le serveur (API Interactions)
Lorsque vous utilisez l'API Interactions de Google (@google/genai >= 2.3.0), Gemini conserve le contexte de la conversation sur les serveurs de Google (store: true par défaut). Enchaînez les tours en passant previous_interaction_id: interaction.id :
TypeScript (@google/genai)
import { GoogleGenAI } from "@google/genai";
const client = new GoogleGenAI({});
// Tour 1 : Établir la conversation et l'état initial du document
const turn1 = await client.interactions.create({
model: "gemini-3.7-flash",
system_instruction: "Vous êtes un assistant codage expert. Souvenez-vous du code actif entre les tours.",
input: `Voici mon fichier actuel :\n\`\`\`ts\n${initialCode}\n\`\`\`\nAjoutez une validation d'entrée.`,
});
console.log(turn1.output_text);
// Tour 2 : Question de suivi ou modification séquentielle — NE renvoyez PAS le fichier s'il n'a pas changé !
// Le serveur de Google conserve déjà l'entrée du tour 1 et la sortie du modèle via previous_interaction_id.
const turn2 = await client.interactions.create({
model: "gemini-3.7-flash",
system_instruction: "Vous êtes un assistant codage expert. Souvenez-vous du code actif entre les tours.",
previous_interaction_id: turn1.id,
input: "Pourquoi avez-vous choisi ce motif de validation, et pouvons-nous aussi journaliser les entrées rejetées ?",
});
console.log(turn2.output_text);
Python (google-genai)
from google import genai
client = genai.Client()
# Tour 1 : Établir le contexte initial
turn1 = client.interactions.create(
model="gemini-3.7-flash",
system_instruction="Vous êtes un assistant codage expert. Souvenez-vous du code actif entre les tours.",
input=f"Voici mon fichier actuel :\n```py\n{initial_code}\n```\nRefactorisez ceci en une classe.",
)
# Tour 2 : Enchaîner avec previous_interaction_id sans retélécharger le fichier
turn2 = client.interactions.create(
model="gemini-3.7-flash",
system_instruction="Vous êtes un assistant codage expert. Souvenez-vous du code actif entre les tours.",
previous_interaction_id=turn1.id,
input="Ajoutez maintenant une méthode de test unitaire pour les cas limites.",
)
Règles importantes pour
interactions.create:
system_instruction,toolsetgeneration_configsont propres à l'interaction — spécifiez-les à nouveau à chaque tour.- Gardez
store: true(valeur par défaut) ;store: falsedésactiveprevious_interaction_id.
Patron 2 : contents multi-tour géré par l'application avec état de l'espace de travail synchronisé par hachage
Lorsque votre backend stocke les tours de conversation dans sa propre base de données (ou utilise ai.models.generateContent / ai.chats.create avec des schémas JSON structurés et des workflows de diff/apply), suivez ces cinq règles pour ne jamais renvoyer redondamment des fichiers ou sections d'espace de travail inchangés :
1. Utilisez des tableaux de tours natifs role: 'user' | 'model'
N'aplatissez jamais les tours précédents en une seule chaîne dans un seul message utilisateur. Construisez toujours un tableau contents multi-tour correct :
type ContentTurn = {
role: "user" | "model";
parts: Array<{ text: string }>;
};
2. Suivez la synchronisation de l'espace de travail par hachage SHA-256
Maintenez un enregistrement de synchronisation par conversation sur le client :
interface SyncedConversationState {
activeDocumentSha256: string | null;
secondaryContextHash: string | null; // ex. : autres onglets ouverts, scènes, ou résumé de l'arborescence du projet
}
- Lorsqu'un tour
ask(Q&R) se termine :activeDocumentSha256reste le SHA-256 du document qui a été envoyé. - Lorsqu'un tour
editse termine avec une proposition :activeDocumentSha256se met à jour avecproposal.sha256(car le tourmodeldans l'historique de la conversation contient maintenant le code proposé). - Lorsque l'utilisateur clique sur Appliquer :
activeDocumentSha256correspond auproposal.sha256appliqué. - Lorsque l'utilisateur clique sur Ignorer ou Annuler :
activeDocumentSha256revient aubase.sha256d'avant la proposition.
3. Omettez le code d'espace de travail inchangé lors des tours de suivi (sourceSynchronized: true)
Avant d'envoyer un nouveau tour, comparez sha256(currentEditorCode) avec syncedState.activeDocumentSha256 :
- Si
sha256(currentEditorCode) === syncedState.activeDocumentSha256(sourceSynchronized: true) :- En mode
Ask(Q&R) : Ne téléchargez pas le fichier du tout (source: null). Le modèle a déjà le code actif exact dans son historique de conversation. - En mode
Edit: Ne dupliquez pascurrentSourceà l'intérieur du nouveau touruserenvoyé à Gemini ({ mode: "edit", prompt, sourceSynchronized: true }). Conservez l'instantané local de base uniquement pour le diff côté client/Annulation et la validation côté serveur.
- En mode
- Si
sha256(currentEditorCode) !== syncedState.activeDocumentSha256(sourceSynchronized: false) :- L'utilisateur a modifié manuellement le fichier ou changé de fichier. Joignez le
currentSourcemis à jour à ce tour pour resynchroniser la conversation, et mettez à jouractiveDocumentSha256.
- L'utilisateur a modifié manuellement le fichier ou changé de fichier. Joignez le
4. Conservez la source complète une seule fois dans l'historique (compacter les tours obsolètes)
Dans une session d'édition de 10 tours, ne conservez pas 10 copies complètes du fichier dans les 10 tours historiques.
- Conservez le code complet uniquement sur le tour le plus récent qui a introduit ou modifié le code actif (soit le tour
modelqui a proposé la dernière édition active, soit le touruserqui a joint pour la dernière fois du code modifié manuellement). - Pour tous les tours plus anciens dont le code a été remplacé, remplacez la chaîne de code en ligne par des métadonnées compactes (
{ supersededSourceSha256: "..." }). Cela maintient l'utilisation des tokens stable sur les longues sessions tout en préservant le fil de raisonnement conversationnel complet.
5. Dédupliquez le contexte secondaire en lecture seule (autres fichiers / onglets / sections)
Si votre application inclut un résumé en lecture seule de fichiers ou sections non actifs :
- Envoyez ce résumé au tour 1 de la conversation.
- Aux tours 2+, ne renvoyez le résumé que si son hachage a changé (
secondaryContextHash !== currentSecondaryContextHash).
Implémentation de référence : Constructeur de requêtes multi-tour (TypeScript)
interface HistoryTurn {
prompt: string;
response: string;
mode: "ask" | "edit";
baseSource?: string | null;
baseSha256?: string | null;
proposalSource?: string | null;
proposalSha256?: string | null;
outcomeStatus?: "none" | "proposed" | "applied" | "discarded" | "undone";
}
export function buildMultiTurnContents(params: {
history: HistoryTurn[];
mode: "ask" | "edit";
prompt: string;
currentSource: string | null;
sourceSynchronized: boolean;
}) {
const { history, mode, prompt, currentSource, sourceSynchronized } = params;
// Identifier quel tour historique contient la source actuellement active
let latestActiveSourceIndex = -1;
if (sourceSynchronized || currentSource === null) {
for (let i = history.length - 1; i >= 0; i--) {
const item = history[i];
const hasValidProposal =
item.mode === "edit" &&
item.proposalSource &&
item.outcomeStatus !== "discarded" &&
item.outcomeStatus !== "undone";
if (hasValidProposal || item.baseSource) {
latestActiveSourceIndex = i;
break;
}
}
}
const contents: Array<{ role: "user" | "model"; parts: Array<{ text: string }> }> = [];
for (let i = 0; i < history.length; i++) {
const item = history[i];
const isLatestSourceTurn = i === latestActiveSourceIndex;
const hasActiveProposal =
item.mode === "edit" &&
Boolean(item.proposalSource) &&
item.outcomeStatus !== "discarded" &&
item.outcomeStatus !== "undone";
// Tour utilisateur dans l'historique
const userPayload: Record<string, unknown> = {
mode: item.mode ?? "ask",
prompt: item.prompt,
};
if (isLatestSourceTurn && !hasActiveProposal && item.baseSource) {
userPayload.currentSource = item.baseSource;
} else if (item.baseSha256) {
userPayload.supersededSourceSha256 = item.baseSha256;
}
contents.push({ role: "user", parts: [{ text: JSON.stringify(userPayload) }] });
// Tour modèle dans l'historique
const modelPayload: Record<string, unknown> = {
kind: item.mode === "edit" ? "code_proposal" : "answer",
explanation: item.response,
};
if (isLatestSourceTurn && hasActiveProposal && item.proposalSource) {
modelPayload.proposedSource = item.proposalSource;
} else if (item.proposalSha256) {
modelPayload.supersededProposalSha256 = item.proposalSha256;
}
contents.push({ role: "model", parts: [{ text: JSON.stringify(modelPayload) }] });
}
// Tour utilisateur final (actuel)
const omitDuplicateSource = sourceSynchronized && latestActiveSourceIndex !== -1;
const currentTurnPayload: Record<string, unknown> = { mode, prompt };
if (omitDuplicateSource) {
currentTurnPayload.sourceSynchronized = true;
} else {
currentTurnPayload.currentSource = currentSource;
}
contents.push({ role: "user", parts: [{ text: JSON.stringify(currentTurnPayload) }] });
return contents;
}
Liste de contrôle lors de la révision ou de la mise en œuvre de l'IA multi-tour
- Utilisons-nous
@google/genai(GoogleGenAI) et jamais l'ancien@google/generative-ai? - Le tour 2+ préserve-t-il les rôles multi-tour
user/model(ouprevious_interaction_id) au lieu d'aplatir l'historique en une seule chaîne de requête ? - Lors des questions de suivi (mode
Ask) lorsque le code de l'éditeur n'a pas changé, omettons-nous de retélécharger le code depuis le client ? - Lors des modifications séquentielles de l'IA (mode
Edit) lorsque le code de l'éditeur correspond au dernier tour, omettons-nous lecurrentSourcedupliqué dans la nouvelle requête utilisateur danscontents? - Les anciennes versions de code obsolètes sont-elles compactées dans l'historique de sorte que seule la version active du code soit envoyée en entier ?
- Si l'utilisateur modifie manuellement le document ou annule une modification, la différence de hachage SHA-256 déclenche-t-elle automatiquement une resynchronisation au tour suivant ?