LangGraph Expliqué : Pourquoi les Workflows Cycliques Changent la Donne pour les Agents Autonomes
La plupart des frameworks IA actuels construisent des pipelines linéaires : une entrée traverse une séquence d'étapes et produit une sortie. C'est simple, prévisible, et ça fonctionne — jusqu'à ce que vous ayez besoin d'un agent qui raisonne, agit, observe et adapte son comportement. Les agents autonomes ont besoin de boucles, pas de lignes droites. C'est exactement ce problème que LangGraph résout avec ses workflows cycliques. Cet article vous explique pourquoi c'est un changement de paradigme.
Le Problème des Chaînes Linéaires
Les DAGs : une illusion de contrôle
Les chaînes LangChain traditionnelles sont des DAGs (Directed Acyclic Graphs) — des graphes orientés acycliques. L'information y circule dans une seule direction, sans jamais revenir en arrière.
Entrée -> Retrieval -> LLM -> Formatage -> Sortie
Ce modèle fonctionne parfaitement pour les tâches de transformation :
- RAG (question -> recherche -> synthèse -> réponse)
- Extraction (document -> parsing -> structuration -> base de données)
- Traduction (texte source -> traduction -> révision -> texte final)
Mais dès que vous introduisez de l'autonomie, le modèle se brise.
Pourquoi les agents autonomes ont besoin de boucles
Un agent autonome ne suit pas un chemin prédéfini. Il observe, raisonne, agit et réévalue en permanence. C'est le cycle OODA (Observe, Orient, Decide, Act) popularisé par le strategiste militaire John Boyd, appliqué à l'intelligence artificielle.
Observe -> Oriente -> Décide -> Agit -> Observe -> ...
Ce cycle est par nature cyclique. L'agent boucle jusqu'à ce qu'il atteigne son objectif ou décide d'abandonner. Les DAGs ne peuvent pas modéliser ce comportement nativement.
Les contournements en LangChain
En pratique, les développeurs LangChain contournent cette limitation avec des boucles manuelles :
# Pattern "while loop" — fragile et difficile à maintenir
max_iterations = 10
for i in range(max_iterations):
thought = agent.think(task)
action = agent.act(thought)
observation = agent.observe(action)
if observation.is_complete():
break
task = observation.feedback # Réinjecte le feedback
Ce code fonctionne, mais il présente plusieurs problèmes :
- L'état est géré manuellement : les variables
thought,action,observationsont dispersées, difficiles à tracer. - Pas de persistance : si le programme crash à l'itération 7, tout est perdu.
- Pas d'observabilité : difficile de comprendre pourquoi l'agent a pris une décision à l'itération 5.
- Pas de human-in-the-loop : impossible d'interrompre pour demander une validation humaine.
- Difficile à paralléliser : les boucles
forsont séquentielles par nature.
Pour une comparaison détaillée entre les deux approches, consultez notre article LangChain vs LangGraph : Quelles Différences et Quand Passer à l'Autre ?.
Qu'est-ce qu'un Workflow Cyclique ?
Définition
Un workflow cyclique est un graphe d'exécution où les nœuds peuvent être visités plusieurs fois, dans un ordre déterminé dynamiquement par des conditions. Contrairement au DAG, le graphe peut contenir des cycles — des chemins qui ramènent à un nœud précédemment visité.
Analogie : la conversation vs le formulaire
- Workflow linéaire (DAG) : un formulaire administratif. Vous remplissez les champs dans l'ordre, vous ne revenez pas en arrière. Simple mais rigide.
- Workflow cyclique : une conversation avec un expert. Vous posez une question, il répond, vous reformulez, il affine, vous validez, il conclut. Flexible et adaptatif.
Représentation graphique
Un workflow cyclique typique pour un agent autonome :
[DÉBUT] -> [Raisonner] -> [Agir] -> [Observer] -> [Évaluer]
^ |
| v
+-----------------------------------+
(si objectif non atteint)
[DÉBUT] -> [Raisonner] -> [Agir] -> [Observer] -> [Évaluer] -> [FIN]
(si objectif atteint)
Ce cycle raisonner-agir-observer-évaluer est le pattern fondamental des agents autonomes. LangGraph le modélise nativement.
L'Approche de LangGraph : Graphes d'État
Le StateGraph
LangGraph modélise les workflows cycliques via des graphes d'état. Un StateGraph est un graphe où :
- L'état est explicite et typé : un dictionnaire (TypedDict ou Pydantic) qui circule entre les nœuds et est mis à jour à chaque étape.
- Les nœuds sont des fonctions pures : chaque nœud reçoit l'état, le transformme et retourne les mises à jour.
- Les arêtes sont conditionnelles : les transitions entre nœuds dépendent de l'état courant.
- Les checkpoints assurent la persistance : l'état est sauvegardé à chaque étape, permettant la reprise et le time-travel debugging.
Exemple : Agent ReAct avec LangGraph
from typing import TypedDict, Annotated, List
from langgraph.graph import StateGraph, START, END
from langgraph.graph.message import add_messages
from langgraph.checkpoint.memory import MemorySaver
from langchain_openai import ChatOpenAI
from langchain_core.tools import tool
# Définition de l'état
class AgentState(TypedDict):
messages: Annotated[list, add_messages]
iteration: int
task_complete: bool
# Définition d'un outil
@tool
def search_web(query: str) -> str:
"""Recherche sur le web."""
# Implémentation réelle avec Tavily, SerpAPI, etc.
return f"Résultats de recherche pour : {query}"
@tool
def calculate(expression: str) -> str:
"""Évalue une expression mathématique."""
try:
return str(eval(expression))
except Exception as e:
return f"Erreur : {e}"
# Outils disponibles
tools = [search_web, calculate]
llm = ChatOpenAI(model="gpt-4o").bind_tools(tools)
# Nœud 1 : L'agent raisonne et décide d'une action
def reason(state: AgentState) -> dict:
response = llm.invoke(state["messages"])
return {
"messages": [response],
"iteration": state["iteration"] + 1
}
# Nœud 2 : Exécuter les outils demandés
def execute_tools(state: AgentState) -> dict:
last_message = state["messages"][-1]
tool_calls = last_message.tool_calls
results = []
for tool_call in tool_calls:
tool_name = tool_call["name"]
tool_args = tool_call["args"]
for t in tools:
if t.name == tool_name:
result = t.invoke(tool_args)
results.append({
"role": "tool",
"content": str(result),
"tool_call_id": tool_call["id"]
})
return {"messages": results}
# Arête conditionnelle : continuer ou terminer
def should_continue(state: AgentState) -> str:
last_message = state["messages"][-1]
# Si l'agent n'appelle plus d'outils, il a terminé
if not hasattr(last_message, "tool_calls") or not last_message.tool_calls:
return END
# Limite de sécurité
if state["iteration"] >= 15:
return END
return "execute_tools"
# Construction du graphe
graph = StateGraph(AgentState)
graph.add_node("reason", reason)
graph.add_node("execute_tools", execute_tools)
graph.add_edge(START, "reason")
graph.add_conditional_edges("reason", should_continue, {
"execute_tools": "execute_tools",
END: END
})
graph.add_edge("execute_tools", "reason")
# Compilation avec persistance
checkpointer = MemorySaver()
app = graph.compile(checkpointer=checkpointer)
# Exécution
config = {"configurable": {"thread_id": "agent-1"}}
result = app.invoke(
{"messages": [{"role": "user", "content": "Combien font les ventes de Q1 multipliées par 1.15 ?"}]},
config
)
print(result["messages"][-1].content)
Ce qui se passe sous le capot
- L'utilisateur envoie une question.
- Le nœud
reasonappelle le LLM, qui décide d'appeler un outil (par exemple,search_webpour trouver les ventes Q1). - L'arête conditionnelle
should_continuedétecte qu'il y a des appels d'outils et redirige versexecute_tools. - Le nœud
execute_toolsexécute les outils et renvoie les résultats. - L'arête inconditionnelle renvoie vers
reason(cycle !). - Le nœud
reasonreçoit les résultats des outils et décide soit d'appeler un autre outil, soit de répondre. - Quand le LLM ne demande plus d'outils,
should_continuerenvoieEND.
À chaque étape, le checkpoint sauvegarde l'état complet. Vous pouvez inspecter, reprendre ou annuler n'importe quelle étape.
Pourquoi les Workflows Cycliques Changent la Donne
1. L'autonomie devient réelle
Avec les chaînes linéaires, l'autonomie d'un agent est simulée — elle repose sur des boucles manuelles. Avec LangGraph, l'autonomie est native. L'agent boucle naturellement jusqu'à ce qu'il atteigne son objectif, sans intervention du développeur.
2. Le debugging devient possible
Les checkpoints de LangGraph permettent le "time-travel debugging" — la capacité de rejouer l'exécution à partir de n'importe quel point. Vous pouvez voir exactement quel était l'état à l'itération 3, pourquoi l'agent a pris une décision, et ce qui se serait passé si une condition avait été différente.
# Récupérer l'état à chaque étape
states = app.get_state_history(config)
for state in states:
print(f"Étape {state.values['iteration']} : {state.values['messages'][-1].content[:80]}...")
3. Le human-in-the-loop devient trivial
Les workflows cycliques permettent d'interrompre l'exécution à n'importe quel point pour demander une validation humaine, puis de reprendre automatiquement.
from langgraph.checkpoint import MemorySaver
def human_review(state: AgentState) -> dict:
"""Nœud qui demande une validation humaine."""
last_action = state["messages"][-1].content
approval = input(f"Approuver cette action ? (oui/non) : {last_action[:100]}...")
if approval.lower() == "oui":
return {"task_complete": True}
return {"task_complete": False, "messages": [
{"role": "user", "content": "Action non approuvée. Réessaie avec une approche différente."}
]}
# Ajout du nœud de validation humaine
graph.add_node("human_review", human_review)
graph.add_edge("execute_tools", "human_review")
graph.add_conditional_edges("human_review", lambda s: "reason" if not s["task_complete"] else END)
4. La persistance garantit la robustesse
En production, un agent peut tourner pendant des minutes ou des heures. Sans persistance, un crash signifie une perte totale de progression. Avec les checkpoints LangGraph, l'état est sauvegardé à chaque étape et peut être repris après n'importe quel incident.
5. La composition permet la complexité
Les graphes LangGraph peuvent être composés — un nœud peut être lui-même un graphe. Cela permet de construire des systèmes hiérarchiques où un superviseur orchestre des sous-graphes spécialisés.
# Un sous-graphe de recherche
research_graph = StateGraph(ResearchState)
research_graph.add_node("search", search_node)
research_graph.add_node("synthesize", synthesize_node)
research_graph.add_edge(START, "search")
research_graph.add_edge("search", "synthesize")
research_graph.add_edge("synthesize", END)
research_app = research_graph.compile()
# Le graphe principal utilise le sous-graphe comme nœud
main_graph = StateGraph(MainState)
main_graph.add_node("supervisor", supervisor_node)
main_graph.add_node("research", research_app) # Sous-graphe comme nœud
main_graph.add_node("write", write_node)
Cas d'Usage Concrets
1. Agent de Recherche Autonome
Un agent qui cherche, synthétise et vérifie des informations en bouclant jusqu'à obtenir un résultat fiable.
class ResearchState(TypedDict):
query: str
findings: List[str]
confidence: float
messages: Annotated[list, add_messages]
def search(state: ResearchState) -> dict:
# Recherche web
new_findings = search_engine(state["query"])
return {"findings": state["findings"] + new_findings}
def synthesize(state: ResearchState) -> dict:
# Synthèse des résultats
summary = llm.invoke(f"Synthèse de : {state['findings']}")
return {"messages": [summary]}
def verify(state: ResearchState) -> dict:
# Vérification des faits
confidence = fact_checker(state["messages"][-1].content)
return {"confidence": confidence}
def needs_more_research(state: ResearchState) -> str:
if state["confidence"] < 0.8:
return "search" # Boucle : chercher plus
return END
# Graphe cyclique : recherche -> synthèse -> vérification -> (recherche ou fin)
graph = StateGraph(ResearchState)
graph.add_node("search", search)
graph.add_node("synthesize", synthesize)
graph.add_node("verify", verify)
graph.add_edge(START, "search")
graph.add_edge("search", "synthesize")
graph.add_edge("synthesize", "verify")
graph.add_conditional_edges("verify", needs_more_research)
2. Agent de Code Autonome
Un agent qui écrit, exécute, débogue et améliore du code en bouclant jusqu'à ce que les tests passent.
class CodingState(TypedDict):
requirements: str
code: str
test_results: str
iteration: int
messages: Annotated[list, add_messages]
def write_code(state: CodingState) -> dict:
code = coder_llm.invoke(
f"Écris du code Python pour : {state['requirements']}\n"
f"Code précédent (si correction) : {state.get('code', 'aucun')}\n"
f"Résultat des tests : {state.get('test_results', 'aucun')}"
)
return {"code": code.content, "iteration": state["iteration"] + 1}
def run_tests(state: CodingState) -> dict:
# Exécution des tests en sandbox
result = sandbox.execute(state["code"])
return {"test_results": result.output}
def should_fix(state: CodingState) -> str:
if "PASS" in state["test_results"]:
return END
if state["iteration"] >= 5:
return END
return "write_code" # Boucle : corriger le code
graph = StateGraph(CodingState)
graph.add_node("write_code", write_code)
graph.add_node("run_tests", run_tests)
graph.add_edge(START, "write_code")
graph.add_edge("write_code", "run_tests")
graph.add_conditional_edges("run_tests", should_fix)
3. Agent de Service Client avec Escalade
Un agent qui répond aux questions, et si la confiance est faible, boucle vers un agent spécialisé ou un humain.
class SupportState(TypedDict):
customer_query: str
response: str
confidence: float
escalated: bool
messages: Annotated[list, add_messages]
def answer(state: SupportState) -> dict:
response = support_llm.invoke(state["messages"])
return {"response": response.content}
def evaluate_confidence(state: SupportState) -> dict:
score = confidence_evaluator(state["response"])
return {"confidence": score, "escalated": score < 0.6}
def route(state: SupportState) -> str:
if state["escalated"]:
return "specialist" # Transfert à un agent spécialisé
return END
graph = StateGraph(SupportState)
graph.add_node("answer", answer)
graph.add_node("evaluate", evaluate_confidence)
graph.add_node("specialist", specialist_node)
graph.add_edge(START, "answer")
graph.add_edge("answer", "evaluate")
graph.add_conditional_edges("evaluate", route)
Comparaison : Chaînes Linéaires vs Workflows Cycliques
| Critère | Chaînes linéaires (LangChain) | Workflows cycliques (LangGraph) |
|---|---|---|
| Flux d'exécution | Séquentiel, prévisible | Cyclique, adaptatif |
| Autonomie de l'agent | Limitée, simulée | Réelle, native |
| Raisonnement itératif | Via boucles manuelles | Via arêtes conditionnelles |
| Persistance | Non native | Checkpoints intégrés |
| Human-in-the-loop | Complexe à implémenter | Natif (interruptions) |
| Debugging | Logging manuel | Time-travel debugging |
| Reprise après crash | Impossible sans code custom | Automatique via checkpoints |
| Composition | Chaînes de chaînes | Graphes de graphes |
| Parallélisme | RunnableParallel | Nœuds parallèles natifs |
| Complexité initiale | Faible | Modérée |
| Adapté aux agents | Partiellement | Totalement |
Patterns de Workflows Cycliques
Pattern ReAct (Reason-Act)
Le pattern le plus courant : l'agent raisonne, agit, observe et boucle.
[Raisonner] -> [Agir] -> [Observer] -> (objectif atteint ?)
^ |
| v
+---------- (non) ---------------------+
Pattern Rédacteur-Critique
Un agent rédige, un autre critique, et le cycle continue jusqu'à validation.
[Rédiger] -> [Critiquer] -> (approuvé ?)
^ |
| v
+-------- (non) ------------+
Pattern Superviseur
Un agent superviseur délègue des tâches à des agents spécialisés et boucle jusqu'à complétion.
[Superviseur] -> [Agent A] -> [Superviseur] -> [Agent B] -> [Superviseur] -> (complété ?)
Pour d'autres approches d'orchestration, consultez notre comparatif CrewAI vs AutoGen.
FAQ
Pourquoi ne pas simplement utiliser une boucle while en Python ?
Vous pouvez, mais vous perdez tous les avantages de LangGraph : persistance, observabilité, human-in-the-loop, composition et reprise après crash. Une boucle while gère l'état manuellement, sans sauvegarde automatique, sans traçabilité et sans capacité de reprendre après un incident. LangGraph industrialise ce pattern.
Les workflows cycliques ne risquent-ils pas de boucler indéfiniment ?
C'est un risque réel, mais LangGraph offre plusieurs garde-fous : le paramètre max_round, les arêtes conditionnelles avec END, et les limites d'itération dans l'état. En production, imposez toujours un maximum d'itérations et un timeout.
LangGraph peut-il fonctionner sans LangChain ?
Techniquement oui, mais en pratique, les deux sont conçus pour fonctionner ensemble. LangGraph s'appuie sur les primitives LangChain (modèles, outils, parsers) pour les briques individuelles, et ajoute l'orchestration cyclique par-dessus. Utiliser LangGraph sans LangChain nécessite de réimplémenter les intégrations.
Quels sont les coûts d'un workflow cyclique par rapport à une chaîne linéaire ?
Un workflow cyclique consomme potentiellement plus de tokens car l'agent peut boucler plusieurs fois. Cependant, il produit généralement de meilleurs résultats car il itère jusqu'à la qualité voulue. Pour contrôler les coûts, fixez des limites d'itération et utilisez des modèles légers pour les itérations intermédiaires.
Les workflows cycliques sont-ils adaptés à toutes les tâches ?
Non. Pour les tâches purement séquentielles et prévisibles (ex. : RAG simple, extraction, traduction), les chaînes linéaires sont plus efficaces et plus simples. Les workflows cycliques sont justifiés dès que l'agent doit itérer, s'adapter ou valider ses résultats. Ne surdimensionnez pas votre architecture.
Mise à jour — Avril 2026
Les workflows cycliques avec LangGraph continuent de mûrir :
- LangGraph Platform : le déploiement managé de graphes LangGraph est disponible en version stable. Il offre un serveur d'API, un dashboard de monitoring et des capacités de scaling pour les workflows cycliques en production.
- LangGraph Studio : l'IDE visuel permet de concevoir des graphes cycliques en glisser-déposer, de les exécuter pas à pas et d'inspecter l'état à chaque nœud — y compris pendant les boucles.
- Optimisation des performances : les checkpoints sont désormais incrémentaux (seuls les deltas sont sauvegardés), réduisant la surcharge mémoire et disque de 60 à 80%.
- Support des sub-graphs : la composition de graphes (un graphe comme nœud d'un autre graphe) est pleinement supportée, permettant des architectures hiérarchiques complexes.
- Intégration avec d'autres frameworks : LangGraph s'intègre désormais plus facilement avec CrewAI (pour les rôles structurés) et Semantic Kernel (pour l'écosystème .NET). Pour les développeurs .NET, consultez notre guide Semantic Kernel pour .NET.
- Standards émergents : le protocole A2A (Agent-to-Agent) de Google et la spécification OpenTelemetry pour les agents normalisent l'observabilité des workflows cycliques.
Les workflows cycliques ne sont pas un gadget technique — ils répondent à un besoin fondamental des agents autonomes : la capacité d'itérer, d'adapter et de persister. En modélisant explicitement les boucles, l'état et les conditions de sortie, LangGraph transforme les agents IA de scripts séquentiels en systèmes véritablement autonomes. Si vous construisez des agents qui doivent raisonner et agir de manière itérative, les workflows cycliques ne sont pas une option — c'est le bon modèle.