Votre agent appelle une API. L’API répond 429. L’agent réessaie immédiatement, reçoit un autre 429, puis recommence jusqu’à l’arrêt de l’exécution ou à l’explosion des coûts. Cette boucle n’est généralement pas intentionnelle : elle vient d’une gestion d’erreur naïve et constitue l’un des problèmes les plus fréquents évoqués sur le forum du SDK Anthropic.
Essayez Apidog dès aujourd’hui
La fiabilité d’un agent dépend moins du modèle que de son comportement lorsqu’un outil est lent, limité ou indisponible. Une récupération bien conçue transforme une dépendance instable en courte pause. Une récupération absente transforme un simple 500 en incident.
Ce guide couvre les quatre mécanismes à implémenter :
- Réessais avec backoff exponentiel et jitter.
- Délais d’attente à chaque niveau.
- Disjoncteurs par dépendance.
- Clés d’idempotence pour les opérations modifiant un état.
Vous verrez également comment tester ces scénarios contre un mock avant qu’un utilisateur ne découvre les failles en production. Pour une vue d’ensemble, consultez pourquoi les agents d’IA tombent en panne en production.
Vous ne pouvez pas tester la récupération contre une API saine
En développement, la dépendance répond souvent correctement. Vos appels réussissent, votre démo fonctionne, puis vous déployez sans avoir exécuté une seule fois le chemin de récupération.
Le problème est simple : une API saine ne produit pas les erreurs que votre code est censé gérer.
Pour tester la récupération, provoquez volontairement les échecs :
- Créez un mock de l’API appelée par l’outil de votre agent.
- Configurez des réponses
429,500, des timeouts ou des corps invalides. - Pointez votre agent vers le mock plutôt que vers le service réel.
- Vérifiez précisément les tentatives, les délais et la réponse finale.
L’échec devient ainsi un scénario de test reproductible, pas une alerte à 3 heures du matin. Apidog peut servir à configurer ce mock et à script les réponses nécessaires.
Réessayer avec un backoff exponentiel et du jitter
Un réessai immédiat est rarement une bonne stratégie. Lorsqu’un service est sous charge, chaque client en échec réessaie au même moment. Le trafic de récupération devient alors une charge supplémentaire qui empêche le service de récupérer.
Utilisez deux mécanismes ensemble :
- Backoff exponentiel : augmentez l’attente entre les tentatives.
- Jitter : ajoutez une variation aléatoire pour désynchroniser les clients.
Exemple de délais :
Tentative 1 : 1 s
Tentative 2 : 2 s
Tentative 3 : 4 s
Tentative 4 : 8 s
Ajoutez ensuite un jitter, par exemple entre 0 et 500 ms, à chaque délai.
Exemple Python :
import random
import time
MAX_ATTEMPTS = 4
MAX_DELAY_SECONDS = 8
def retry_with_backoff(call):
for attempt in range(MAX_ATTEMPTS):
try:
return call()
except Exception:
if attempt == MAX_ATTEMPTS - 1:
raise
base_delay = min(2 ** attempt, MAX_DELAY_SECONDS)
jitter = random.uniform(0, 0.5)
time.sleep(base_delay + jitter)
Encadrez toujours les réessais avec :
- un nombre maximal de tentatives ;
- un délai maximal entre tentatives ;
- une liste explicite d’erreurs réessayables.
En pratique, trois à cinq tentatives couvrent la plupart des erreurs transitoires. Au-delà, vous réessayez souvent un problème permanent.
Le SDK Anthropic applique déjà une partie de cette logique à ses propres appels : il réessaie certaines erreurs de connexion et certains codes HTTP avec backoff exponentiel, avec un plafond configurable via max-retries. En revanche, cela ne couvre pas les autres API appelées par les outils de votre agent.
Pour les API à fort impact, notamment financières, les réessais doivent être conçus avec encore plus de prudence. Consultez cette analyse de la logique de réessai pour les API à enjeux élevés.
Définir un délai d’attente pour chaque appel
Un réessai ne peut fonctionner que si l’appel échoue. Or, le pire cas est souvent un appel qui ne renvoie jamais : la connexion est établie, puis la dépendance se bloque.
Sans timeout :
- l’appel d’outil reste suspendu ;
- l’exécution de l’agent est bloquée ;
- aucun mécanisme de récupération ne démarre ;
- votre budget de temps et de tokens continue d’être consommé.
Définissez au minimum trois budgets :
| Budget | Rôle |
|---|---|
| Timeout de connexion | Temps maximal pour établir la connexion |
| Timeout de lecture | Temps maximal pour recevoir la réponse |
| Budget global d’exécution | Temps maximal pour l’ensemble du run de l’agent |
Exemple avec httpx :
import httpx
timeout = httpx.Timeout(
connect=2.0,
read=10.0,
write=5.0,
pool=2.0,
)
with httpx.Client(timeout=timeout) as client:
response = client.get("https://api.example.com/data")
Lorsqu’un timeout se déclenche, traitez-le comme une erreur potentiellement réessayable, dans les limites définies par votre stratégie.
Ne choisissez pas les valeurs au hasard. Basez-les sur la latence réelle de la dépendance :
- partez du p99 observé ;
- ajoutez une marge de sécurité ;
- utilisez un budget séparé pour les réponses en streaming.
Un timeout trop court annule des requêtes qui auraient réussi. Un timeout trop long conserve un agent bloqué bien après le point d’utilité.
Déclencher un disjoncteur lorsqu’une dépendance est en panne
Le backoff convient à un service temporairement occupé. Il ne résout pas le cas d’un service durablement indisponible.
Si une API est en panne depuis plusieurs minutes, continuer à attendre puis réessayer dégrade l’expérience utilisateur et ajoute de la charge à une dépendance déjà cassée.
Un disjoncteur (circuit breaker) utilise trois états :
- Fermé : les requêtes passent normalement et les échecs sont comptabilisés.
- Ouvert : après un seuil d’échecs, les requêtes échouent immédiatement pendant une période de refroidissement.
- Semi-ouvert : après le refroidissement, une requête de test est autorisée.
Le comportement attendu est :
fermée -> échecs répétés -> ouverte
ouverte -> délai de refroidissement -> semi-ouverte
semi-ouverte + succès -> fermée
semi-ouverte + échec -> ouverte
Implémentez le disjoncteur par dépendance, et non globalement.
Par exemple :
- une panne de l’API de recherche ne doit pas empêcher les appels à une API de facturation saine ;
- une panne de paiement doit produire une erreur rapide et compréhensible ;
- l’agent doit pouvoir choisir une alternative ou informer l’utilisateur plutôt que d’accumuler des timeouts.
Sécuriser les réessais avec des clés d’idempotence
Les réessais ne sont sûrs que si répéter l’opération ne produit pas un deuxième effet.
Cas classique :
- L’agent envoie
POST /charge. - Le serveur traite le paiement.
- La réponse expire avant d’arriver au client.
- L’agent pense que l’appel a échoué.
- Il réessaie.
- Le client est facturé deux fois.
Le réessai a fonctionné comme prévu. C’est la conception de l’endpoint qui n’était pas sûre.
La solution est une clé d’idempotence. Générez une clé unique pour chaque action logique et envoyez-la dans l’en-tête Idempotency-Key.
import uuid
import httpx
idempotency_key = str(uuid.uuid4())
response = httpx.post(
"https://api.example.com/charge",
headers={
"Idempotency-Key": idempotency_key,
},
json={
"customer_id": "cus_123",
"amount": 2500,
},
)
Point essentiel : générez la clé avant d’entrer dans la boucle de réessai.
import uuid
idempotency_key = str(uuid.uuid4())
for attempt in range(3):
response = send_charge(
idempotency_key=idempotency_key,
)
Ne faites pas ceci :
for attempt in range(3):
# Incorrect : une nouvelle clé à chaque tentative
idempotency_key = str(uuid.uuid4())
response = send_charge(idempotency_key=idempotency_key)
La clé doit :
- rester identique pour tous les réessais d’une même action ;
- changer pour une nouvelle action métier ;
- être utilisée pour les appels qui créent ou modifient un état.
Cela concerne notamment :
- les paiements ;
- les commandes ;
- les e-mails ;
- les créations d’enregistrements ;
- les mises à jour d’état.
Pour aller plus loin, consultez le guide sur les clés d’idempotence.
Survivre aux limites de débit et à la boucle RateLimitError
Les limites de débit demandent un traitement spécifique parce que le serveur fournit souvent une instruction explicite.
Une réponse d’excès de limite de débit est généralement un 429 avec l’en-tête Retry-After.
Cet en-tête peut indiquer :
- un nombre de secondes à attendre ;
- ou une date à laquelle la requête peut être tentée à nouveau.
Respectez cette valeur.
import time
def handle_rate_limit(response):
retry_after = response.headers.get("Retry-After")
if retry_after:
time.sleep(int(retry_after))
else:
# Repli : backoff exponentiel avec jitter
time.sleep(2)
Si le serveur demande d’attendre 30 secondes et que vous réessayez après 2 secondes, vous obtenez probablement un autre 429. Répéter ce comportement crée la boucle RateLimitError : limitation, réessai prématuré, limitation plus sévère, puis arrêt de l’exécution.
Un fil de discussion du SDK traite de ce problème rencontré par les développeurs.
Votre stratégie doit être :
- Lire
Retry-After. - Attendre au moins la durée demandée.
- Utiliser le backoff exponentiel avec jitter uniquement si l’en-tête est absent.
- Limiter le nombre de tentatives.
- Retourner une erreur claire lorsque la limite persiste.
Le SDK Anthropic respecte déjà Retry-After pour ses propres appels. Appliquez la même règle aux autres API limitées appelées par votre agent.
Ajoutez également une protection proactive : si le fournisseur annonce une limite par minute, utilisez un mécanisme de limitation côté client, par exemple un seau de jetons (token bucket), pour rester sous le plafond.
Comment tester le chemin de récupération
Les mécanismes ci-dessus ne valent que s’ils sont couverts par des tests qui déclenchent les pannes attendues.
Voici un scénario de récupération complet :
Mockez la dépendance
Créez un mock de l’API appelée par l’outil. Vous devez contrôler les codes HTTP, les en-têtes, les corps et les délais sans déclencher de vrai paiement ni envoyer de vrai e-mail.-
Scriptz une séquence de réponses
Configurez le mock pour répondre, dans cet ordre :-
429avecRetry-After: 2; -
500; -
200avec un corps valide.
-
Pointez l’agent vers le mock
Remplacez l’URL de production par l’URL du mock pour l’exécution du test.-
Affirmez le comportement
Vérifiez que :- l’agent attend au moins deux secondes après le
429; - il réessaie après le
500; - il réussit au troisième appel ;
- il ne dépasse jamais le nombre maximal de tentatives.
- l’agent attend au moins deux secondes après le
Exemple de test conceptuel :
def test_agent_recovers_from_429_then_500():
result = run_agent(base_url=mock_server.url)
assert mock_server.call_count == 3
assert mock_server.delay_before_second_call >= 2
assert result.status == "success"
Ajoutez au minimum deux autres scénarios.
Scénario d’abandon
Configurez le mock pour échouer à chaque requête.
Vérifiez que l’agent :
- arrête les tentatives à la limite configurée ;
- retourne une erreur propre ;
- ne boucle pas indéfiniment.
Scénario de disjoncteur
Configurez plusieurs échecs successifs, puis vérifiez que :
- le disjoncteur passe à l’état ouvert ;
- les appels suivants échouent rapidement ;
- aucune attente de timeout n’est payée pour chaque nouvelle requête.
Scénario d’idempotence
C’est le test qui évite les doubles envois coûteux.
- Acceptez un appel
POSTmodifiant un état. - Supprimez volontairement la réponse du mock.
- Laissez l’agent réessayer.
- Vérifiez que les deux requêtes utilisent la même
Idempotency-Key. - Vérifiez que le mock ne compte qu’une seule action logique.
Une nouvelle clé au second essai, ou deux opérations créées, signifie que vous avez détecté un double envoi avant vos utilisateurs.
Pour mettre en place un harnais plus complet, consultez comment tester les agents qui appellent vos API.
Liste de contrôle avant la production
Avant de déployer un agent, vérifiez les points suivants :
- [ ] Chaque appel sortant possède un timeout de connexion, de lecture et un budget global d’exécution.
- [ ] Les réessais utilisent un backoff exponentiel avec jitter.
- [ ] Les délais et le nombre de tentatives sont plafonnés.
- [ ] Les réponses
429lisent et respectentRetry-After. - [ ] Un disjoncteur est configuré par dépendance.
- [ ] Chaque opération modifiant un état utilise une clé d’idempotence stable.
- [ ] Le chemin d’abandon retourne une erreur claire plutôt qu’une attente infinie.
- [ ] Chaque comportement est validé contre un mock qui force la défaillance.
Si ces points sont couverts, votre agent se rétablit volontairement au lieu de dépendre de la chance.
Où Apidog s’intègre — et où il ne s’intègre pas
Gardez le rôle de l’outil clair : Apidog n’est pas un framework d’agent, un hébergeur de modèle ou un environnement d’exécution. Il ne construit pas, n’exécute pas et n’orchestre pas votre agent. Il n’évalue pas non plus la sortie du modèle.
Son rôle concerne la couche API appelée par votre agent, précisément l’endroit où la récupération se gagne ou se perd.
Apidog peut vous aider à :
- Simuler les dépendances utilisées par votre agent.
- Programmer des réponses d’échec :
429avecRetry-After,500, timeout ou corps malformé. - Vérifier les requêtes reçues par le mock :
- présence de
Idempotency-Key; - stabilité de la clé entre les tentatives ;
- forme du payload ;
- nombre d’appels attendu.
- présence de
L’intégration est donc simple : Apidog simule les échecs auxquels votre agent doit survivre et vérifie ce qu’il renvoie.
Foire aux questions
Le SDK Anthropic ne gère-t-il pas les réessais pour moi ?
Pour ses propres appels, oui. Le SDK réessaie certaines erreurs avec un backoff exponentiel, respecte Retry-After et expose une limite via max-retries.
Il ne couvre pas les autres API appelées par les outils de votre agent. Vous devez appliquer les mêmes mécanismes à ces dépendances.
Quand ai-je besoin d’une clé d’idempotence ?
Utilisez-en une pour tout appel qui crée ou modifie un état : paiements, commandes, messages, créations d’enregistrements ou mises à jour.
Les appels en lecture seule peuvent généralement être réessayés sans clé. Générez néanmoins la clé une fois par action logique afin qu’elle reste stable pendant tous les réessais.
Répétez un échec cette semaine
Vous n’avez pas besoin d’implémenter les quatre mécanismes immédiatement.
Commencez par le scénario le plus risqué pour votre système :
- boucle de limite de débit ;
- timeout sans abandon ;
- réessai non idempotent ;
- dépendance durablement indisponible.
Programmez un 429, supprimez une réponse après un appel modificateur, puis observez les requêtes envoyées par l’agent. Lorsque vous verrez un backoff propre et une seule clé d’idempotence là où vous craigniez un double paiement, votre confiance reposera sur un test reproductible plutôt que sur une démo réussie.
Utilisez Apidog pour simuler ces échecs, script les séquences de réponse et vérifier le comportement de votre agent lorsque l’API résiste.

Top comments (0)