Claude Agent SDK

Construire des systèmes agentiques en production

Applied AI — Niveau avancé · Session 3
Yann Isola · Préparation Claude Certified Architect

Slide 2 — Objectifs de la session

À l'issue des 2 heures, vous saurez :

  1. Décrire l'architecture du SDK : Agent, Runner, outils, handoffs, guardrails, hooks, contexte
  2. Implémenter des outils typés avec @tool
  3. Concevoir des handoffs et justifier le choix vs appel d'outil
  4. Poser des guardrails d'entrée/sortie
  5. Orchestrer coordinateur, pipeline, exécution parallèle
  6. Gérer erreurs, timeouts, replis

SDK = Software Development Kit (kit de développement logiciel)

Slide 3 — Pourquoi un SDK dédié ?

Appeler l'API (Application Programming Interface) « à la main » impose de réécrire :

  • la boucle envoi → détection tool call → exécution → réinjection → itération
  • la sérialisation des schémas d'outils
  • la gestion des erreurs, retries, timeouts
  • l'orchestration multi-agents

Le SDK n'est pas magique : c'est la boucle agentique de la session 1, packagée et outillée pour la production.

Slide 4 — Les 7 briques du SDK

Brique Rôle
Agent Configuration : nom, modèle, instructions, outils
Runner La boucle d'exécution
@tool Fonctions Python → capacités du modèle
Handoffs Transfert de contrôle entre agents
Guardrails Validation avant/après le modèle
Hooks Callbacks de cycle de vie (audit, métriques)
Contexte typé État partagé, invisible du modèle

Slide 5 — La classe Agent

from claude_agent_sdk import Agent

agent_support = Agent(
    name="support-client",           # routage, logs, handoffs
    model="claude-sonnet-4-5",       # ⚠ identifiant volatile
    instructions=(
        "Tu es l'agent support d'Acme. Réponds en français. "
        "Cite toujours la source interne utilisée."
    ),
    tools=[chercher_kb, creer_ticket],
)

instructions = le contrat comportemental. Tout ce qui n'y figure pas est laissé à l'interprétation du modèle.

Slide 6 — Le Runner : la boucle agentique

from claude_agent_sdk import Runner

res = Runner.run(agent_support, "Facturé deux fois ce mois-ci.")
print(res.final_output)

Ce que fait Runner.run() :

  1. Envoie message + instructions + schémas d'outils
  2. Le modèle répond : texte final ou appels d'outils
  3. Exécute les outils, réinjecte les résultats
  4. Boucle jusqu'à réponse finale, handoff, ou max_turns

Slide 7 — Le flux complet

Utilisateur → [Guardrail entrée] → Agent (modèle)
                                     │
                   ┌── appel outil ──┤── handoff ──→ Autre agent
                   ▼                 │
              Exécution outil        ▼
                   │           Réponse finale
                   └── résultat ──→ (boucle)
                                     │
                             [Guardrail sortie] → Utilisateur

Les hooks observent chaque flèche. Le contexte typé circule partout — jamais dans le prompt.

Slide 8 — ⚠ Piège certification : sync vs async

  • Runner.run()synchrone, scripts simples
  • Runner.run_async()asyncio, obligatoire pour le parallèle

Question type : « Cinq analyses indépendantes à lancer simultanément — quelle méthode ? » → run_async + asyncio.gather

Slide 9 — Outils : le décorateur @tool

@tool
def chercher_kb(requete: str, max_resultats: int = 5) -> str:
    """Recherche dans la base de connaissances interne.

    À utiliser pour toute question produit ou procédure.

    Args:
        requete: termes de recherche en langage naturel.
        max_resultats: nombre maximal de documents.
    """
    ...
  • Docstring → description envoyée au modèle
  • Type hints → schéma JSON (JavaScript Object Notation)
  • Valeur par défaut → paramètre optionnel

Slide 10 — La docstring est du prompt engineering

❌ """Cherche des trucs."""

✅ Dit quand utiliser l'outil, décrit chaque paramètre, précise les limites

La docstring n'est pas un commentaire pour vos collègues. C'est une instruction pour le modèle.

Slide 11 — Erreurs d'outils : métier vs technique

Type d'erreur Traitement Exemple
Métier Retourner un texte actionnable « Aucun résultat pour X. Vérifier le format SETL-XXX. »
Technique Lever une exception Base de données injoignable

