Annexes · Claude
Raisons d'arrêt et erreurs
Chaque stop_reason et erreur HTTP avec cause, détection et code de traitement — la référence de fiabilité pour la boucle agentique.
Les items de fiabilité reposent sur deux énumérations : les valeurs de stop_reason sur lesquelles une boucle doit brancher, et les erreurs HTTP qu’un client doit classer en réessai-ou-correction. Cette page couvre chacune avec cause, détection et traitement.
La règle de fiabilité
Branchez sur stop_reason pour contrôler la boucle et sur le statut HTTP pour décider réessai vs correction. Ne parsez jamais la prose pour « done », n’utilisez jamais un plafond d’itérations comme arrêt principal, et ne réessayez jamais une erreur client 4xx.
stop_reason — chaque valeur
| Valeur | Cause | Détection | Traitement |
|---|---|---|---|
end_turn | Le modèle a terminé naturellement | stop_reason == "end_turn" | Fini — renvoyez la réponse |
tool_use | Le modèle veut exécuter des outils | stop_reason == "tool_use" | Exécuter tous les blocs tool_use, ajouter le(s) tool_result dans un message user, rappeler |
max_tokens | La sortie a atteint max_tokens | stop_reason == "max_tokens" | Tronqué — continuer (« Continue. ») ou augmenter le plafond ; ne jamais traiter comme complet |
stop_sequence | A atteint une chaîne d’arrêt configurée | stop_reason == "stop_sequence" ; vérifiez stop_sequence | Fini — la chaîne était le terminateur voulu |
pause_turn | Long tour d’outil côté serveur mis en pause | stop_reason == "pause_turn" | Renvoyer la conversation inchangée pour continuer |
refusal | Les systèmes de sécurité ont refusé | stop_reason == "refusal" | Repli explicite (reformuler, transfert humain, message sûr) ; ne réessayez pas aveuglément |
def handle(r, messages): sr = r.stop_reason if sr == "tool_use": messages.append({"role": "assistant", "content": r.content}) messages.append({"role": "user", "content": run_tools(r.content)}) return "continue" if sr == "max_tokens": messages.append({"role": "assistant", "content": r.content}) messages.append({"role": "user", "content": "Continue from where you stopped."}) return "continue" if sr == "pause_turn": return "resend" # re-send unchanged if sr == "refusal": return handle_refusal(r) # explicit fallback path return "done" # end_turn / stop_sequencemax_tokens n’est pas un succès
Un arrêt max_tokens signifie que la réponse est coupée. La renvoyer comme finale est l’anti-pattern de silent-failure — particulièrement dangereux pour une sortie structurée où le JSON est désormais invalide.
Erreurs HTTP — chaque statut
| HTTP | Type | Cause | Retry ? | Traitement |
|---|---|---|---|---|
| 400 | invalid_request_error | Requête malformée (p. ex. tool_choice forcé sur Fable 5.1, budget_tokens sur non-Haiku, mauvais schéma) | Non | Corriger la requête |
| 401 | authentication_error | Clé API manquante/invalide | Non | Corriger les identifiants |
| 403 | permission_error | La clé manque du droit d’accès (modèle/fonctionnalité) | Non | Vérifier l’accès/le droit |
| 404 | not_found_error | Le modèle/la ressource n’existe pas (p. ex. modèle retiré) | Non | Corriger l’ID / migrer |
| 413 | request_too_large | La charge utile dépasse les limites | Non | Réduire l’entrée ; chunker ; Files API |
| 429 | rate_limit_error | RPM/ITPM/OTPM dépassés | Oui | Backoff + jitter, respectez retry-after ; réduisez le débit proactivement |
| 500 | api_error | Erreur côté serveur | Oui | Backoff + jitter |
| 529 | overloaded_error | Capacité saturée | Oui | Backoff ; envisagez un repli vers un modèle plus récent ou équivalent |
Retry? ── status in {429, 500, 502, 503, 529} ──► YES: exponential backoff + jitter, honour retry-after └─ status in {400, 401, 403, 404, 413} ──► NO: fix the request/credentials/entitlementimport time, randomfrom anthropic import APIStatusError, RateLimitError, APIConnectionError
RETRYABLE = {429, 500, 502, 503, 529}
def call(fn, **params): for attempt in range(6): try: return fn(**params) except RateLimitError as e: wait = float(e.response.headers.get("retry-after", 0)) or min(60, 2 ** attempt) time.sleep(wait + random.uniform(0, 0.5)) except APIStatusError as e: if e.status_code in RETRYABLE: time.sleep(min(60, 2 ** attempt) + random.uniform(0, 0.5)) else: raise # 4xx → fix, don't retry except APIConnectionError: time.sleep(min(60, 2 ** attempt) + random.uniform(0, 0.5)) raise RuntimeError("exhausted retries")import { APIError } from '@anthropic-ai/sdk';const RETRYABLE = new Set([429, 500, 502, 503, 529]);const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms));
export async function call<T>(fn: () => Promise<T>): Promise<T> { for (let attempt = 0; attempt < 6; attempt++) { try { return await fn(); } catch (err) { if (err instanceof APIError && RETRYABLE.has(err.status ?? 0)) { const ra = Number(err.headers?.['retry-after']) || Math.min(60, 2 ** attempt); await sleep((ra + Math.random() * 0.5) * 1000); continue; } throw err; // 4xx → fix } } throw new Error('exhausted retries');}Erreurs de streaming
Un événement error peut arriver en cours de flux (communément overloaded_error). Traitez-le comme le statut HTTP : surcharge/timeout réessayable → backoff et redémarrez le flux ; requête malformée → corrigez.
event: errordata: {"type":"error","error":{"type":"overloaded_error","message":"Overloaded"}}Erreurs d’exécution d’outil (pas HTTP)
Un outil qui échoue n’est pas une erreur API — l’appel API a réussi. Renvoyez un tool_result structuré avec is_error: true :
{ "type": "tool_result", "tool_use_id": "toolu_01", "is_error": true, "content": "{\"category\":\"not_found\",\"retryable\":false,\"message\":\"No order ORD-999\"}" }| Category | Signification | Le modèle devrait |
|---|---|---|
not_found | Ressource absente | Le dire à l’utilisateur, demander un ID valide |
invalid_input | Mauvais arguments | Corriger et réessayer |
transient | Temporaire (timeout) | Réessayer une fois, puis signaler |
forbidden | Non autorisé | S’arrêter ; escalader |
Ne renvoyez jamais un succès vide sur échec (anti-pattern de silent empty-success).
Référence rapide de décision
| Symptôme | Diagnostic | Action |
|---|---|---|
| La boucle ne finit jamais | Utilise la prose/un plafond d’itérations pour s’arrêter | Brancher sur stop_reason |
| JSON tronqué | max_tokens atteint | Augmenter le plafond / continuer ; valider |
| « Ça s’est arrêté et mis en pause » | pause_turn | Renvoyer inchangé |
| Refus de sécurité traité comme un crash | refusal mal géré | Repli explicite |
| Tempêtes de 429 | Réessais sans backoff ou ignore retry-after | Backoff + jitter + en-tête ; monter de palier |
| Réessayer un 400 indéfiniment | Réessayer une erreur client | Corriger la requête |
| Thinking disparu après un repli | A basculé vers un modèle plus ancien | Migrer uniquement vers plus récent ou équivalent |
| L’outil a « marché » mais n’a rien renvoyé | Succès vide | is_error structuré |
Idées reçues courantes
| Idée reçue | Réalité | Pourquoi cela compte à l’examen |
|---|---|---|
| « Toute erreur devrait être réessayée » | Seulement 429/5xx/529 ; corrigez les 4xx | Distracteur de tempête de retries |
« max_tokens est une réponse complète » | C’est une troncature | Anti-pattern de silent-failure |
| « Parser le texte pour savoir que c’est fini » | Brancher sur stop_reason | Anti-pattern de parsing de prose |
| « Plafonner les itérations pour arrêter la boucle » | Le plafond est un garde-fou ; stop_reason l’arrête | Anti-pattern de plafond d’itérations |
« refusal est une erreur serveur » | C’est un refus de sécurité ; gérez-le explicitement | Distracteur de mauvaise gestion de refus |
| « Un échec d’outil est une erreur API » | Les erreurs d’outil utilisent is_error sur le résultat | Confusion de couche d’erreur |
| « 529 signifie abandonner » | Backoff ; envisagez un modèle de repli | Distracteur de disponibilité |
Analyse de scénario
Un agent sur Opus 5 en production, par intermittence : (a) renvoie du JSON coupé, (b) atteint des 529 sous charge, (c) a une fois renvoyé un refus de sécurité montré aux utilisateurs comme « Error 500 », et (d) un ingénieur a ajouté tool_choice: "any" après avoir basculé un flux vers Fable 5.1 et obtient maintenant des 400.
- JSON coupé —
stop_reason: max_tokens. Augmentezmax_tokenset/ou continuez ; validez avant usage. - 529 — réessayable ; backoff exponentiel + jitter respectant
retry-after; après des 529 répétés, basculez vers un modèle plus récent ou équivalent (pas vers le bas). - Refus montré comme 500 —
stop_reason: refusala été mal géré ; routez vers un message de repli explicite, pas une erreur générique. - 400 sur Fable 5.1 — le
tool_choiceforcé est non supporté (400, non réessayable) ; basculez versauto+instruction,strict: true, ououtput_config.format.
Alternatives rejetées : réessayer le 400 (erreur client — corrigez-la), traiter le JSON tronqué comme valide (silent failure), et présenter le refus comme un 500 (mauvaise gestion de refus).
Points clés à retenir
- Valeurs de
stop_reason:end_turn,tool_use,max_tokens,stop_sequence,pause_turn,refusal— branchez sur chacune. - Réessayez 429/5xx/529 avec backoff + jitter +
retry-after; corrigez 400/401/403/404/413. max_tokens= troncature,refusal= refus de sécurité — les deux nécessitent une gestion explicite, pas un laisser-passer silencieux.- Les échecs d’outil utilisent un
is_errorstructuré, jamais un succès vide ni une erreur HTTP. - Sur des 529 répétés, ne basculez que vers un modèle plus récent ou équivalent pour garder valides les blocs de thinking de Fable 5.1.
Dernière mise à jour le 18 sept. 2026