Changer le thème

Surveillance, alertes et reprise après échec des agents IA : de la journalisation aux machines à états

Easton editorial illustration: large Agent state recorder, coral failure beacon, checkpoint rewind handle, recovery status strip

Le rapport Gartner 2024 indique que 87 % des projets d’agents IA en entreprise affichent un taux d’échec des tâches supérieur à 25 % dans les trois mois suivant la mise en production. Les causes se cachent souvent dans des appels d’outils imbriqués, avec des journaux éparpillés partout — impossible de remonter la piste.

Le problème n’est pas le volume d’alertes, mais l’architecture de surveillance elle-même. Un agent n’est pas un service backend ordinaire : ses chemins non déterministes rendent les méthodes classiques insuffisantes. Cet article vous propose une approche complète — journaux, métriques, tracing OpenTelemetry, puis machines à états — pour transformer l’agent d’une « boîte noire incontrôlable » en un système transparent où chaque échec est traçable et récupérable.

Chapitre 1 : Pourquoi la surveillance traditionnelle échoue sur les agents

Avez-vous déjà vécu ceci : une tâche d’agent échoue, vous fouillez les journaux et ne trouvez que des fragments de sortie LLM, incapables de reconstituer le parcours d’exécution. Vous soupirez, relancez, et espérez que ça passe cette fois.

La logique de surveillance des services backend classiques est simple : une requête entre, traverse les microservices A, B et C, chaque nœud enregistre statut et horodatage, et en cas de problème on remonte la chaîne. Les agents ne fonctionnent pas ainsi.

Leur chemin d’exécution est généré dynamiquement. Pour une même tâche, le premier passage peut appeler l’outil A, le second l’outil B, le troisième peut sauter l’appel d’outil. Le rapport OpenAI 2024 indique un taux de complétion moyen de 61,8 % — derrière ce chiffre, l’agent prend des décisions en cours de raisonnement, et la décision elle-même est incertaine.

Pire encore, le God Prompt — toute la logique de l’agent tassée dans un méga-prompt. Le blog technique d’ArizenAI qualifie cette pratique de « tueur numéro un en production ». Pourquoi ? Trois défauts : non testable, non déboguable, non prévisible.

Vous ne pouvez pas unit-tester un prompt de 5 000 mots. Vous ne pouvez pas localiser précisément l’étape de raisonnement en échec. Vous ne pouvez pas prédire si changer un paramètre provoquera un effet domino. Sur un projet, j’ai vu un God Prompt dont un seul exemple modifié faisait chuter le taux de succès de 70 % à 30 %. Une semaine de debug pour comprendre que le nouvel exemple avait appris à l’agent à « prioriser l’outil A », alors que cet outil ne devait pas être déclenché dans ce scénario.

Le rapport OpenAI mentionne aussi : 82 % des échecs d’agents sont des erreurs récupérables. Ce n’est pas un manque de capacité, c’est un manque de robustesse de conception. La surveillance ne doit pas seulement « détecter » — elle doit être une boucle de rétroaction pour améliorer l’agent. Il vous faut le taux de succès par état, la latence par appel d’outil, la fréquence par type d’erreur : ces données indiquent où intervenir.

L’ancienne mentalité : « on investigue quand ça casse ». La mentalité agent : « chaque pas laisse une trace ; l’échec est une opportunité d’apprentissage ». Ce changement de perspective est le point de départ de tout le système.

Chapitre 2 : Architecture d’observabilité en trois couches

Surveiller un agent ne repose pas sur un seul levier, mais sur trois couches superposées : journaux, métriques, tracing. Chacune couvre une dimension différente.

Couche 1 : des journaux chaotiques à l’enregistrement structuré

Avez-vous déjà lu les journaux bruts d’un agent ? Des fragments de texte générés par le LLM, mélangés à des stack traces, avec des horodatages éparpillés. On ne peut qu’« archéologiser » après coup, pas surveiller en temps réel.

La clé des journaux structurés : étiqueter chaque entrée. ID agent, ID tâche, état courant, résumé entrée/sortie — ces champs permettent d’agréger par tâche, filtrer par état, trier par temps.

# Exemple de journal structuré
import structlog

logger = structlog.get_logger()