Le texte actionnable permet au modèle de s'auto-corriger (reformuler, redemander).

Slide 12 — Bonnes pratiques outils

  • Un outil = une responsabilité (pas de faire_tout(action))
  • Nommer du point de vue du modèle (requete, pas q)
  • Toujours borner : max_resultats, timeouts, pagination
  • Retour concis et structuré — pas un dump JSON de 50 Ko

Slide 13 — Handoffs : le concept

Un handoff = transfert de contrôle : l'agent A passe la conversation à l'agent B.

Appel d'outil Handoff
Qui garde la main ? L'appelant La cible
Retour automatique ? Oui Non
Contexte transmis Arguments Historique complet
Cas d'usage Capacité ponctuelle Changement de spécialité

Slide 14 — Handoffs : implémentation

from claude_agent_sdk import Agent, handoff

agent_triage = Agent(
    name="triage",
    model="claude-haiku-4-5",   # ⚠ router = tâche simple → modèle léger
    instructions="Route vers le bon spécialiste. "
                 "Ne résous JAMAIS toi-même.",
    handoffs=[handoff(agent_facturation),
              handoff(agent_conformite)],
)

Le modèle voit chaque handoff comme un pseudo-outil transfer_to_Xc'est lui qui décide de router.

Slide 15 — Handoffs : trois points d'architecture

  1. L'agent cible hérite de l'historique — un spécialiste qui re-questionne = handoff mal configuré
  2. Handoff retour possible (cible → source) → risque de ping-pong → borner avec max_turns
  3. Référence circulaire : câbler le handoff retour après l'instanciation des agents

Slide 16 — Anti-pattern : le sous-agent amnésique

❌ Mauvais

Runner.run(agent_redacteur, "Rédige la section 2.")

✅ Bon

Runner.run(agent_redacteur, f"""
Mission : rédiger la section 2 du rapport « {titre} ».
Plan global : {plan}
Sections déjà rédigées (résumé) : {resume}
Ton : formel. Public : direction financière. 400–600 mots.
Livrable : Markdown, sans préambule.
""")

Un sous-agent ne partage pas votre mémoire de travail. Cause n° 1 d'échec multi-agents en production.

Slide 17 — Guardrails : validation dure

@input_guardrail
def bloquer_pan(ctx, agent, message: str):
    """Refuse tout numéro de carte bancaire."""
    if PAN_RE.search(message):
        return GuardrailTripwire(triggered=True,
            message="Utilisez le portail sécurisé.")
    return GuardrailTripwire(triggered=False)
  • Input guardrail : avant le modèle · Output guardrail : après
  • Tripwire déclenché → run interrompu + exception dédiée à intercepter

Slide 18 — Guardrails : règles vs LLM-as-judge

Règles / regex Juge LLM léger
Latence ~0 ms +200–800 ms ⚠
Coût 0 1 appel/réponse ⚠
Faux négatifs Élevés (paraphrases) Faibles

Production : défense en profondeur — règles en 1ʳᵉ ligne, juge en 2ᵉ ligne.

Slide 19 — Instructions vs guardrails

« Où mettre : ne jamais divulguer de données personnelles ? »

Les deux :

  • instructions → oriente le modèle (probabiliste)
  • guardrail → code qui bloque (déterministe)

La conformité réglementaire exige la couche déterministe.

Slide 20 — Hooks : observabilité du cycle de vie

class HooksAudit(RunHooks):
    async def on_tool_start(self, ctx, agent, tool):
        logger.info("agent=%s outil=%s", agent.name, tool.name)
    async def on_handoff(self, ctx, source, cible):
        logger.info("handoff %s → %s", source.name, cible.name)

Runner.run(agent, msg, hooks=HooksAudit())

Usages : audit réglementaire, métriques latence/coût, kill-switch.

Distinction d'examen : hook = observer · guardrail = bloquer. La sécurité principale va dans les guardrails.

Slide 21 — Variables de contexte : état typé partagé

@dataclass
class ContexteClient:
    client_id: str
    tier: str

@tool
def consulter_factures(ctx: RunContextWrapper[ContexteClient]) -> str:
    """Liste les factures du client authentifié."""
    return api.factures(ctx.context.client_id)  # jamais demandé au modèle

