Aller au contenu principal

Context variables et mémoire d'agent

Mis à jour le 29 juillet 2026

Context variables et mémoire d’agent

Un agent sans mémoire oublie tout entre chaque exécution : il redemande le nom du client à chaque message et repart de zéro à chaque tour. Deux mécanismes complémentaires corrigent cela. Les context variables injectent un état partagé dans l’agent et ses tools, tandis que la mémoire conversationnelle maintient le fil d’une conversation multi-tours.

Context variables : état partagé

Le RunContextWrapper est un objet typé qui circule entre l’agent, ses tools et ses guardrails. Concrètement, vous définissez une dataclass décrivant la session — identité, panier, préférences — et chaque tool la reçoit en premier paramètre. Dans l’exemple qui suit, ajouter_au_panier écrit dans cet objet et voir_panier le relit : les deux tools partagent le même panier sans qu’aucune donnée transite par le prompt.

from dataclasses import dataclass, field
from agents import Agent, Runner, RunContextWrapper, function_tool

@dataclass
class SessionUtilisateur:
    user_id: str
    nom: str
    email: str
    panier: list[dict] = field(default_factory=list)
    preferences: dict = field(default_factory=dict)

@function_tool
def ajouter_au_panier(
    ctx: RunContextWrapper[SessionUtilisateur],
    produit: str,
    quantite: int = 1,
) -> str:
    """Ajoute un produit au panier de l'utilisateur."""
    ctx.context.panier.append({"produit": produit, "quantite": quantite})
    total = len(ctx.context.panier)
    return f"{produit} (x{quantite}) ajouté au panier. Total : {total} article(s)."

@function_tool
def voir_panier(ctx: RunContextWrapper[SessionUtilisateur]) -> str:
    """Affiche le contenu du panier."""
    if not ctx.context.panier:
        return "Votre panier est vide."
    lignes = [f"- {item['produit']} x{item['quantite']}" for item in ctx.context.panier]
    return f"Panier de {ctx.context.nom} :\n" + "\n".join(lignes)

agent = Agent(
    name="Agent boutique",
    instructions="Vous aidez les clients à faire leurs achats.",
    tools=[ajouter_au_panier, voir_panier],
    model="gpt-5.6-terra",
)

session = SessionUtilisateur(
    user_id="usr_789",
    nom="Marie Dupont",
    email="[email protected]",
)

# Le contexte est partagé entre tous les tools
result = Runner.run_sync(agent, "Ajoutez un laptop et deux souris à mon panier", context=session)
print(result.final_output)
print(f"Panier après exécution : {session.panier}")

Le contexte est mutable : les tools peuvent le modifier, et les modifications persistent après l’exécution. C’est ce qui rend la dernière ligne intéressante — après le run_sync, l’objet session de votre application contient réellement les deux articles, prêts à être envoyés à votre système de commande.

Instructions dynamiques

Cet état ne sert pas qu’aux tools : il peut aussi façonner le comportement de l’agent. Les instructions peuvent être une fonction qui reçoit le contexte et fabrique le prompt système à la volée, avec le nom du client et sa langue de préférence.

def instructions_dynamiques(ctx: RunContextWrapper[SessionUtilisateur]) -> str:
    prefs = ctx.context.preferences
    langue = prefs.get("langue", "français")
    return f"""Vous êtes un assistant commercial.
    Le client s'appelle {ctx.context.nom}.
    Son email est {ctx.context.email}.
    Il a {len(ctx.context.panier)} article(s) dans son panier.
    Communiquez en {langue}."""

agent = Agent(
    name="Agent personnalisé",
    instructions=instructions_dynamiques,
    tools=[ajouter_au_panier, voir_panier],
    model="gpt-5.6-terra",
)

Les instructions sont évaluées à chaque tour de la boucle agent, ce qui permet une personnalisation en temps réel : si un tool vient d’ajouter un article, le tour suivant en tient compte immédiatement dans le prompt système.

