← Stack

LangGraph

Graphes d'agents stateful : cycles, checkpoints, human-in-the-loop.

Vue d'ensemble

LangGraph est le framework d'orchestration bas niveau et le runtime d'agents de LangChain, conçu pour les agents stateful et long-running. L'abstraction centrale est un graphe orienté : les nœuds sont des fonctions qui reçoivent l'état courant et renvoient des mises à jour partielles ; les edges déterminent quel nœud s'exécute ensuite. Le runtime (Pregel) procède par supersteps, en parallélisant à chaque étape les nœuds éligibles.

Ce qui fait la différence, c'est la durabilité. Via un checkpointer (Postgres en production), chaque superstep est capturé. Si le process plante à 3h du matin, il reprend au dernier checkpoint stable, sans repartir de zéro. C'est ce qui fait de LangGraph le bon choix pour les pipelines qui durent plus de quelques secondes, dialoguent avec des APIs externes ou demandent une étape d'approbation humaine.

LangGraph 1.0 est sorti le 22 octobre 2025, simultanément avec LangChain 1.0. Engagement semver : pas de breaking changes avant la 2.0. La couche plateforme (hosting managé) a atteint la GA le 14 mai 2025 sous le nom LangGraph Platform, rebrandé ensuite LangSmith Deployment.

Architecture

Trois modèles mentaux couvrent 90% des usages en production. Le builder StateGraph compile vers un runtime Pregel. Le runtime avance par supersteps : à chaque étape, tous les nœuds dont les préconditions sont satisfaites s'exécutent en parallèle, leurs writes sont fusionnés par des reducers, puis l'ensemble de nœuds suivant est déterminé. La persistence est indépendante : il suffit d'attacher un checkpointer avant d'appeler compile() pour que chaque superstep soit capturé automatiquement.

StateGraphdéfinir Stateajouter nodesajouter edges.compile()Compiledinvoke()stream()astream()Runtime Pregelsuperstepssuperstep n+1
Cycle de vie StateGraph : définir State → ajouter nœuds/edges → compile() → runtime Pregel
State[n]Nœud ANœud BNœud CPARALLÈLEReducersfusion des writesState[n+1]
Un superstep Pregel : exécution parallèle des nœuds, agrégation par reducers, résolution de l'étape suivante
THREADck0checkpointck1checkpointck2checkpointinterrupt()ck3checkpointCommand(resume=)ck4checkpointrevue humaine
Thread comme séquence de checkpoints : interrupt() met en pause, reprise via Command(resume=)

Concepts clés

StateGraph
Classe builder paramétrée par un type State défini par l'utilisateur. Les nœuds s'ajoutent comme fonctions State → Partial<State> ; les edges les connectent. Pas exécutable directement : appeler .compile() au préalable.
Reducer
Fonction de fusion par clé (Value, Value) → Value déclarée via Annotated. Sans reducer, les writes concurrents appliquent le last-write-wins. Le reducer intégré add_messages ajoute aux listes de messages au lieu de les remplacer.
Checkpoint
Snapshot de l'état du graphe à un superstep : ID UUID v6 monotone, channel_values, channel_versions, versions_seen par nœud. Le checkpointer en écrit un par superstep ; le thread est la séquence ordonnée de ces snapshots.
Thread
Séquence de checkpoints identifiée par thread_id. Configuré via {"configurable": {"thread_id": ...}}. Se restreint à un checkpoint_id spécifique pour le time-travel ou le debug.
Pregel
Le runtime de message-passing sous-jacent de LangGraph, inspiré de Google Pregel (BSP). Les développeurs ne l'instancient jamais directement : CompiledStateGraph étend Pregel. Le décorateur @entrypoint de l'API Fonctionnelle renvoie lui aussi un Pregel.
interrupt() / Command
interrupt() met en pause l'exécution dans un nœud, sauvegarde l'état et attend indéfiniment. Reprise via Command(resume=value), qui devient la valeur de retour d'interrupt(). Requiert un checkpointer et un thread_id. À la reprise, le nœud entier se ré-exécute depuis le début, pas depuis la ligne interrupt().

Quand l'utiliser

Cas adaptés

  • Flux cycliques : l'agent doit boucler (réfléchir, retenter, itérer) en fonction de résultats intermédiaires.
  • Pipelines stateful : l'état doit survivre à plusieurs appels LLM, à des appels API ou à un redémarrage de session.
  • Tolérance aux pannes : si le process plante en cours d'exécution, reprendre depuis le dernier checkpoint est une exigence ferme.
  • Human-in-the-loop : un humain doit examiner ou approuver avant que le pipeline continue.
  • Coordination multi-agents avec machines à états explicites : contrôle total sur quel agent s'exécute, quand et sous quelle condition.

