Alejandro Rioja.
AI Agents

Pourquoi votre Agent IA Continue d'Échouer en Production (Et Comment le Corriger)

Alejandro Rioja
Alejandro Rioja
6 min de lecture
TL;DR

La plupart des défaillances d'agents en production proviennent de cinq causes : des prompts fragiles qui ne gèrent pas les cas limites, une logique de nouvelle tentative manquante pour les erreurs API transitoires, aucune observabilité pour voir ce qui se casse, des boucles incontrôlables sans condition de sortie et des définitions d'outils suffisamment ambiguës pour que le modèle choisisse le mauvais. Les cinq sont corrigeables sans changer de modèle ni de framework.

Newsletter gratuite

Chaque mercredi. 28 400+ opérateurs. Zéro superflu.

Table des matières

Mis à jour juin 2026.

TL;DR : La plupart des défaillances d’agents en production proviennent de cinq causes : des prompts fragiles qui ne gèrent pas les cas limites, une logique de nouvelle tentative manquante pour les erreurs API transitoires, aucune observabilité pour voir ce qui se casse, des boucles incontrôlables sans condition de sortie et des définitions d’outils suffisamment ambiguës pour que le modèle choisisse le mauvais. Les cinq sont corrigeables sans changer de modèle ni de framework.

[Lecture de l’opérateur] Je gère plus de 30 agents en production. J’ai eu toutes ces défaillances. Celles qui m’ont fait perdre le plus de temps n’étaient pas les exotiques — c’étaient les défaillances d’infrastructure ennuyeuses que je pensais avoir gérées.

Défaillance 1 : Prompts fragiles qui se cassent sur des entrées en cas limites

Un prompt qui fonctionne sur vos cas de test échouera sur des entrées que vous n’avez pas anticipées. Ce n’est pas une limitation du modèle — c’est un problème de rédaction d’instructions.

Symptômes : L’agent produit une sortie absurde, appelle le mauvais outil ou génère un JSON malformé quand l’entrée est légèrement différente de ce que vous avez testé.

Cause principale : Votre system prompt décrit uniquement le chemin heureux. Il ne dit pas au modèle quoi faire quand les données sont manquantes, malformées ou ambiguës.

Correction : Ajoutez une gestion explicite des cas limites à votre system prompt :

code
If the input data is missing a required field, return:
{ "status": "error", "reason": "missing_field", "field": "<fieldname>" }
Do NOT attempt to infer or hallucinate missing values.

If you are uncertain which tool to call, call no tool and return:
{ "status": "clarification_needed", "question": "..." }

Le modèle suit des instructions explicites pour les cas limites de manière fiable. L’erreur est de supposer qu’il généralisera les instructions du chemin heureux pour gérer les cas désordonnés.

Défaillance 2 : Aucune logique de nouvelle tentative pour les erreurs API transitoires

Chaque API externe que votre agent appelle échouera à un moment donné. L’API de Claude, la Meta Graph API, votre base de données — toutes renvoient des erreurs 5xx, expirent ou limitent le débit. Si votre agent n’a pas de logique de nouvelle tentative, une erreur transitoire tue toute l’exécution.

Symptômes : Les exécutions d’agents échouent aléatoirement à différentes étapes. Les journaux montrent un 503 ou 429 sans tentative de suivi.

Correction : Enveloppez chaque appel externe dans une nouvelle tentative avec retrait exponentiel :

typescript
async function withRetry<T>(fn: () => Promise<T>, retries = 3, baseDelayMs = 500): Promise<T> {
  for (let attempt = 0; attempt <= retries; attempt++) {
    try {
      return await fn();
    } catch (err: any) {
      const isTransient = err.status === 429 || err.status >= 500 || err.code === "ECONNRESET";
      if (!isTransient || attempt === retries) throw err;
      const delay = baseDelayMs * Math.pow(2, attempt) + Math.random() * 100;
      await new Promise((r) => setTimeout(r, delay));
    }
  }
  throw new Error("unreachable");
}

// Usage
const result = await withRetry(() => client.messages.create({ ... }));

Trois tentatives avec retrait exponentiel gère ~99% des défaillances transitoires. Ajoutez ceci à chaque appel externe et la moitié de vos défaillances aléatoires disparaîtront.

Défaillance 3 : Aucune observabilité — vous ne pouvez pas voir ce qui se casse

C’est le mode de défaillance le plus courant en production et celui qui coûte le plus de temps à déboguer : l’agent échoue silencieusement ou produit une sortie incorrecte, et vous n’avez aucune idée où dans la chaîne ça a mal tourné.

Symptômes : Vous savez que quelque chose va mal mais ne pouvez pas identifier l’étape. Vous ajoutez des déclarations console.log et ré-exécutez manuellement en essayant de reproduire.

Correction : Journalisation structurée à chaque étape, avec un ID d’exécution qui trace toute l’exécution :

