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

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étrique | Métriques concrètes | Seuil d’alerte suggéré |
|---|---|---|
| Consommation de tokens | Total, par tâche, par appel d’outil | > 10 000 tokens par tâche |
| Latence | P50, P99, durée d’appel d’outil | P99 > 30 s |
| Taux d’erreur | Échec de tâche, échec d’outil, succès après retry | Taux d’échec > 20 % |
| Coût | Coût par tâche, coût journalier total | Pic 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
| Dimension | God Prompt | Machine à états |
|---|---|---|
| Testabilité | Pas de tests unitaires | Chaque état testé indépendamment |
| Débogage | Localisation floue | Frontières d’état claires |
| Coût | Raisonne tout le prompt à chaque fois | Ne raisonne que la partie de l’état courant |
| Gestion d’erreurs | Cachée dans le prompt | Transitions 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 :
| Type | Part | Caractéristiques | Traitement |
|---|---|---|---|
| Transitoires | ~60 % | Timeout API, jitter, rate limit | Retry 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 config | Blocage + 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énario | Outil recommandé | Points forts | Équipe cible |
|---|---|---|---|
| Tracing | Langfuse | OpenTelemetry natif, open source, auto-hébergement | Besoin de déploiement sur mesure |
| Monitoring | LangSmith | Officiel LangChain, alertes intégrées | Écosystème LangChain/LangGraph |
| Journaux | Loki + Grafana | Coût faible, friendly K8s | Grand déploiement, budget serré |
| Détection d’anomalies | Luna-2 (petit modèle) | Patterns d’échec agent, bon débruitage | Alertes 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 ?
| Dimension | Sans surveillance | Avec surveillance |
|---|---|---|
| Localisation | Fouille de logs, long | Par état, réponse en secondes |
| Reprise | Retry aveugle, faible succès | Traitement par catégorie |
| Qualité des alertes | Bruit, cause noyée | Agrégation, signal clair |
| Amélioration | Réglages au feeling | Optimisation 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
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
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
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
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
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 ?
Comment la machine à états réduit-elle les coûts d'inférence ?
Qu'est-ce que la Context Contamination ?
Comment concevoir les seuils d'alerte pour un agent ?
OpenTelemetry ou LangSmith : que choisir ?
Que faire après l'échec des retries ?
11 min de lecture · Publié le: 27 mai 2026 · Mis à jour le: 30 juil. 2026
Guide d'ingénierie AI Agent
Si vous arrivez depuis la recherche, le plus rapide est de passer à l’article précédent ou suivant de cette série.
Précédent
Comment évaluer la capacité de planification d'un Agent ? Profondeur de raisonnement, décomposition de tâches et auto-correction
Comment évaluer la planification d'un Agent ? Ce guide détaille la méthodologie d'évaluation — profondeur de raisonnement, décomposition de tâches, auto-correction — et compare AgentBench, ToolBench, ACPBench avec un guide pratique.
Partie 14 sur 16
Suivant
Architecture DeepAgents : outils de planification, sous-agents et système de fichiers
Analyse approfondie des quatre piliers de DeepAgents : Planning Tools, Sub-agents, File System et System Prompts, comparaison avec LangGraph, AutoGen et autres frameworks, avec exemples de code et bonnes pratiques
Partie 16 sur 16



Commentaires
Connectez-vous avec GitHub pour laisser un commentaire