Anti-patterns

  • Gonflement des checkpoints à l'échelle : le checkpointer Postgres écrit sur 4 tables. Une conversation typique (2 messages, 3 tool calls) génère environ 93 records. À 120 000+ conversations par semaine, le stockage croît sans limite faute de politique de rétention/purge. Source : tadeodonegana.com/posts/scaling-langgraph-postgres-checkpointer.
  • Débat infini dans les boucles multi-agents : sans état terminal, les agents peuvent tourner indéfiniment, brûlant des tokens et atteignant les rate limits. À contenir avec recursion_limit et des edges conditionnels sur un champ retry_count.
  • Ré-exécution du nœud à la reprise : quand Command(resume=) est appelé, le nœud entier se ré-exécute depuis sa première ligne, pas depuis l'appel interrupt(). Tout side-effect placé avant interrupt() (appel API, écriture DB) s'exécutera deux fois. Rendre ces side-effects idempotents ou les déplacer après interrupt().
  • InMemorySaver en production : l'état est perdu au redémarrage du process. Utiliser PostgresSaver (ou un checkpointer persistant équivalent) dans tout environnement susceptible de redémarrer.
  • Pipeline linéaire sans état : si le flux n'a ni cycles, ni état persistant, ni étape d'approbation humaine, le surcoût de StateGraph ne se justifie pas. Utiliser LCEL ou une simple composition de fonctions à la place.

Exemples de code

StateGraph minimal avec reducer add_messages

from typing import Annotated
from langgraph.graph import StateGraph, START, END
from langgraph.graph.message import add_messages
from langchain_core.messages import HumanMessage, AIMessage
from typing_extensions import TypedDict

class State(TypedDict):
    messages: Annotated[list, add_messages]

def chat_node(state: State) -> dict:
    # Replace with your actual LLM call
    reply = AIMessage(content="Hello from LangGraph")
    return {"messages": [reply]}

builder = StateGraph(State)
builder.add_node("chat", chat_node)
builder.add_edge(START, "chat")
builder.add_edge("chat", END)

graph = builder.compile()
result = graph.invoke({"messages": [HumanMessage(content="Hi")]})

add_messages est le reducer intégré qui ajoute les messages au lieu de remplacer la liste. Sans lui, chaque write de nœud écraserait les messages précédents.

Human-in-the-loop avec interrupt() et Command(resume=)

from langgraph.types import interrupt, Command
from langgraph.checkpoint.postgres import PostgresSaver
from langgraph.graph import StateGraph, START, END
from typing_extensions import TypedDict

class State(TypedDict):
    proposal: str
    approved: bool

def generate_node(state: State) -> dict:
    return {"proposal": "Deploy to production at 14:00"}

def review_node(state: State) -> dict:
    decision = interrupt({"proposal": state["proposal"]})
    return {"approved": decision == "yes"}

def deploy_node(state: State) -> dict:
    return {}

builder = StateGraph(State)
builder.add_node("generate", generate_node)
builder.add_node("review", review_node)
builder.add_node("deploy", deploy_node)
builder.add_edge(START, "generate")
builder.add_edge("generate", "review")
builder.add_edge("review", "deploy")
builder.add_edge("deploy", END)

with PostgresSaver.from_conn_string("postgresql://...") as checkpointer:
    graph = builder.compile(checkpointer=checkpointer)
    config = {"configurable": {"thread_id": "run-001"}}
    graph.invoke({"proposal": "", "approved": False}, config)
    # Human reviews, then:
    graph.invoke(Command(resume="yes"), config)

interrupt() requiert un checkpointer et un thread_id. À la reprise, le nœud se ré-exécute depuis sa première ligne : tout side-effect placé avant interrupt() s'exécute deux fois. Placer les side-effects après interrupt() ou les rendre idempotents.

PostgresSaver pour la persistence en production

from langgraph.checkpoint.postgres import PostgresSaver
import os

POSTGRES_URI = os.environ["DATABASE_URL"]

with PostgresSaver.from_conn_string(POSTGRES_URI) as checkpointer:
    checkpointer.setup()  # creates tables on first run

    graph = builder.compile(checkpointer=checkpointer)
    config = {"configurable": {"thread_id": "session-42"}}

    result = graph.invoke(input_state, config)
    result2 = graph.invoke(next_input, config)

    history = list(graph.get_state_history(config))

