MLOps

Tolérance aux pannes dans LangGraph : retries, timeouts et error handlers

LangGraph propose trois primitives déclaratives pour rendre vos agents IA robustes en production : RetryPolicy, TimeoutPolicy et error_handler. Voici comment les configurer et les combiner.

Tolérance aux pannes dans LangGraph : retries, timeouts et error handlers

Tolérance aux pannes dans LangGraph : retries, timeouts et error handlers

Mettre un agent en production, c'est accepter que les choses vont échouer. Les APIs en amont tombent en 5xx, les requêtes HTTP s'éternisent, les sous-processus se figent. Un agent qui ne sait pas gérer ces pannes bloque, produit des états incohérents ou se crashe silencieusement — et l'équipe passe sa nuit à débugguer. Le 4 juin 2026, LangChain a publié un article de référence sur les mécanismes de tolérance aux pannes de LangGraph, documentant trois primitives qui permettent de rendre un graphe d'agents robuste en production : RetryPolicy, TimeoutPolicy et error_handler.

Ce sont des outils que toute équipe qui industrialise des agents doit connaître — pas seulement pour les utiliser, mais pour comprendre ce qu'ils font (et ce qu'ils ne font pas) avant de les configurer à l'aveugle.

Le contexte : pourquoi la tolérance aux pannes est différente pour les agents

Un service web classique tombe, se relance, et la plupart des erreurs transitoires s'effacent avec un retry basique. Un graphe d'agents, c'est plus complexe : chaque nœud peut appeler un LLM, un outil externe, un sous-agent, une base de données vectorielle. La chaîne d'appels est longue, les latences sont élevées, et un blocage à mi-chemin peut laisser l'état du graphe dans un état partiel difficile à récupérer.

Sans mécanisme de tolérance aux pannes intégré, les équipes construisent des wrappers ad hoc autour de chaque appel — du code fragile, non testé, qui diverge entre les nœuds. LangGraph adresse ce problème en proposant des primitives déclaratives, configurables nœud par nœud, qui s'appliquent uniformément à tout le graphe.

RetryPolicy : retries automatiques avec backoff configurable

La RetryPolicy est l'élément de base. Elle se configure directement à la création d'un nœud et gère automatiquement les tentatives en cas d'erreur transitoire.

from langgraph.retry import RetryPolicy

StateGraph(State).add_node(
    "call_llm",
    call_llm,
    retry_policy=RetryPolicy(
        max_attempts=4,
        initial_interval=0.5,
        backoff_factor=2.0,
        max_interval=128.0,
        jitter=True,
    ),
)

Les paramètres clés :

  • max_attempts : nombre maximum de tentatives (défaut : 3)
  • initial_interval : délai initial en secondes avant le premier retry (défaut : 0,5 s)
  • backoff_factor : multiplicateur appliqué à chaque délai (défaut : 2,0)
  • max_interval : cap sur le délai entre tentatives (défaut : 128 s)
  • jitter : ajoute une variante aléatoire pour éviter les thundering herds
  • retry_on : liste d'exceptions ou callable qui détermine si une erreur est éligible au retry

