Récupération d'erreurs pour les agents
Comment les agents LLM devraient gérer et se remettre des erreurs — réessayer, stocker, apprendre.
Récupération d'erreurs pour les agents
Les erreurs arrivent. Les réseaux tombent, les API renvoient des 500, les Mind Keys expirent. Ce guide montre comment les agents LLM devraient gérer les erreurs gracieusement et en tirer des leçons.
Principes de gestion des erreurs
- Réessayer avec backoff — les erreurs transitoires se résolvent souvent
- Stocker l'erreur — apprendre des schémas
- Dégrader gracieusement — ne pas crasher toute la session
- Notifier l'humain — pour les erreurs que vous ne pouvez pas résoudre
Gestion des erreurs HTTP
Réessayer avec backoff exponentiel
import time
import requests
def call_with_retry(url, max_retries=3, backoff_base=2):
"""Call URL with exponential backoff."""
for attempt in range(max_retries):
try:
r = requests.get(url, headers={"Authorization": f"Bearer {KEY}"})
# Succès
if r.status_code < 400:
return r
# Limité en débit — attendre et réessayer
if r.status_code == 429:
wait = int(r.headers.get("Retry-After", 60))
print(f"Rate limited. Waiting {wait}s...")
time.sleep(wait)
continue
# Erreur serveur — réessayer avec backoff
if r.status_code >= 500:
wait = backoff_base ** attempt
print(f"Server error {r.status_code}. Retrying in {wait}s...")
time.sleep(wait)
continue
# Erreur client — ne pas réessayer
if 400 <= r.status_code < 500:
raise ClientError(f"{r.status_code}: {r.text}")
except requests.RequestException as e:
# Erreur réseau — réessayer
wait = backoff_base ** attempt
print(f"Network error: {e}. Retrying in {wait}s...")
time.sleep(wait)
raise MaxRetriesError(f"Failed after {max_retries} retries")Gestion des erreurs d'auth
def safe_call(url):
"""Call with auth error handling."""
try:
return call_with_retry(url)
except ClientError as e:
if "401" in str(e):
# Mind Key invalide — critique, impossible à récupérer
store_error("auth_invalid", str(e), "Check Mind Key")
notify_human("My Mind Key is invalid. Please update.")
raise AuthError("Cannot continue without valid Mind Key")
elif "403" in str(e):
store_error("forbidden", str(e), "Wrong token type?")
raise ForbiddenError(str(e))
elif "404" in str(e):
# Le chemin n'existe pas — ne pas réessayer
store_error("not_found", str(e), "Check endpoint path")
raise NotFoundError(str(e))
else:
raiseStocker les erreurs en mémoire
Quand des erreurs se produisent, stockez-les pour que les futures sessions puissent apprendre :
def store_error(error_type, error_message, recovery_hint=""):
"""Store an error as a memory for future reference."""
requests.post(f"{URL}/memory",
headers={"Authorization": f"Bearer {KEY}",
"Content-Type": "application/json"},
json={
"category": "mistake",
"key": f"error_{error_type}_{int(time.time())}",
"content": f"Error: {error_type}\nMessage: {error_message}\nRecovery: {recovery_hint}",
"tags": ["error", error_type],
"priority": "high"
})
# Exemple
try:
deploy()
except DeployError as e:
store_error("deploy_failed", str(e),
"Check CI logs, verify Docker image exists")
raiseScénarios d'erreur courants
Scénario 1 : Mind Key invalide
# Détection : 401 sur chaque appel
# Récupération : Impossible — nécessite une intervention humaine
def handle_invalid_mind_key():
store_error("mind_key_invalid",
"All API calls returning 401",
"Mind Key may be revoked. Need new key.")
# Notifier l'humain via chat (si possible)
try:
reply("⚠️ My Mind Key is invalid. I cannot access memories. "
"Please check and update SYNAPSE_MIND_KEY.")
except:
pass # Le chat peut aussi échouer avec une mauvaise clé
# Sortir gracieusement
raise CriticalError("Cannot continue without valid Mind Key")Scénario 2 : erreur réseau
# Détection : ConnectionError, Timeout
# Récupération : Réessayer avec backoff, puis dégrader
def handle_network_error(url, retry=3):
for attempt in range(retry):
try:
return requests.get(url, timeout=10)
except (requests.ConnectionError, requests.Timeout) as e:
wait = 2 ** attempt
print(f"Network error, retrying in {wait}s: {e}")
time.sleep(wait)
# Tous les essais échoués — dégrader
store_error("network_failure",
f"Cannot reach {url}",
"Check internet connection, Synapse may be down")
# Travailler hors ligne si possible
return work_offline()Scénario 3 : limité en débit
# Détection : 429 avec en-tête Retry-After
# Récupération : Attendre et réessayer, ou passer à l'auth par en-tête
def handle_rate_limit(response):
retry_after = int(response.headers.get("Retry-After", 60))
print(f"Rate limited. Waiting {retry_after}s...")
time.sleep(retry_after)
# Si cela continue, suggérer de passer à l'auth par en-tête
if has_query_param_auth(url):
store_error("rate_limited",
"Frequent 429s with ?key= auth",
"Switch to Authorization: Bearer header (no rate limit)")Scénario 4 : erreur serveur (5xx)
# Détection : 500, 502, 503
# Récupération : Réessayer avec backoff, vérifier /health
def handle_server_error(url):
# Vérifier si le serveur fonctionne
health = requests.get(f"{URL}/health")
if health.status_code != 200:
store_error("server_down",
"Synapse health check failing",
"Wait for server recovery")
raise ServerDownError()
# Réessayer avec backoff
return call_with_retry(url, max_retries=5)Scénario 5 : échec d'appel d'outil
# Détection : L'outil renvoie un contenu d'erreur
# Récupération : Essayer une approche alternative, stocker l'échec
def call_tool_safely(tool_name, args, alternatives=None):
try:
result = call_tool(tool_name, args)
if result.get("isError"):
raise ToolError(result["content"])
return result
except ToolError as e:
store_error(f"tool_{tool_name}_failed",
f"Args: {args}\nError: {e}",
f"Try: {alternatives or 'no alternatives'}")
# Essayer des alternatives
if alternatives:
for alt in alternatives:
try:
return call_tool(alt, args)
except:
continue
raiseSchéma : disjoncteur
Pour les échecs répétés, arrêter d'essayer temporairement :
class CircuitBreaker:
def __init__(self, threshold=5, reset_time=300):
self.failures = 0
self.threshold = threshold
self.reset_time = reset_time
self.last_failure = 0
def call(self, fn, *args, **kwargs):
if self.failures >= self.threshold:
if time.time() - self.last_failure < self.reset_time:
raise CircuitOpenError("Circuit breaker open")
else:
# Réinitialiser
self.failures = 0
try:
result = fn(*args, **kwargs)
self.failures = 0 # Réinitialiser sur succès
return result
except:
self.failures += 1
self.last_failure = time.time()
raise
# Utilisation
breaker = CircuitBreaker(threshold=5)
try:
result = breaker.call(api_call, url)
except CircuitOpenError:
print("Too many failures, waiting before retry")