def log_agent_step(agent_id: str, task_id: str, state: str, input: dict, output: dict):
    logger.info(
        "agent_step",
        agent_id=agent_id,
        task_id=task_id,
        state=state,
        input_summary=str(input)[:100],  # tronquer pour éviter l'explosion des logs
        output_summary=str(output)[:100],
        timestamp=time.time()
    )

Cela paraît simple, mais beaucoup d’équipes ne le font pas. Elles déversent la sortie brute du LLM dans les logs et espèrent que grep en extraira quelque chose d’utile. Ça ne marche pas.

Couche 2 : métriques dédiées aux agents

Les métriques répondent à l’analyse de tendance. Un journal dit qu’une tâche a échoué ; une métrique dit que le taux d’échec monte.

Quatre familles de métriques essentielles :

Type de métriqueMétriques concrètesSeuil d’alerte suggéré
Consommation de tokensTotal, par tâche, par appel d’outil> 10 000 tokens par tâche
LatenceP50, P99, durée d’appel d’outilP99 > 30 s
Taux d’erreurÉchec de tâche, échec d’outil, succès après retryTaux d’échec > 20 %
CoûtCoût par tâche, coût journalier totalPic journalier de 50 %

Le dashboard LangSmith en est un bon exemple : métriques par agent, drill-down par tâche. Les seuils doivent reposer sur l’historique, pas sur l’intuition. Une semaine de collecte, plage normale calculée, seuil autour de 1,5 fois la limite supérieure.

Couche 3 : standard OpenTelemetry pour le tracing

Le tracing reconstruit la chaîne. Une trace part de la requête utilisateur, passe par la détection d’intention, le choix d’outil, l’exécution, la validation, jusqu’à la sortie finale. Chaque étape est un Span avec horodatage, statut, entrées et sorties.

OpenTelemetry devient la norme du secteur. Le blog PredictionGuard note que ce standard unifie le format de tracing entre frameworks et outils. Pydantic AI, smolagents, Strands Agents, LangGraph le supportent déjà.

# Exemple de tracing OpenTelemetry
from opentelemetry import trace
from opentelemetry.sdk.trace.export import ConsoleSpanExporter

tracer = trace.get_tracer("agent_tracer")

async def run_agent_with_trace(task: str):
    with tracer.start_as_current_span("agent_task") as span:
        span.set_attribute("task_input", task)
        
        # détection d'intention
        with tracer.start_as_current_span("intent_detection") as intent_span:
            intent = await detect_intent(task)
            intent_span.set_attribute("intent_result", intent)
        
        # appel d'outil
        with tracer.start_as_current_span("tool_call") as tool_span:
            result = await call_tool(intent)
            tool_span.set_attribute("tool_result", str(result)[:200])
        
        span.set_attribute("final_output", result)
        return result

Langfuse et LangSmith acceptent l’import OpenTelemetry. Vous pouvez collecter avec une stack open source et visualiser sur une plateforme commerciale — sans verrouillage fournisseur.

L’intérêt de la superposition : journaux pour le détail, métriques pour la tendance, tracing pour la vue d’ensemble. Aucune dimension ne manque.

Chapitre 3 : machines à états — le pattern qui rend les échecs observables

Le problème du God Prompt, c’est le « tout-en-un ». Toute la logique est mélangée ; en cas de panne, impossible de savoir quelle étape a lâché. La machine à états découpe la grande marmite en petites marmites en série.

Le blog ArizenAI cite un chiffre : les machines à états réduisent les coûts d’inférence de 80 %. Comment ? Chaque état ne fait qu’une chose ; le LLM n’a pas à tout raisonner depuis le début.

Machine à états vs God Prompt : différences fondamentales

DimensionGod PromptMachine à états
TestabilitéPas de tests unitairesChaque état testé indépendamment
DébogageLocalisation floueFrontières d’état claires
CoûtRaisonne tout le prompt à chaque foisNe raisonne que la partie de l’état courant
Gestion d’erreursCachée dans le promptTransitions typées avec chemins explicites

Structure typique :

[Init] → [Intention] → [Choix outil] → [Exécution] → [Validation] → [Terminé]
         ↘            ↗
           [Erreur]

ArizenAI recommande 5 à 12 états. Trop peu, on retombe sur un God Prompt ; trop, les transitions deviennent ingérables. Chaque état doit avoir des types d’entrée et de sortie explicites — ce sont les transitions typées.

