Reprenez l’agent du chapitre précédent et menez-en une vraie conversation :
— Je m’appelle Léa. — Enchanté, Léa ! — Quel est mon prénom ? — Je ne dispose pas de cette information.
Frustrant, et pourtant logique : par défaut, chaque appel à l’agent repart d’une ardoise vierge. Le LLM est sans état, et rien ne conserve l’historique. Ce chapitre corrige cela avec une seule primitive : le checkpointer.
Pourquoi l’agent oublie
À chaque invoke, vous passez une liste de messages, l’agent la traite, renvoie un
résultat… et l’oublie. La liste vit le temps de l’appel, puis disparaît. Pour
qu’une conversation existe, il faut que quelqu’un conserve l’historique entre les
appels et le réinjecte au début du suivant.
Vous pourriez le faire à la main : stocker les messages dans une variable et les repasser à chaque fois. Ça marche pour un script. Mais ça ne survit pas à un redémarrage, ne gère pas plusieurs conversations simultanées, et ne s’étend pas. LangGraph industrialise tout cela.
Le checkpointer : sauvegarder l’état à chaque étape
Un checkpointer sauvegarde automatiquement l’état du graphe après chaque
étape. Pour l’activer, on le passe à compile() :
from langgraph.checkpoint.memory import InMemorySaver
from langgraph.prebuilt import create_react_agent
checkpointer = InMemorySaver()
agent = create_react_agent(modele, outils, checkpointer=checkpointer)
C’est tout. L’agent sait désormais persister son état. Reste à lui dire à quelle conversation rattacher cet état — c’est le rôle du thread.
Le thread : l’identité d’une conversation
Un thread regroupe tous les états d’une même conversation, identifié par un
thread_id. On le passe dans la configuration de l’appel :
config = {"configurable": {"thread_id": "lea-001"}}
agent.invoke({"messages": [{"role": "user", "content": "Je m'appelle Léa."}]}, config)
# Plus tard, MÊME thread_id :
rep = agent.invoke(
{"messages": [{"role": "user", "content": "Quel est mon prénom ?"}]},
config,
)
# → "Votre prénom est Léa."
La magie tient au thread_id partagé : avant d’exécuter le moindre nœud, le
checkpointer recharge l’historique du thread lea-001. On ne passe que le
nouveau message — le reducer add_messages le fusionne avec l’historique
rechargé.
Isoler plusieurs conversations
Le même agent gère sans effort des conversations parallèles : il suffit de
changer de thread_id. Chaque thread a sa mémoire, totalement isolée.
alice = {"configurable": {"thread_id": "alice"}}
bob = {"configurable": {"thread_id": "bob"}}
agent.invoke({"messages": [hello_alice]}, alice) # mémoire d'Alice
agent.invoke({"messages": [hello_bob]}, bob) # mémoire de Bob, séparée
C’est exactement ce qu’il faut pour un service réel : un agent, des milliers d’utilisateurs, chacun avec son fil — sans qu’ils ne se mélangent jamais.
Inspecter la mémoire
Comme l’état est persisté, on peut le relire à tout moment, sans relancer l’agent :
snapshot = agent.get_state(config)
for m in snapshot.values["messages"]:
m.pretty_print()
Pratique pour déboguer : on voit exactement ce que l’agent « a en tête » pour une conversation donnée.
La mémoire grandit — et c’est un problème
Une conversation longue gonfle l’historique, donc le nombre de tokens envoyés à chaque tour, donc le coût et la latence. À l’échelle, on ne garde pas tout indéfiniment : on résume les anciens messages, ou on n’en conserve qu’une fenêtre récente. Ces stratégies de gestion de la fenêtre de contexte sont un sujet à part entière, qu’on approfondira dans l’espace deep-dive.
Ce qu’il faut retenir
- Sans checkpointer, un agent oublie tout entre deux requêtes.
- Le checkpointer sauvegarde l’état après chaque étape ; le thread_id identifie la conversation à laquelle cet état appartient.
- Un même agent gère des conversations parallèles isolées, une par thread.
InMemorySaverpour apprendre ; un backend durable pour la production.
Votre agent agit et se souvient. Il ne lui manque qu’une chose pour être prêt au monde réel : que vous puissiez voir ce qu’il fait. Au dernier chapitre de ce parcours, on l’instrumente avec une première trace LangFuse.