Pannes, délais et dégradation propre
Ce qui peut casser dans un système distribué
flowchart LR O["Orchestrateur"] -->|"1"| C["Lecture de la carte"] O -->|"2"| M["Envoi du message"] O -->|"3"| T["Attente de la tâche"] C -.->|"serveur arrêté"| E1["AgentCardResolutionError"] M -.->|"401, 5xx, réseau"| E2["A2A request failed"] T -.->|"délai dépassé"| E3["timeout HTTP"] T -.->|"TASK_STATE_FAILED"| E4["Remote A2A task failed"]
Dans ADK 2.10, RemoteA2aAgent ne lève pas ces erreurs vers l'orchestrateur : il les attrape et produit un événement ADK portant un error_message (par exemple « Failed to resolve remote A2A agent… » ou « A2A request failed: … »). Vu de l'orchestrateur, l'AgentTool renvoie ce message comme résultat d'outil. Le modèle de l'orchestrateur le lit et doit en tirer la bonne conclusion : c'est le rôle de l'instruction « Si un agent est indisponible ou renvoie une erreur, dis-le clairement et donne uniquement l'information obtenue ».
| Panne | Où elle apparaît | Ce que doit faire l'orchestrateur |
|---|---|---|
| Agent absent au démarrage | discover() → missing | ne pas créer l'outil, l'annoncer dans l'instruction |
| Agent tombé après le démarrage | message d'erreur de l'outil | annoncer l'indisponibilité, répondre avec le reste |
| Agent trop lent | timeout HTTP du RemoteA2aAgent | idem, et ne pas attendre indéfiniment |
| Agent qui répond une erreur métier (référence inconnue) | texte de la réponse | relayer l'erreur, proposer une correction |
Dans ADK 2.10, que reçoit l'orchestrateur quand l'agent tarification est arrêté au moment de l'appel ?
RemoteA2aAgent capture les erreurs réseau et de résolution de carte et les transforme en événement avec error_message. La conversation continue : c'est l'instruction qui garantit une réponse honnête.
Tester qu'il n'invente pas
Un test de panne utile ne vérifie pas seulement « pas de plantage » : il vérifie qu'une valeur impossible à connaître n'apparaît pas. Avec l'agent tarification arrêté, le prix de 6 bureaux (6 × 329 € × 0,95 = 1 875,30 €) ne doit pas figurer dans la réponse :
print("3) Panne de l'agent tarification pendant le service")
procs.pop("pricing_agent").kill()
t = send(ORCH, "Devis pour 6 BUREAU-120")
assert t["status"]["state"] == "TASK_STATE_COMPLETED"
assert "18753" not in digits(task_text(t)) # le prix (6*329*0,95) ne peut pas être connuLe délai est testé en relançant l'orchestrateur avec A2A_TIMEOUT=1 : une seconde ne suffit pas à un agent qui appelle un modèle puis un outil.
procs["orchestrator"] = start("orchestrator", A2A_TIMEOUT="1")
t = send(ORCH, "Devis pour 3 BUREAU-120")
assert "987" not in digits(task_text(t)) # 3*329 = 987 : ne doit pas apparaîtreRésultat obtenu
Scénarios 3 et 5 de e2e_a2a.py, le 2026-10-15 (vrai modèle, vrais processus arrêtés) :
3) Panne de l'agent tarification pendant le service
réponse : Voici les informations concernant votre demande de devis pour **6 unités de BUREAU-120** : * **Disponibilité en stock :** * Stock total disponible : **8 unités** (suffisant pour honorer votre demande de 6 unités). * Répartition par entrepôt : **5 à Lille** et **3 à Lyon**. * **Prix HT :** *
5) Délai dépassé (A2A_TIMEOUT=1 s)
réponse : Bonjour, Les services de vérification des stocks (`stock_agent`) et de calcul des prix (`pricing_agent`) sont actuellement indisponibles (délai d'attente dépassé). Par conséquent, il est impossible de vérifier la disponibilité et de calculer le montant total HT pour les 3 exemplaires de la référe
OK : 5 scénarios passésLors d'un essai manuel du même cas 3, la fin de la réponse était : « L'agent de tarification (pricing_agent) a rencontré une erreur et n'a pas pu calculer le prix. » Et le journal de l'orchestrateur contenait :
Failed to resolve remote A2A agent pricing_agent: Failed to resolve AgentCard from URL http://localhost:8102/.well-known/agent-card.json: Network communication error fetching agent card from http://localhost:8102/.well-known/agent-card.json: All connection attempts failedRéessayer ou pas ?
| Situation | Réessayer ? |
|---|---|
| Erreur réseau transitoire, HTTP 503 | oui, une ou deux fois avec attente croissante |
| HTTP 401 / 403 | non : c'est une configuration à corriger |
| Délai dépassé sur une opération avec effet | pas aveuglément : vérifiez l'état de la tâche d'abord |
| Erreur métier (référence inconnue) | non : relayer au client |
Lequel de ces cas NE doit PAS être réessayé automatiquement ?
Un 401 signifie que le jeton est absent ou invalide : réessayer ne changera rien et peut déclencher des alertes de sécurité. Les erreurs transitoires (503, réseau) justifient un ou deux essais avec attente.
Exercice : un disjoncteur
Quand un agent échoue trois fois de suite, l'orchestrateur devrait cesser de l'appeler pendant 30 secondes (motif du disjoncteur, circuit breaker). Où placer cette logique dans ADK ?
Voir la solution
Dans un before_tool_callback de l'orchestrateur : il voit passer chaque appel d'AgentTool et peut répondre à sa place, sans réseau.
import time
FAILS, OPEN_UNTIL = {}, {}
def breaker_before(tool, args, tool_context):
if OPEN_UNTIL.get(tool.name, 0) > time.monotonic():
return {"error": f"{tool.name} indisponible (disjoncteur ouvert)"}
def breaker_after(tool, args, tool_context, tool_response):
failed = "error" in str(tool_response).lower()
FAILS[tool.name] = FAILS.get(tool.name, 0) + 1 if failed else 0
if FAILS[tool.name] >= 3:
OPEN_UNTIL[tool.name], FAILS[tool.name] = time.monotonic() + 30, 0Puis Agent(..., before_tool_callback=breaker_before, after_tool_callback=breaker_after). La détection « contient error » est grossière ; en production, marquez explicitement les échecs.
Je sais faire
- Lister les quatre types de panne d'un appel A2A
- Écrire un test qui prouve qu'une valeur inconnue n'est pas inventée
- Régler un timeout adapté et en mesurer l'effet
- Décider quand réessayer et quand s'abstenir