# Exemple de définition d'états (pseudo-code)
from typing import TypedDict, Literal

class IntentState(TypedDict):
    task_input: str
    intent_type: Literal["query", "action", "clarify"]

class ToolState(TypedDict):
    intent: IntentState
    selected_tool: str
    tool_params: dict

class ErrorState(TypedDict):
    failed_state: str
    error_type: str
    retry_count: int

# transition d'état : chemin d'erreur explicite
def transition_from_intent(intent: IntentState) -> ToolState | ErrorState:
    try:
        tool = select_tool(intent)
        return {"intent": intent, "selected_tool": tool, "tool_params": {}}
    except IntentError as e:
        return {"failed_state": "intent", "error_type": "ambiguous", "retry_count": 0}

Points de surveillance par état

Chaque état est naturellement une unité de monitoring. Pas besoin de pêcher dans le chaos des logs : consultez les métriques par état.

  • Initialisation : heure de début, contrôle d’intégrité des entrées
  • Détection d’intention : distribution des types, latence, taux d’ambiguïté
  • Choix d’outil : fréquence d’appel, latence de sélection, taux sans outil correspondant
  • Exécution : durée, taux de succès, distribution des types d’échec
  • Validation : taux de passage, tentatives de correction
  • Gestion d’erreurs : distribution des types, succès après retry, déclenchements de dégradation

Ces métriques montrent immédiatement où l’agent faiblit. La détection d’intention passe de 2 s à 10 s ? Le prompt est peut-être trop long. Le taux d’échec d’outil monte de 5 % à 30 % ? Un service API est peut-être en panne.

La machine à états affine la granularité de « toute la tâche » à « chaque pas ». Plus efficace qu’une règle d’alerte — car localiser le problème fait partie de la surveillance.

Chapitre 4 : reprise après échec — pratiques d’ingénierie

La surveillance détecte ; la reprise corrige. Mais la reprise n’est pas un simple « retry » — un retry aveugle aggrave parfois la situation.

Classification des erreurs : toutes les défaillances ne se valent pas

Dans les projets que j’ai vus, les erreurs se répartissent en trois catégories :

TypePartCaractéristiquesTraitement
Transitoires~60 %Timeout API, jitter, rate limitRetry backoff exponentiel (max 5)
Logiques~30 %Format de paramètre, outil absent, intention ambiguëAuto-réflexion + ajustement de stratégie
En cascade~10 %Panne service cœur, mauvaise configBlocage + dégradation

Alibaba Cloud indique qu’un mécanisme de retry bien conçu peut faire passer le taux de succès API de 85 % à 99,5 %. À condition qu’il soit « bien conçu ».

Piège du retry : Context Contamination

Un article Arxiv de mai 2026 décrit un effet contre-intuitif : le simple retry abaisse souvent le taux de succès.

Pourquoi ? Les informations d’échec « contaminent » le raisonnement suivant.

Scénario : l’agent appelle l’outil A, échec, message ajouté à l’historique. Il en déduit « A est défaillant, essayons B ». B échoue aussi. Deux échecs dans l’historique. L’agent peut conclure « tâche trop complexe, j’abandonne ».

C’est la Context Contamination — l’échec modifie le chemin de raisonnement et pousse vers l’abandon ou de mauvaises stratégies.

Solution : isolation d’état. Chaque retry repart d’un état « propre », sans hériter de tout l’historique d’échec. Ou, avant retry, compresser l’échec en résumé structuré plutôt qu’une stack brute.

# Exemple de retry avec isolation d'état
async def retry_with_clean_state(task: str, error: AgentError, max_retries: int = 3):
    for attempt in range(max_retries):
        # pas d'historique complet d'échec, seulement un résumé structuré
        error_summary = {
            "type": error.type,
            "failed_step": error.step,
            "hint": get_recovery_hint(error)
        }
        
        result = await run_agent_state(
            start_state="error_recovery",
            context={"original_task": task, "error_summary": error_summary}
        )
        
        if result.success:
            return result
    
    return {"status": "failed", "reason": "max_retries_exceeded"}

Dégradation : accepter l’échec, sortir proprement

Certaines erreurs ne se récupèrent pas automatiquement. Après 3 à 5 échecs consécutifs, déclenchez la dégradation.

