De un chatbot à nœud unique à un agent pris en charge par MCP dans LangGraph
Créez une application LangGraph couche par couche : états et réducteurs, arêtes, boucles d’outils, threads avec sauvegardes intermédiaires, trois modes de streaming, ainsi que des outils fournis via MCP.
Sources: Building AI Applications with LangGraph: From a Basic Chatbot to Tools, Memory, Streaming and MCP
La plupart des prototypes de LLM commencent par une seule action : envoyer un prompt et afficher la réponse. Cette structure s’effondre dès que l’application a besoin d’une recherche sur le Web, d’une décision quant à l’étape suivante, de la mémoire des échanges précédents ou d’une mise à jour en temps réel dans l’interface utilisateur. LangGraph résout ce problème en faisant du flux de contrôle un graphe explicite basé sur un état partagé. Cette présentation montre comment transformer une application de chatbot à nœud unique en un agent ReAct capable d’utiliser des outils, disposant d’une mémoire sauvegardée, de fonctionnalités de streaming et d’outils fournis par un serveur MCP distinct, tout en soulignant les détails qui ont tendance à poser des problèmes au cours du processus.
Le modèle mental : l’état circule entre les nœuds le long des arêtes
L’intégration classique d’un LLM suit une ligne droite allant du prompt au modèle pour obtenir une réponse. LangGraph, quant à lui, vous demande de penser en cycle : un objet state entre dans le graphe, les nodes le lisent et renvoient des modifications, les edges déterminent quel node sera exécuté ensuite, et l’état mis à jour ressort en fin de processus. Les trois éléments constitutifs sont donc :
- State : les données qui circulent entre les étapes, comme l’historique des messages.
- Nodes : des fonctions ordinaires qui effectuent une tâche précise et renvoient une mise à jour partielle.
- Edges : les règles qui relient les nodes, y compris le point de départ et la fin d’un exécution.
Le graphe le plus simple possible comporte un seul node entre le point d’entrée et le point de sortie :
┌─────────────┐
│ State │
└──────┬──────┘
│
▼
┌─────────────┐
│ Node │
│ chatbot │
└──────┬──────┘
│
▼
┌─────────────┐
│ END │
└─────────────┘
La séparation de « quels données existent » et de « quoi s’exécute dans quel ordre » constitue le choix fondamental de conception. Vous pouvez modifier le flux sans toucher à la manière dont les données sont stockées, et vous pouvez examiner l’état à n’importe quel moment sans devoir effectuer une ingénierie inverse d’une chaîne d’appels de fonctions.
Pourquoi un graphe plutôt qu’une chaîne d’appels
User
↓
LLM
↓
Response
Ajoutons maintenant des exigences réelles : une recherche sur le web, plusieurs outils, une logique de branchement, de la mémoire, une étape d’approbation par un humain, plusieurs agents, des résultats diffusés en continu, ainsi qu’un comportement différent selon ce que le modèle renvoie. Chacun de ces éléments ajoute une branche ou un boucle, et les instructions if imbriquées autour des appels API deviennent rapidement difficiles à suivre. Même le cas le plus courant, « appeler un outil si le modèle en demande un », contient déjà un cycle :
┌─────────────┐
│ LLM │
└──────┬──────┘
│
Need a tool?
/ \
Yes No
↓ ↓
Tool Response
│
↓
LLM
Lorsque le flux devient un graphe, les branches et les boucles deviennent des objets visibles que l’on peut nommer, tester et dessiner, au lieu de représenter des flux de contrôle cachés à l’intérieur de fonctions d’aide.
Étape 1 : un chatbot à un seul nœud avec StateGraph
La première version est délibérément simple, avec un seul nœud relié entre les marqueurs de début et de fin :
START
↓
Chatbot Node
↓
END
Définition de l’état et de son réducteur
Pour un chatbot, l’état nécessite principalement l’historique des conversations. Une première tentative consiste à utiliser un TypedDict contenant une liste messages :
from typing import TypedDict
class State(TypedDict):
messages: list
Conceptuellement, cette liste accumule les tours alternés de l’humain et de l’IA :
State
└── messages
├── HumanMessage
├── AIMessage
├── HumanMessage
└── AIMessage
Un détail est très important ici. Dans LangGraph, chaque clé de l’état peut disposer d’un réducteur qui détermine comment la valeur retournée par un nœud est fusionnée avec la valeur existante. Une annotation list simple ne possède pas de réducteur ; par conséquent, un nœud qui renvoie {"messages": [reply]} remplace toute la liste au lieu de s’y ajouter. Pour l’historique de chat, on souhaite presque toujours utiliser le réducteur add_messages, déclaré sous la forme Annotated[list, add_messages] (ou en héritant de MessagesState préconstruit). Avec celui-ci, les messages renvoyés sont ajoutés, et ceux ayant un ID correspondant sont mis à jour sur place. Le fragment ci-dessus convient bien comme illustration mentale, mais il faut utiliser la forme annotée dans le code réel.
Rédaction du nœud du chatbot
Un nœud n’est rien d’autre qu’une fonction qui reçoit l’état actuel et renvoie les clés qu’elle souhaite modifier. Le nœud du chatbot transmet l’historique au modèle et renvoie la réponse :
def chatbot(state: State):
return {
"messages": [
llm.invoke(state["messages"])
]
}
Lisez-le de l’intérieur vers l’extérieur. Toute l’état est transmise à la fonction :
state
Elle extrait l’historique de la conversation jusqu’à présent :
state["messages"]
Elle envoie cet historique au modèle :
llm.invoke(state["messages"])
Puis elle enveloppe la réponse du modèle dans un dictionnaire dont les clés correspondent aux champs mis à jour. Notez que le nœud ne modifie pas directement l’état ; il renvoie une mise à jour partielle et laisse LangGraph la fusionner :
{
"messages": [
llm.invoke(state["messages"])
]
}
En termes de flux de données, le nœud se situe entre deux versions du même état :
State
│
│ messages
▼
┌─────────────┐
│ Chatbot │
│ Node │
└──────┬──────┘
│
│ LLM Response
▼
State
Enregistrement du nœud
La fonction devient partie intégrante du graphe une fois qu’elle a été enregistrée dans l’outil de construction sous un nom :
graph_builder.add_node(
"llmchatbot",
chatbot
)
Le premier argument est l’identifiant du nœud, auquel se référeront les arêtes et les journaux :
"llmchatbot"
Le deuxième argument est la fonction appelable qui s’exécute chaque fois que l’exécution atteint ce nœud :
chatbot
Étape 2 : connexion des arêtes et compilation
Un nœud seul ne s’exécute jamais ; les arêtes indiquent à LangGraph comment y accéder et où aller ensuite. Ce graphe en nécessite deux. La première relie le marqueur spécial START au chatbot :
graph_builder.add_edge(
START,
"llmchatbot"
)
En forme de diagramme, l’exécution entre dans le graphe et se dirige directement vers le nœud :
START
↓
llmchatbot
La deuxième arête envoie le contrôle du chatbot vers END, ce qui termine l’exécution :
graph_builder.add_edge(
"llmchatbot",
END
)
Cela correspond à :
llmchatbot
↓
END
Together they form the complete, if minimal, graph:
START
│
▼
┌────────────────┐
│ llmchatbot │
│ Node │
└────────┬───────┘
│
▼
END
Compiling and invoking
La compilation valide la structure (par exemple, que chaque arête pointe vers un nœud connu) et génère un objet exécutable :
graph = graph_builder.compile()
On le lance en passant un état initial. Les dictionnaires simples de rôles/contenus sont acceptés et convertis en objets de message LangChain :
result = graph.invoke({
"messages": [
{
"role": "user",
"content": "What is LangGraph?"
}
]
})
Au niveau interne, l’état d’entrée se trouve à START ; le nœud du chatbot appelle le modèle, la réponse est intégrée à l’état, et l’exécution se termine :
Input State
↓
START
↓
llmchatbot
↓
LLM
↓
Updated State
↓
END
La valeur de retour de invoke est l’état final, donc la réponse du modèle correspond à la dernière entrée dans result["messages"].
Étape 3 : affichage du graphe
Un graphe compilé peut s’afficher automatiquement. Dans un notebook Jupyter, les outils d’affichage d’IPython affichent un PNG généré à partir d’une description Mermaid du graphe :
from IPython.display import Image, display
display(
Image(
graph.get_graph().draw_mermaid_png()
)
)
Pour ce graphe, l’image est courte :
┌───────┐
│ START │
└───┬───┘
│
▼
┌──────────────┐
│ llmchatbot │
└──────┬───────┘
│
▼
┌───────┐
│ END │
└───────┘
La valeur n’est pas évidente avec trois cases, mais elle augmente rapidement. Une fois que l’on dispose d’arêtes conditionnelles, de nœuds d’outil et de boucles, un diagramme affiché est le moyen le plus rapide de confirmer que le graphe construit correspond bien à celui que l’on voulait créer. Notez que draw_mermaid_png() affiche par défaut via un service externe Mermaid, ce qui nécessite un accès à Internet ; si celui-ci n’est pas disponible, draw_mermaid() renvoie le texte Mermaid, que vous pouvez afficher ailleurs.
Étape 4 : fournir un outil de recherche au modèle
Le chatbot ne peut répondre qu’avec les informations apprises par le modèle pendant l’entraînement. Une question concernant les derniers développements en matière d’intelligence artificielle agente nécessite des informations fraîches, c’est pourquoi l’étape suivante ajoute un outil de recherche sur le Web. L’exemple utilise Tavily Search, et le flux souhaité est le suivant :
User
↓
LLM
↓
Search required?
↓
Tavily Search
↓
Search Results
↓
LLM
↓
Final Answer
Lier des outils n’est pas la même chose que les exécuter
La première moitié du travail consiste en la liaison des outils : indiquer au modèle quels outils existent et quels arguments ils acceptent.
llm_with_tools = llm.bind_tools(
[tavily_search]
)
Après cette liaison, le modèle peut répondre par une invocation structurée d’outil (“appeler tavily_search avec cette requête”) au lieu de texte brut. Il ne peut pas exécuter lui-même cette invocation. Quelque chose dans votre application doit détecter l’invitation à utiliser un outil, l’exécuter, ajouter le résultat sous forme de ToolMessage, puis renvoyer tout cela au modèle. Fournir à un modèle l’accès à un outil ne fait pas de lui un agent ; c’est le flux d’exécution qui l’est. C’est précisément ce que fournit le graphe.
Étape 5 : transformer cela en boucle ReAct
ReAct combine la réflexion et l’action : le modèle réfléchit à la tâche, décide s’il doit agir, exécute l’action à l’aide d’un outil, observe le résultat et continue. En termes de graphe, la ligne droite précédente :
START
↓
LLM
↓
END
devient un graphe comportant un point de décision et un chemin qui ramène au modèle :
START
│
▼
Agent
│
▼
┌─────────┐
│ LLM │
└────┬────┘
│
Need a tool?
/ \
Yes No
│ │
▼ ▼
Tool END
│
▼
Agent
│
▼
LLM
Dans le code de LangGraph, cette décision correspond à un arête conditionnelle. Le ToolNode prédéfini exécute ce que contient le dernier message d’IA envoyé par l’outil correspondant, et tools_condition redirige vers ce nœud lorsque des appels d’outil sont présents, ou vers END lorsqu’ils ne le sont pas. Un arête allant du nœud de l’outil vers le modèle ferme la boucle. L’aide prédéfinie create_react_agent assemble pour vous la même structure ; consultez la documentation actuelle de LangGraph pour connaître son emplacement exact, car les aides d’agent prédéfinies ont changé de package au fil du temps.
Suivi d’un passage à travers la boucle
Supposons que l’utilisateur demande les dernières nouvelles concernant LangGraph. Une exécution de la boucle produit cette séquence de messages et d’étapes :
HumanMessage
↓
Agent
↓
LLM
↓
"I need current information."
↓
Tool Call
↓
Tavily Search
↓
Search Results
↓
ToolMessage
↓
Agent
↓
LLM
↓
Final Answer
La différence structurelle importante réside dans le cycle. Le premier chatbot suivait une ligne droite :
START → LLM → END
Le graphe ReAct peut reconsulter le modèle autant de fois que nécessaire :
START
↓
Agent
↓
LLM
↓
Tool?
┌───────┐
│ │
Yes No
│ │
↓ ↓
Tool END
│
↓
Agent
Puisque les boucles peuvent, en principe, tourner indéfiniment, LangGraph impose une limite de récursivité à chaque exécution (configurable via la valeur recursion_limit). Si un modèle continue de demander des outils, vous obtiendrez une erreur plutôt qu’une facture infinie.
Étape 6 : mémoriser les conversations avec MemorySaver
Jusqu’à présent, chaque appel à invoke commence à zéro. Prenons en compte deux échanges : d’abord, l’utilisateur se présente :
User:
My name is Himanshi.
Puis, dans une demande distincte, il pose une question complémentaire :
User:
What is my name?
La deuxième étape ne peut être traitée que si le graphe contient encore la première. LangGraph résout ce problème grâce à un checkpointer : après chaque étape, il enregistre une capture de l’état, et la prochaine exécution sur la même conversation reprend à partir de cette capture. MemorySaver est l’implémentation la plus simple :
┌─────────────┐
│ Agent │
└──────┬──────┘
│
▼
┌───────────┐
│ State │
└─────┬─────┘
│
▼
┌─────────────┐
│ MemorySaver │
└─────────────┘
On l’attache au moment de la compilation avec graph_builder.compile(checkpointer=memory). Au lieu que chaque appel constitue une exécution isolée, chaque exécution fait désormais partie d’une conversation, et son état est conservé entre les exécutions.
Les threads séparent les conversations
Le élément qui relie les exécutions entre elles est un identifiant de thread, transmis dans la configuration sous la forme {"configurable": {"thread_id": "..."}}. Chaque thread possède son propre historique ainsi que son propre état actuel :
Thread 1
├── Message 1
├── Message 2
├── Message 3
└── Current State
Thread 2
├── Message 1
├── Message 2
└── Current State
Ainsi, deux utilisateurs sur deux threads ne voient jamais les messages de l’autre :
User A → Thread A → State A
User B → Thread B → State B
Deux précautions s’imposent. MemorySaver conserve tout en mémoire de processus, de sorte que toute l’historique disparaît à la redémarrage et n’est pas partagé entre les instances du serveur ; les déploiements en production utilisent un point de contrôle persistant tel que ceux de Postgres ou SQLite. Dans les versions récentes, la classe est également exportée sous le nom de InMemorySaver. Pour en savoir plus sur ce qu’un point de contrôle stocke réellement, consultez comment les points de contrôle, les écritures et les blobs s’intègrent dans InMemorySaver.
Étape 7 : diffusion de l’état au fur et à mesure que le graphe s’exécute
Tout ce qui a été mentionné ci-dessus utilisait une appel bloquant :
graph.invoke(...)
Il attend que tout le graphe soit terminé avant de renvoyer des résultats. Avec des agents à plusieurs étapes, cela peut signifier plusieurs secondes sans rien afficher à l’écran, ce qui est perçu comme un défaut par les utilisateurs. Le graphe compilé expose également la fonction stream, qui génère des résultats tant que l’exécution est en cours.
stream_mode="values" : l’état complet après chaque étape
Le premier mode à connaître est :
stream_mode="values"
Lorsqu’il est utilisé dans une boucle, il fournit un aperçu de l’état complet après chaque étape :
for chunk in graph.stream(
input_state,
stream_mode="values"
):
print(chunk)
Chaque partie montre à quoi ressemble l’état global à ce moment-là, permettant ainsi de voir comment il évolue au fur et à mesure que les nœuds et outils s’exécutent :
START
↓
State Update #1
↓
Node Execution
↓
State Update #2
↓
Tool Execution
↓
State Update #3
↓
END
Cela est pratique lorsque l’état est de petite taille et que l’on souhaite simplement afficher « la dernière version » à chaque fois.
stream_mode="updates"> : uniquement ce que chaque nœud a modifié
Le deuxième mode est :
stream_mode="updates"
La différence réside dans ce que contient chaque morceau de données. Dans un mode, vous recevez l’état complet :
values
Dans l’autre mode, vous ne recevez que la différence, identifiée par le nom du nœud qui l’a générée :
updates
Un moyen rapide de se souvenir de cette distinction :
- values répondent à la question « À quoi ressemble l’état en ce moment ? »
- updates répondent à la question « Quel est le changement apporté par ce nœud en particulier ? »
updates est généralement le meilleur choix pour le débogage et l’enregistrement, car il indique quel nœud est responsable de chaque changement et permet de garder les morceaux de données de petite taille lorsque l’état est volumineux. Aucun des deux modes ne transmet des tokens individuels provenant du modèle ; pour une sortie token par token, il existe un mode de flux messages distinct, et vous pouvez passer une liste de modes pour en recevoir plusieurs en même temps.
Étape 8 : visibilité fine avec astream_events
Les captures d’état et les deltas de nœuds vous indiquent ce qui a changé, mais pas tout ce qui s’est produit. astream_events va un niveau plus profond et émet un événement pour chaque moment du cycle de vie de chaque composant au cours de l’exécution :
Graph Execution
│
├── Graph Started
│
├── Node Started
│
├── LLM Started
│
├── LLM Streaming
│
├── LLM Completed
│
├── Tool Started
│
├── Tool Completed
│
├── Node Completed
│
└── Graph Completed
Vous le consommez de manière asynchrone. Notez que l’appel print dans le fragment ci-dessous doit être indenté à l’intérieur du corps de la boucle ; comme le montre l’exemple, Python le rejetterait en raison d’une erreur d’indentation :
async for event in graph.astream_events(
input_state,
version="v2"
):
print(event)
Chaque événement contient un type (par exemple on_chat_model_stream ou on_tool_start), le nom du composant et ses données. Cela rend le flux d’événements utile pour :
- le débogage et l’enregistrement structuré des logs
- l’observabilité générale des exécutions d’agents
- le suivi de chaque appel à un LLM et de chaque appel à une outil
Les ingénieurs ayant une expérience en backend reconnaîtront ce schéma : il s’agit davantage de tracer une requête distribuée que de lire la valeur de retour d’une seule fonction. La question passe de « L’agent a-t-il fonctionné ? » à « Qu’a exactement fait l’agent, dans quel ordre, et combien de temps a duré chaque étape ? »
Comment les éléments s’assemblent
Au regard du passé, chaque étape a ajouté une fonctionnalité au même squelette. Tout a commencé par une seule appel à un modèle :
START
↓
LLM
↓
END
Les outils ont ajouté une liaison aller-retour avec le monde extérieur :
START
↓
LLM
↓
Tool
↓
LLM
↓
END
Le mécanisme de point d’arrêt a permis au cycle de mémoriser :
START
↓
Agent
↓
LLM ↔ Tools
↓
Memory / Checkpoint
↓
END
Le streaming et les événements ont mis en visibilité l’exécution tant pour l’interface utilisateur que pour le suivi :
Graph
↓
Events
↓
UI / Logs / Observability
Aucune de ces étapes n’a nécessité la réécriture des étapes précédentes ; chacune représente un nouveau nœud, une nouvelle arête, une nouvelle option de compilation ou une nouvelle appel de méthode au sein du même graphe.
Étape 9 : placer les outils derrière un serveur MCP
Jusqu’à présent, les outils se trouvaient à l’intérieur de l’application. Le Model Context Protocol (MCP) adopte une approche différente : un serveur distinct expose les outils (et les ressources), et tout client MCP peut les découvrir et les utiliser. L’application n’a plus besoin d’inclure elle-même toutes les implémentations des outils :
┌──────────────┐
│ MCP Client │
└──────┬───────┘
│
│
▼
┌──────────────┐
│ MCP Server │
└──────┬───────┘
│
┌──────────┼──────────┐
↓ ↓ ↓
add() multiply() weather()
Un serveur minimal avec trois outils
Un petit serveur pédagogique suffit pour comprendre l’architecture. Il expose un outil d’addition :
add(a, b)
un outil de multiplication :
multiply(a, b)
et un outil météo qui prend en entrée le nom d’une ville :
weather(city)
Outil météo qui ne fait pas appel à un service météorologique réel. Il renvoie une chaîne de caractères fixe, ce qui met l’accent sur le protocole plutôt que sur les clés API :
"It's sunny in Mumbai."
Il s’agit du trajet aller-retour, et non de la prévision météorologique :
Client
↓
MCP Server
↓
Tool
↓
Result
↓
Client
Les outils n’ont plus besoin d’exister au sein de l’application qui les utilise
L’idée principale est que les outils peuvent être hébergés indépendamment des applications qui les emploient. L’application communique avec un client MCP, le client utilise le protocole pour communiquer avec le serveur, et c’est le serveur qui gère les implémentations :
Application
│
▼
MCP Client
│
MCP Protocol
│
▼
MCP Server
/ | \
/ | \
↓ ↓ ↓
Add Multiply Weather
Cela rend possibles des serveurs d’outils réutilisables. Une équipe peut gérer un serveur qui expose toute une catégorie de fonctionnalités :
Database Tools
File Tools
Git Tools
Search Tools
Weather Tools
Internal APIs
et plusieurs agents différents, clients de messagerie ou intégrations IDE peuvent utiliser le même serveur sans dupliquer de code.
Étape 10 : combiner LangGraph et MCP
Ces deux technologies résolvent des aspects différents du problème. LangGraph gère le flux de travail : l’état, le routage, les boucles, la mémoire et le streaming. Le standard MCP normalise la manière dont les outils et ressources sont exposés. Dans une conception combinée, le nœud d’outil de l’agent communique avec un client MCP au lieu d’appeler des fonctions locales :
User
│
▼
LangGraph Agent
│
▼
LLM
│
Need a tool?
│
▼
MCP Client
│
▼
MCP Server
/ | \
↓ ↓ ↓
Math Search Weather
L’agent n’a pas besoin de connaître la manière dont un outil est implémenté. Il lui suffit de savoir quels fonctionnalités existent et comment les appeler, le serveur MCP s’occupant du reste. En Python, le paquet langchain-mcp-adapters est une méthode courante pour charger des outils MCP en tant qu’outils LangChain, afin de pouvoir les lier à un modèle et les utiliser dans un ToolNode. L’article existant sur la standardisation de l’intégration des outils dans LangGraph avec MCP aborde cette intégration plus en détail.
Un modèle mental complet
En réunissant tout cela, un agent LangGraph fonctionne de la manière suivante : l’entrée de l’utilisateur devient un état, les nœuds y opèrent, le modèle décide s’il est nécessaire d’utiliser un outil, les résultats de l’outil sont renvoyés au modèle, et l’exécution se termine par une réponse :
USER
│
▼
┌────────────┐
│ LangGraph │
│ Agent │
└──────┬─────┘
│
▼
STATE
│
┌─────────────┼─────────────┐
│ │ │
▼ ▼ ▼
NODE NODE NODE
│ │ │
└─────────────┼─────────────┘
│
▼
LLM
│
Need a tool?
/ \
Yes No
│ │
▼ ▼
TOOL RESPONSE
│
▼
RESULT
│
└──────► LLM
│
▼
END
Au sein de cette boucle principale se trouvent les fonctionnalités que vous activez selon les besoins :
┌──────────────────────────────┐
│ Capabilities │
│ │
│ Memory / Checkpointing │
│ Streaming │
│ Event Tracking │
│ MCP │
│ Tools │
└──────────────────────────────┘
Où LangChain s’arrête et LangGraph commence
Puisque LangGraph est généralement appris après LangChain, il est utile de séparer leurs rôles. LangChain fournit les éléments de base pour les applications basées sur des LLM : des enveloppes de modèles, des messages, des outils, des prompts et des sorties structurées. LangGraph orchestre ces éléments pour créer des workflows explicites et gérant un état. Il s’agit de couches complémentaires, non de choix concurrents, et la plupart des codes LangGraph utilisent des composants LangChain à l’intérieur de leurs nœuds.
Leçons applicables aux graphes plus importants
Quelques principes issus de cette petite implémentation s’appliquent sans modification à des systèmes bien plus grands.
L’état est l’élément central
Au lieu d’envoyer des données via les arguments de fonction, chaque nœud lit dans et écrit sur un état partagé, et les réducteurs définissent comment les mises à jour sont fusionnées :
State
↓
Node
↓
Updated State
↓
Next Node
Les nœuds sont des unités de travail
Chaque nœud doit effectuer une tâche reconnaissable, ce qui facilite la décomposition et le test des workflows complexes :
chatbot node
search node
tool node
validation node
human approval node
Les arêtes codent le workflow
Une arête répond à une question : une fois ce nœud terminé, où se poursuit l’exécution ? Les arêtes simples permettent de définir des séquences ; les arêtes conditionnelles génèrent des branches et des boucles.
Les diagrammes valent mieux que la lecture de code
Pour un graphe comportant de nombreux nœuds et des chemins conditionnels, un diagramme affiché communique l’architecture plus rapidement que la lecture du code de construction :
Node → Decision → Tool → Agent → END
Les points de contrôle sont plus que de l’historique de chat
Un checkpointer conserve l’état complet du graphe, et non seulement les messages. C’est ce qui permet des conversations à plusieurs tours, et c’est également la base pour des agents fonctionnant sur de longues périodes, ainsi que pour les pauses avec intervention humaine et la reprise après défaillance.
Le streaming sert à la fois les utilisateurs et les développeurs
Le streaming améliore la perception de réactivité, et les mêmes sorties vous aident à déboguer et à observer les exécutions.
L’exécution d’un agent comprend de nombreuses opérations
Avec astream_events, il devient clair qu’une seule appel d’agent correspond à une séquence imbriquée d’opérations sur le graphe, les nœuds, les modèles et les outils :
Graph
↓
Node
↓
LLM
↓
Tool
↓
LLM
↓
Node
↓
Graph
Plus l’application est complexe, plus cette visibilité devient nécessaire.
MCP sépare les outils des applications
Les outils exposés via MCP peuvent être partagés entre différentes applications et évoluer indépendamment des agents qui les appellent.
Où aller ensuite
Cette fondation ouvre plusieurs directions dignes d’être explorées : des arêtes conditionnelles et des branches parallèles, des interruptions avec intervention humaine, une mémoire à long terme et des points de contrôle persistants, une génération améliorée par la récupération d’informations au sein d’un graphe, des systèmes multi-agents, des intégrations plus profondes avec MCP, l’observabilité et l’évaluation des agents, le déploiement en production, ainsi qu’une tolérance aux pannes grâce à des tentatives de répétition.
Le point commun est qu’une application d’IA n’est pas simplement un modèle qui produit du texte. C’est un système conçu autour de ce modèle, capable d’accepter une demande, de prendre une décision, d’agir, d’observer le résultat, de s’en souvenir et de continuer :
Understand
↓
Decide
↓
Act
↓
Observe
↓
Remember
↓
Continue
Un graphe constitue un moyen naturel d’exprimer ce cycle.
En résumé
Tout a commencé avec le graphe le plus simple possible :
START → Chatbot → END
et a progressé, couche par couche, pour devenir un système doté d’outils, d’un cycle d’agents, de mémoire, de flux en temps réel et de MCP :
┌─────────────┐
│ LLM │
└──────┬──────┘
│
▼
┌─────────────┐
│ Tools │
└──────┬──────┘
│
▼
┌─────────────┐
│ Agent │
└──────┬──────┘
│
┌──────────┼──────────┐
↓ ↓ ↓
Memory Streaming MCP
│ │ │
└──────────┼──────────┘
↓
AI Application
Lorsqu’on les considère ensemble, ces éléments cessent d’avoir l’air d’une simple liste pour ressembler à une architecture unifiée :
- L’état stocke les données et, grâce aux points de contrôle, la mémoire ; on attribue au réducteur
add_messagesles listes de messages. - Les nœuds effectuent le travail et renvoient des mises à jour partielles.
- Les arêtes définissent le flux, et les arêtes conditionnelles le transforment en boucle.
- Le modèle fournit le raisonnement et le langage ; les outils lui permettent d’interagir avec le monde extérieur.
- Le streaming et les événements rendent chaque exécution observable, des mises à jour de l’interface jusqu’aux traces générées.
- MCP standardise la manière dont les outils sont exposés, afin qu’ils puissent exister en dehors de l’agent qui les utilise.