typescript
function createLogger(runId: string, agentName: string) {
  return {
    step: (step: string, data: object) =>
      console.log(JSON.stringify({ runId, agent: agentName, step, ts: new Date().toISOString(), ...data })),
    error: (step: string, err: unknown) =>
      console.error(JSON.stringify({ runId, agent: agentName, step, error: String(err), ts: new Date().toISOString() })),
  };
}

const log = createLogger(crypto.randomUUID(), "newsletter-agent");
log.step("fetch_topic", { topicId: topic.id, topic: topic.name });
// ... do work ...
log.step("draft_complete", { subject: draft.subject, wordCount: draft.body.split(" ").length });

Si vous êtes sur Cloudflare Workers, ces journaux vont dans Logpush ou Workers Tail. Si vous tournez localement ou sur un VPS, canalisez-les vers un agrégateur de journaux. Le JSON structuré signifie que vous pouvez filtrer par runId pour voir exactement ce qui s’est passé dans une seule exécution.

Défaillance 4 : Boucles incontrôlables sans condition de sortie

Les boucles agentiques — où le modèle appelle des outils et itère jusqu’à ce qu’une condition soit remplie — peuvent s’exécuter indéfiniment si cette condition n’est jamais remplie ou si le modèle l’identifie mal.

Symptômes : L’agent dépense des centaines de dollars en coûts API avant d’expirer. Ou il exécute le même appel d’outil encore et encore sans progresser.

Correction : Ayez toujours un plafond d’itération strict et une vérification de progression :

typescript
const MAX_ITERATIONS = 10;
let iterations = 0;
let lastToolCallName = "";
let sameToolCallCount = 0;

while (true) {
  iterations++;
  if (iterations > MAX_ITERATIONS) {
    log.error("loop", { reason: "exceeded_max_iterations" });
    break;
  }

  const response = await client.messages.create({ ... });

  // Detect stuck loops: same tool called 3x in a row
  const toolCall = response.content.find(b => b.type === "tool_use");
  if (toolCall?.name === lastToolCallName) {
    sameToolCallCount++;
    if (sameToolCallCount >= 3) {
      log.error("loop", { reason: "stuck_loop", tool: toolCall.name });
      break;
    }
  } else {
    sameToolCallCount = 0;
    lastToolCallName = toolCall?.name ?? "";
  }

  if (response.stop_reason === "end_turn") break;
}

Cela capture à la fois les modes de défaillance “a tourné trop longtemps” et “a tourné en rond”. Le plafond doit être suffisamment généreux pour le chemin heureux mais suffisamment serré pour limiter le rayon d’explosion.

Défaillance 5 : Définitions d’outils ambiguës que le modèle résout mal

Si vous donnez au modèle deux outils avec des descriptions qui se chevauchent, il appellera parfois le mauvais. C’est particulièrement courant avec des outils comme search_database vs get_record ou send_email vs create_draft.

Symptômes : Le modèle appelle la bonne catégorie d’outil mais choisit le mauvais spécifique. Ou il appelle un outil dans le mauvais contexte (utilisant un outil d’écriture alors que seule la lecture était appropriée).

Correction : Rendez les descriptions d’outils mutuellement exclusives et ajoutez explicitement “quand NE PAS utiliser ceci” :

typescript
const tools = [
  {
    name: "get_subscriber",
    description: "Fetch a single subscriber record by email. Use ONLY when you have a specific email address. Do NOT use for searching or listing subscribers.",
    input_schema: { ... }
  },
  {
    name: "search_subscribers",
    description: "Search subscribers by tag, segment, or status. Use when you need to find subscribers matching a criteria — NOT when you have a specific email address.",
    input_schema: { ... }
  }
];

La clause “ne PAS utiliser quand X” est la partie que la plupart des gens ignorent. C’est la partie la plus importante. Les modèles sont meilleurs pour suivre des contraintes négatives explicites que pour les inférer à partir de descriptions positives.

Encore une chose : testez vos agents sur de mauvaises entrées

La plupart des agents sont testés uniquement sur des entrées propres en chemin heureux. La production a des entrées sales : chaînes vides, champs nuls, cas limites Unicode, réponses d’API qui renvoient 200 mais avec un schéma inattendu.

Ajoutez une suite de tests qui exercice explicitement :

Si votre agent se casse sur l’une d’elles, corrigez-la avant qu’elle ne soit mise en production. L’environnement de production trouvera chaque hypothèse que vous avez faite.

La conclusion de l’opérateur

La plupart des défaillances d’agents en production sont des problèmes d’infrastructure se faisant passer pour des problèmes de modèle. Avant de changer de modèle, ajoutez des nouvelles tentatives, une journalisation structurée, des plafonds de boucle et une gestion explicite des cas limites à vos prompts. Corrigez les définitions d’outils ambiguës. Puis testez sur de mauvaises entrées. Faites tout cela avant de blâmer le modèle — dans mon expérience, le modèle est généralement la dernière chose qui doit changer.

Continuer à lire

Recevez le guide IA dans votre boîte mail

Chaque mercredi. 28 400+ opérateurs. Zéro superflu.

↵ pour voir tous les résultats esc esc pour fermer