Stratégies selon le contexte :

  • Simplifier la tâche : version allégée, résultat partiel
  • Intervention humaine : mise en attente, notification ops ou utilisateur
  • Réponse de secours : réponse générique prédéfinie pour ne pas couper l’expérience

NIST SP 800-61 Rev. 3 (mise à jour 2025) définit six fonctions de réponse aux incidents : Govern, Identify, Protect, Detect, Respond, Recover. Cadre issu de la cybersécurité, mais parfaitement applicable à l’exploitation d’agents.

Mapping NIST → agent :

  • Govern : seuils d’échec, stratégies de dégradation, responsabilités
  • Identify : typologie des erreurs, chaîne d’échec
  • Protect : dégradation preset, circuit breaker
  • Detect : surveillance temps réel, détection d’anomalies
  • Respond : retry ou dégradation, journalisation d’incident
  • Recover : retour au service normal, post-mortem

L’intérêt : la reprise est un processus complet, pas un bricolage de dernière minute.

Chapitre 5 : cas pratiques et outils recommandés

La théorie suffit ; voici des intégrations concrètes.

LangGraph + Langfuse

LangGraph supporte OpenTelemetry nativement ; l’accroche Langfuse tient en quelques lignes :

from langfuse import Langfuse
from langfuse.callback import CallbackHandler

langfuse_handler = CallbackHandler(
    public_key="pk-xxx",
    secret_key="sk-xxx",
    host="https://cloud.langfuse.com"
)

# injection du callback à la compilation LangGraph
agent = graph.compile()
result = agent.invoke(
    {"input": task},
    config={"callbacks": [langfuse_handler]}
)

Langfuse collecte automatiquement le tracing par nœud : entrées, sorties, durée, tokens. Le dashboard permet de suivre la chaîne complète par ID de tâche.

Endpoint de health check CrewAI

CrewAI n’a pas de monitoring intégré ; prévoyez un endpoint dédié :

from fastapi import FastAPI
from crewai import Crew

app = FastAPI()

@app.get("/health")
async def health_check():
    # taux de succès sur les 100 dernières tâches
    recent_tasks = get_recent_tasks(limit=100)
    success_rate = sum(1 for t in recent_tasks if t.status == "success") / len(recent_tasks)
    
    return {
        "status": "healthy" if success_rate > 0.8 else "degraded",
        "success_rate": success_rate,
        "last_error": recent_tasks[-1].error_summary if recent_tasks[-1].status == "failed" else None
    }

Cet endpoint s’intègre aux probes Kubernetes ou alimente un système d’alertes.

Matrice d’outils recommandés

ScénarioOutil recommandéPoints fortsÉquipe cible
TracingLangfuseOpenTelemetry natif, open source, auto-hébergementBesoin de déploiement sur mesure
MonitoringLangSmithOfficiel LangChain, alertes intégréesÉcosystème LangChain/LangGraph
JournauxLoki + GrafanaCoût faible, friendly K8sGrand déploiement, budget serré
Détection d’anomaliesLuna-2 (petit modèle)Patterns d’échec agent, bon débruitageAlertes bruyantes

PredictionGuard note que de petits modèles comme Luna-2 comprennent des schémas d’échec spécifiques aux agents, plus intelligents que des seuils fixes. Si votre panneau affiche des dizaines de notifications par jour dont 90 % sont du bruit, ce type de modèle vaut le coup.

Conclusion

Quelle différence entre absence et présence d’un système de surveillance agent ?

DimensionSans surveillanceAvec surveillance
LocalisationFouille de logs, longPar état, réponse en secondes
RepriseRetry aveugle, faible succèsTraitement par catégorie
Qualité des alertesBruit, cause noyéeAgrégation, signal clair
AméliorationRéglages au feelingOptimisation pilotée par les données

Du God Prompt à la machine à états, des journaux chaotiques au tracing OpenTelemetry, du retry aveugle à la reprise isolée — ce n’est pas un « plus », c’est le passage obligé pour la production.

Si un méga-prompt porte encore tout votre agent, découpez aujourd’hui : 5 à 12 états discrets, responsabilité unique, chemins d’échec explicites.

Si OpenTelemetry n’est pas branché, c’est le moment : les frameworks majeurs le supportent ; Langfuse et LangSmith importent les traces directement.

