Leçon 6 — Robustesse et sécurité

Pannes, délais et dégradation propre

5 min de lecture

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 ».

PanneOù elle apparaîtCe que doit faire l'orchestrateur
Agent absent au démarragediscover() → missingne pas créer l'outil, l'annoncer dans l'instruction
Agent tombé après le démarragemessage d'erreur de l'outilannoncer l'indisponibilité, répondre avec le reste
Agent trop lenttimeout HTTP du RemoteA2aAgentidem, et ne pas attendre indéfiniment
Agent qui répond une erreur métier (référence inconnue)texte de la réponserelayer 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 ?

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 :

files/e2e_a2a.py
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 connu

Le 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.

files/e2e_a2a.py
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ître

Ré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és

Lors 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 failed

Réessayer ou pas ?

SituationRéessayer ?
Erreur réseau transitoire, HTTP 503oui, une ou deux fois avec attente croissante
HTTP 401 / 403non : c'est une configuration à corriger
Délai dépassé sur une opération avec effetpas 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 ?

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, 0

Puis 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
Suivant