Mémoire conversationnelle multi-tours

Le contexte transporte des données, pas le dialogue lui-même. Pour maintenir une conversation sur plusieurs échanges, passez l’historique. Le principe tient en trois gestes : vous ajoutez le message de l’utilisateur, vous exécutez l’agent sur la liste complète, puis vous remplacez votre historique par celui que renvoie le résultat — messages, appels de tools et réponses compris.

from agents import Agent, Runner

agent = Agent(
    name="Assistant",
    instructions="Vous êtes un assistant qui se souvient de la conversation.",
    model="gpt-5.6-terra",
)

historique = []

async def converser(message_utilisateur: str) -> str:
    # Ajouter le message de l'utilisateur à l'historique
    historique.append({"role": "user", "content": message_utilisateur})

    # Exécuter l'agent avec l'historique complet
    result = await Runner.run(agent, historique)

    # Sauvegarder les nouveaux échanges pour le prochain tour
    historique.clear()
    historique.extend(result.to_input_list())

    return result.final_output

# Simulation d'une conversation
async def demo():
    print(await converser("Je m'appelle Thomas."))
    print(await converser("Quel est mon prénom ?"))  # L'agent se souvient
    print(await converser("Recommandez-moi un bon restaurant à Paris."))

Cette mémoire vit en RAM. Elle disparaît au redéploiement de votre service, et elle grossit indéfiniment si la conversation dure : deux limites à garder en tête avant la mise en production.

Pattern : mémoire persistante avec base de données

Pour une mémoire qui survit aux redémarrages, stockez l’historique en base. L’approche présentée ici va un cran plus loin : au lieu de tout conserver, l’agent décide lui-même de ce qui mérite d’être retenu, via une paire de tools de sauvegarde et de récupération. Le client dit « je suis allergique aux arachides », l’agent appelle sauvegarder_note, et l’information reste disponible trois semaines plus tard.

import json
from agents import Agent, Runner, function_tool

@function_tool
async def sauvegarder_note(ctx: RunContextWrapper, cle: str, valeur: str) -> str:
    """Sauvegarde une information pour s'en souvenir plus tard."""
    # En production : écriture en base de données
    notes = ctx.context.notes
    notes[cle] = valeur
    return f"Note sauvegardée : {cle} = {valeur}"

@function_tool
async def recuperer_note(ctx: RunContextWrapper, cle: str) -> str:
    """Récupère une information sauvegardée précédemment."""
    notes = ctx.context.notes
    valeur = notes.get(cle)
    if valeur:
        return f"{cle} = {valeur}"
    return f"Aucune note trouvée pour '{cle}'"

@dataclass
class ContexteMemoire:
    user_id: str
    notes: dict = field(default_factory=dict)

agent = Agent(
    name="Assistant avec mémoire",
    instructions="""Vous êtes un assistant avec une mémoire persistante.
    Utilisez sauvegarder_note pour retenir des informations importantes
    et recuperer_note pour les retrouver.""",
    tools=[sauvegarder_note, recuperer_note],
    model="gpt-5.6-terra",
)

Contexte vs. instructions : quand utiliser quoi

Ces quatre mécanismes se recouvrent partiellement, et l’erreur classique consiste à tout faire passer par les instructions. Le tableau ci-dessous résume l’affectation à retenir.

BesoinSolution
Données utilisateur (nom, rôle)Context variables
Comportement de l’agentInstructions (statiques ou dynamiques)
Historique de conversationresult.to_input_list()
Mémoire long termeTools + base de données
Configuration runtimeContext variables

Points clés à retenir

  • RunContextWrapper transporte un état typé accessible par tous les tools
  • Le contexte est mutable : les tools peuvent le modifier pendant l’exécution
  • Les instructions dynamiques (fonctions) s’adaptent au contexte à chaque tour
  • L’historique conversationnel se maintient avec result.to_input_list()
  • Pour une mémoire persistante, combinez des tools de sauvegarde avec une base de données