PostgresSaver écrit sur 4 tables. À l'échelle (100k+ conversations/semaine), mettre en place une politique de rétention pour borner la croissance du stockage : environ 93 records par conversation en moyenne.

Comparatif

vs LangChain LCEL

Cycles, état, tolérance aux pannes

LCEL pour les pipelines linéaires sans état persistant. LangGraph dès que vous avez besoin de cycles, de branchements conditionnels, de checkpointing ou d'approbation humaine. Les deux se composent : une chaîne LCEL peut servir de nœud dans un LangGraph.

vs CrewAI

Niveau d'abstraction, équipes basées sur les rôles

CrewAI monte l'abstraction : vous définissez des rôles (Researcher, Writer, Validator) et le framework gère la coordination. Moins de code pour les pipelines séquentiels organisés par rôles, mais moins de contrôle sur la topologie des cycles et les transitions d'état. LangGraph est le bon choix quand la machine à états est le produit. Source : docs.crewai.com (source primaire, biais CrewAI assumé).

vs AutoGen / AG2

Contrôle du flux, état, statut du projet

LangGraph pilote le flux par un graphe explicite (nœuds, arêtes, routage conditionnel) et un checkpointer automatique. AutoGen modélise l'interaction des agents comme une conversation au routage émergent, avec une persistance manuelle (save_state / load_state). Choisir le style AutoGen pour du prototypage multi-agent conversationnel rapide, LangGraph pour une machine à états cyclique et auditable. Le statut compte : l'AutoGen original de Microsoft est en maintenance (successeur : Microsoft Agent Framework), et AG2 est le fork communautaire qui conserve l'API v0.2. Sources : zenml.io, github.com/ag2ai/ag2.

vs LlamaIndex Workflows

Modèle de programmation, charge idéale

Les Workflows de LlamaIndex sont event-driven et découpés en étapes (les branches renvoient des types d'événements, les boucles ré-émettent des événements), avec un store Context et du checkpointing pour reprendre après un redémarrage. Ils excellent sur le RAG et le Q&A documentaire ; LangGraph excelle sur le contrôle multi-agent cyclique et long-running. Les deux se composent souvent : LlamaIndex pour le retrieval, LangGraph pour l'orchestration. Sources : developers.llamaindex.ai, leanware.co.

Ressources

FAQ

Quelle est la différence entre LangGraph et LangChain ?
LangChain fournit les blocs de construction (clients LLM, prompt templates, pipelines LCEL). LangGraph est la couche d'orchestration stateful construite par-dessus : elle ajoute le modèle de graphe, le runtime Pregel et le checkpointing. LangChain seul suffit pour les pipelines linéaires. Ajouter LangGraph quand vous avez besoin de cycles, d'état persistant ou d'une reprise tolérante aux pannes.
Ai-je besoin de Postgres en production ?
Si votre agent doit survivre à un redémarrage de process, oui. InMemorySaver perd tout l'état au redémarrage. PostgresSaver (ou tout checkpointer persistant) est le choix prêt pour la production. Le compromis : PostgresSaver écrit sur 4 tables, soit environ 93 records par conversation en moyenne. Prévoir une politique de rétention si vous attendez un volume élevé.
Comment fonctionne le human-in-the-loop ?
Appeler interrupt(payload) dans un nœud. LangGraph sauvegarde l'état courant et met en pause indéfiniment. Reprendre en appelant graph.invoke(Command(resume=value), config) avec le même thread_id. La valeur de retour d'interrupt() devient la valeur passée à Command(resume=). Attention : à la reprise, le nœud entier se ré-exécute depuis son début, donc les side-effects placés avant interrupt() s'exécuteront deux fois.
LangGraph est-il un système d'exécution durable comme Temporal ?
Pas tout à fait. Le checkpointing de LangGraph permet de reprendre depuis un état sauvé, mais ce n'est pas un runtime d'exécution durable complet. En mode OSS mono-process, il n'y a pas de watchdog, pas de détection automatique des pannes, et pas de re-scheduling. Vous avez besoin d'infrastructure externe pour détecter un process crashé et déclencher une reprise. Temporal résout cela au niveau du runtime. Source : langchain.com/resources/langgraph-vs-temporal.
Qu'est-ce qu'un checkpoint et un thread ?
Un checkpoint est un snapshot de tout l'état du graphe à un superstep : il porte un ID UUID v6 monotone et stocke les valeurs des canaux et les vecteurs de version. Un thread est la séquence ordonnée de checkpoints partageant le même thread_id. Vous pouvez rejouer ou brancher un thread en ciblant un checkpoint_id précis.