Ce que LangGraph retente (et ce qu'il refuse de retenter)

Le choix par défaut des erreurs éligibles est opinioné et documenté : LangGraph retente les ConnectionError, les réponses 5xx des librairies httpx et requests, et quelques catégories d'erreurs transitoires génériques. En revanche, il ne retente pas ValueError, TypeError, RuntimeError et leurs dérivés — parce que ces erreurs signalent presque toujours un bug de programmation, pas une panne réseau. Les retenter masquerait le problème et consommerait des tokens inutilement.

Ce choix évite l'écueil classique des retries trop permissifs : une API qui renvoie un 422 (paramètre invalide) ne mérite pas d'être retentée trois fois à coût croissant.

TimeoutPolicy : stopper un nœud qui se fige

Un nœud qui appelle un LLM distant peut rester bloqué indéfiniment si la connexion est maintenue ouverte mais que les données ne transitent plus. Sans timeout explicite, ce blocage immobilise le graphe entier. La TimeoutPolicy fixe deux types de limites :

from langgraph.timeout import TimeoutPolicy

StateGraph(State).add_node(
    "call_llm",
    call_llm,
    timeout=TimeoutPolicy(run_timeout=30, idle_timeout=5),
)
  • run_timeout : cap absolu en secondes sur la durée d'une tentative, indépendamment de l'activité du nœud. Si la tentative dépasse ce délai, LangGraph lève une NodeTimeoutError, annule les écritures partielles de la tentative échouée, et laisse la RetryPolicy décider de la suite.
  • idle_timeout : durée maximale sans "signal de progrès" observable — écriture dans un channel, chunk streamé, événement d'un sous-agent, callback LangChain. Par défaut en mode auto, ce délai se réinitialise à chaque signe d'activité, ce qui évite de tuer un nœud légitime qui génère simplement une réponse lente.

Pourquoi les timeouts comptent comme des pannes transitoires

Un point contre-intuitif, mais important : LangGraph traite un timeout comme une panne transitoire, pas comme une erreur définitive. L'hypothèse est que la requête a peut-être abouti côté serveur, et qu'une nouvelle tentative est légitime. Combiner TimeoutPolicy(run_timeout=30) et RetryPolicy(max_attempts=3) donne donc jusqu'à trois tentatives de 30 secondes maximum chacune — soit un plafond de 90 secondes de durée totale avant abandon.

error_handler : la sortie de secours après épuisement des retries

Quand tous les retries sont épuisés, l'error_handler prend le relais. C'est une fonction normale qui reçoit l'état courant du graphe et un objet NodeError contenant les informations sur l'échec.

def handle_model_failure(state: State, error: NodeError):
    # error.node : nom du nœud ayant échoué
    # error.error : l'exception d'origine
    return {"status": "degraded", "last_error": str(error.error)}

StateGraph(State).add_node(
    "call_llm",
    call_llm,
    retry_policy=RetryPolicy(max_attempts=4, backoff_factor=2.0),
    timeout=TimeoutPolicy(run_timeout=30, idle_timeout=5),
    error_handler=handle_model_failure,
)

Trois propriétés importantes de l'error_handler :

  1. Il ne s'exécute qu'après épuisement des retries — pas à chaque tentative échouée.
  2. La transition est atomique — l'handler s'exécute dans le même cycle d'exécution que l'échec final.
  3. On ne peut pas chaîner des error handlers — pas de récursion infinie possible si l'handler lui-même échoue.

L'error_handler permet d'implémenter des stratégies de dégradation gracieuse : basculer sur un modèle de fallback, retourner un résultat partiel, logger l'erreur dans un système de monitoring, ou aiguiller le graphe vers un nœud d'alerte humaine.

Composer les trois primitives : patterns concrets

La puissance réelle vient de la combinaison des trois primitives sur le même nœud, comme dans l'exemple de la documentation officielle :

StateGraph(State)
    .add_node(
        "call_llm",
        call_llm,
        retry_policy=RetryPolicy(max_attempts=4, backoff_factor=2.0),
        timeout=TimeoutPolicy(run_timeout=30, idle_timeout=5),
        error_handler=handle_model_failure,
    )

En pratique, trois patterns reviennent fréquemment en production.

Pattern dégradation gracieuse : l'error_handler bascule sur un modèle moins cher ou moins capable — par exemple, passer d'un Opus à un Haiku si l'appel principal a épuisé ses retries. Le graphe continue, l'utilisateur reçoit une réponse moins riche, mais le service reste disponible.

Pattern circuit-breaker manuel : l'error_handler écrit un flag dans l'état du graphe ({"llm_circuit_open": True}) que les nœuds suivants lisent pour décider d'éviter cet appel. LangGraph ne fournit pas de circuit-breaker natif, mais l'error_handler + l'état conditionnel permet de l'implémenter proprement.

Pattern alerte humaine : pour les agents à impact élevé (exécution d'actions irréversibles, transactions financières), l'error_handler peut aiguiller vers un nœud "human-in-the-loop" qui suspend le graphe et notifie l'équipe. LangGraph supporte nativement les interruptions de graphe pour ce cas.

Erreurs fréquentes de configuration à éviter

Configurer la tolérance aux pannes sans réfléchir aux implications crée des problèmes spécifiques.

Retries trop agressifs sur des appels non idempotents. Si un nœud déclenche une action externe non idempotente (écriture en base, envoi de message, débit bancaire), le retenter peut dupliquer l'effet. Il faut soit rendre l'action idempotente (token d'idempotence côté API), soit placer l'action dans un nœud sans RetryPolicy et gérer manuellement l'erreur.

run_timeout trop court pour des LLMs lents. Un run_timeout=5 sur un appel à un modèle qui génère 2000 tokens garantit une NodeTimeoutError systématique. Calibrer le timeout requiert de mesurer les latences P99 de l'API cible en production, pas de poser un chiffre rond.

Ignorer le coût des retries sur le budget token. Avec max_attempts=4 et backoff_factor=2.0, le pire cas est : 0,5s → 1s → 2s → 4s d'attente, plus quatre appels LLM complets. Sur un nœud qui tokenise un contexte long, ça représente une dépense significative. Suivre les coûts par nœud et par tentative dans votre système d'observabilité permet de détecter les nœuds qui consomment leur budget de retry en continu.

Ce que ça change pour les équipes IA

Ces trois primitives ne sont pas qu'un détail d'implémentation : elles définissent la frontière entre un agent de démonstration et un agent de production.

L'observabilité passe par la lisibilité des pannes. Quand LangGraph capte une NodeTimeoutError et la passe à l'error_handler avec error.node et error.error, le log de l'incident est structuré dès la source. Pas besoin d'enrouler chaque appel dans un try/except ad hoc — l'information est là, prête à être envoyée à LangSmith ou à n'importe quel système d'observabilité.

Les retries sont un coût, pas une assurance. Configurer max_attempts=5 sur tous les nœuds parce que "c'est plus sûr" revient à multiplier par cinq la latence maximale et le budget token des scénarios de panne. La bonne pratique est de calibrer max_attempts et backoff_factor nœud par nœud, en fonction du SLA réel de chaque appel externe — et d'utiliser retry_on pour ne retenter que les erreurs qui le méritent.

La tolérance aux pannes est un sujet d'architecture, pas de code. Décider quels nœuds ont un error_handler, lesquels basculent sur un modèle de fallback, et lesquels doivent alerter un humain — c'est une décision métier autant que technique. C'est exactement le travail que SeedVision effectue lors de la mise en production d'agents : auditer les points de défaillance, définir les politiques de retry et de dégradation, et brancher l'observabilité au bon niveau.

En bref

  • LangGraph propose trois primitives de tolérance aux pannes déclaratives : RetryPolicy, TimeoutPolicy, et error_handler, configurables nœud par nœud.
  • La RetryPolicy gère le backoff exponentiel avec jitter ; elle exclut par défaut les erreurs de programmation (ValueError, TypeError) pour ne retenter que les pannes transitoires réelles.
  • La TimeoutPolicy distingue le timeout absolu (run_timeout) du timeout d'inactivité (idle_timeout), et traite les deux comme des pannes transitoires éligibles au retry.
  • L'error_handler se déclenche après épuisement des retries, reçoit le contexte complet de l'échec, et permet d'implémenter des stratégies de dégradation gracieuse.
  • Bien configurées, ces primitives transforment un agent fragile en service observable et résilient — sans wrapper ad hoc autour de chaque appel.

Vous industrialisez vos agents IA ? SeedVision propose des audits IA en 3-5 jours et des forfaits de mise en production de 15 à 30 jours. Voir les packages ou réserver un appel de 30 min.

Photo de couverture : Photo by Jake Walker on Unsplash.