agent = Agent[ContexteClient](name="support", ...)
Runner.run(agent, msg, context=ContexteClient("C-4812", "premium"))

Slide 22 — ⚠ Règle de sécurité (classique d'examen)

L'identité de l'utilisateur vient du contexte applicatif (session authentifiée), JAMAIS d'un paramètre rempli par le modèle.

Sinon : une injection de prompt fait lire les factures d'autrui.

Le contexte n'est jamais sérialisé dans le prompt — seul ce que les outils retournent atteint le modèle.

Slide 23 — Pattern 1 : coordinateur + sous-agents

Le coordinateur décompose, délègue, agrège.

options_coordinateur = {
    "allowedTools": ["Read", "Grep", "Task"],  # ⬅ "Task" = droit de déléguer
    "maxTurns": 40,
}

Piège d'examen : sans "Task" dans allowedTools, pas de sous-agents — le coordinateur tente tout lui-même, silencieusement. Symptôme : « mon multi-agents n'utilise qu'un agent ».

Slide 24 — Pattern 2 : pipeline

brut    = Runner.run(agent_extracteur, document).final_output
analyse = Runner.run(agent_analyste,  f"Données :\n{brut}").final_output
rapport = Runner.run(agent_redacteur, f"Analyse :\n{analyse}").final_output

✅ Étapes testables isolément, modèle dimensionné par étape
❌ Latence cumulée, erreur amont propagée
guardrail de sortie entre les étapes

Slide 25 — Pattern 3 : parallèle

taches = [Runner.run_async(agent_analyste, c) for c in chunks]
resultats = await asyncio.gather(*taches, return_exceptions=True)
ok     = [r.final_output for r in resultats if not isinstance(r, Exception)]
echecs = [r for r in resultats if isinstance(r, Exception)]
  • return_exceptions=True : un échec n'annule pas les N−1 succès
  • Latence ÷ N, mais tokens simultanés × N → attention aux rate limits ⚠

Slide 26 — Grille de choix des patterns

Besoin Pattern
Spécialités disjointes, routage à l'entrée Triage + handoffs
Étapes dépendantes, transformation progressive Pipeline
Sous-tâches indépendantes, volume Parallèle + agrégateur
Décomposition dynamique à l'exécution Coordinateur + Task

Slide 27 — Gestion d'erreurs : trois familles

  1. Erreur d'outil → try/except dans l'outil, texte actionnable renvoyé au modèle
  2. Échec d'agentmax_turns (boucles), output guardrail + relance contrôlée, puis fallback
  3. Timeoutasyncio.wait_for(..., timeout=120) + idempotence des outils à effet de bord
try:
    res = await asyncio.wait_for(Runner.run_async(agent, msg), timeout=120)
except asyncio.TimeoutError:
    res = reponse_degradee(...)
except OutputGuardrailTripwireTriggered:
    res = escalade_humaine(msg)

Slide 28 — L'idempotence : le cas du remboursement

Un outil initier_remboursement retenté après timeout ne doit pas payer deux fois.

Solution : clé d'idempotence — le second appel avec la même clé est ignoré côté serveur.

Phrase de synthèse : en production, la question n'est pas si un agent échoue, mais quoi ensuite. Une architecture certifiable définit le comportement de chaque échec.

Slide 29 — Récapitulatif : les 8 réflexes de l'architecte

  1. Docstring d'outil = prompt engineering
  2. Erreur métier en texte, erreur technique en exception
  3. Handoff = transfert de contrôle + historique
  4. Contexte complet dans chaque prompt de délégation
  5. Identité via contexte typé, jamais via paramètre du modèle
  6. Guardrails = déterministe · instructions = probabiliste · hooks = observation
  7. "Task" dans allowedTools pour déléguer
  8. return_exceptions=True, max_turns, timeouts, idempotence

Slide 30 — Et maintenant

Exercices (à terminer en autonomie) :

  1. Agent + outils typés (45 min)
  2. Triage + handoffs (60 min)
  3. Guardrails + hooks d'audit (60 min)

Quiz d'ancrage : 10 QCM — objectif ≥ 8/10

Session 4 : (voir programme) — apporter vos exercices corrigés

⚠ Rappel : identifiants de modèles, noms de paquets et limites de débit évoluent — toujours vérifier la documentation officielle Anthropic.