Le retry n’est pas une panacée. La Context Contamination enfonce parfois plus profond. Concevez l’isolation d’état.

Industrialiser un agent, ce n’est pas « un bon prompt suffit ». Surveillance et reprise — c’est là qu’il devient réellement maîtrisable.

Construire un système d'observabilité pour agents IA

Mise en place complète de la surveillance, de la journalisation aux machines à états

⏱️ Estimated time: 45 min

  1. 1

    Step 1: Concevoir un format de journaux structurés

    Étiquetez chaque entrée avec l'ID agent, l'ID tâche, l'état courant et un résumé entrée/sortie. Utilisez structlog ou équivalent pour un format unifié, et tronquez les longs textes pour éviter l'explosion des logs.
  2. 2

    Step 2: Configurer les métriques clés de l'agent

    Surveillez la consommation de tokens (seuil 10 000 par tâche), la latence (seuil P99 30 secondes), le taux d'erreur (seuil d'échec 20 %) et le coût (pic journalier de 50 %).
  3. 3

    Step 3: Intégrer le tracing OpenTelemetry

    Définissez un Span pour chaque étape, de la requête utilisateur à la sortie finale. LangGraph, Pydantic AI et d'autres frameworks majeurs le supportent nativement ; importez dans Langfuse ou LangSmith pour la visualisation.
  4. 4

    Step 4: Découper l'architecture en machine à états

    Divisez les God Prompts en 5 à 12 états discrets, chacun à responsabilité unique. Utilisez des transitions typées pour définir des chemins d'erreur explicites.
  5. 5

    Step 5: Implémenter la classification et la reprise d'erreurs

    Retry à backoff exponentiel pour les erreurs transitoires (max 5 tentatives), auto-réflexion pour les erreurs logiques, blocage et dégradation pour les erreurs en cascade. Isolez l'état à chaque retry pour éviter la Context Contamination.

FAQ

Pourquoi la surveillance traditionnelle échoue-t-elle sur les agents ?
Les chemins d'exécution des agents sont générés dynamiquement — une même tâche peut emprunter des routes différentes à chaque fois. La surveillance traditionnelle repose sur des chaînes fixes et ne peut pas suivre les décisions non déterministes. De plus, les God Prompts concentrent toute la logique dans un seul prompt, rendant impossible de localiser l'étape en échec.
Comment la machine à états réduit-elle les coûts d'inférence ?
Chaque état ne fait qu'une seule chose — le LLM n'a pas besoin de raisonner l'intégralité de la logique à partir de zéro à chaque fois. Les données d'ArizenAI montrent une réduction de 80 % des coûts d'inférence. Surtout, chaque état est testable indépendamment, ce qui permet une localisation précise en cas d'échec.
Qu'est-ce que la Context Contamination ?
Les informations d'échec contaminent le raisonnement suivant. Quand un appel d'outil échoue, les messages d'erreur sont ajoutés à l'historique de conversation, ce qui peut pousser l'agent vers une mauvaise stratégie ou l'abandon de la tâche. La solution : des retries isolés par état, sans hériter de l'historique complet des échecs.
Comment concevoir les seuils d'alerte pour un agent ?
Lancez une semaine pour collecter une baseline, calculez les plages normales, puis fixez les seuils autour de 1,5 fois la limite supérieure normale. Évitez les seuils au jugé — trop bas provoque une explosion de bruit, trop haut fait rater les vrais problèmes.
OpenTelemetry ou LangSmith : que choisir ?
Langfuse pour un déploiement auto-hébergé (open source), LangSmith si vous êtes dans l'écosystème LangChain/LangGraph (intégration d'alertes plus complète). Les deux supportent l'import/export OpenTelemetry, évitant le verrouillage fournisseur.
Que faire après l'échec des retries ?
Des échecs consécutifs (3 à 5 fois) déclenchent la dégradation : simplifier la tâche pour un résultat partiel, demander une intervention humaine, ou renvoyer une réponse de secours pour préserver l'expérience utilisateur. Le cadre NIST SP 800-61 traite la reprise comme un processus complet, pas un palliatif.

11 min de lecture · Publié le: 27 mai 2026 · Mis à jour le: 30 juil. 2026

Commentaires

Connectez-vous avec GitHub pour laisser un commentaire

Easton BlogEaston Blog