Applied AI · AvancĂ© 🔮 · Session 3
📝 Guide du professeur
← Retour au programme 📄 Source .md

Guide Formateur — Session 3 (Niveau AvancĂ©)

Claude Agent SDK : construire des systĂšmes agentiques en production

Programme : Applied AI — Yann Isola Niveau : AvancĂ© — architectes solutions prĂ©parant la certification Claude Certified Architect DurĂ©e : 2 h 00 PrĂ©requis : Sessions 1–2 (architectures agentiques, orchestration multi-agents), Python intermĂ©diaire, notions d’API (Application Programming Interface — interface de programmation applicative) LLM (Large Language Model — grand modĂšle de langage).


1. Objectifs pédagogiques

À la fin de la session, les participants sauront :

  1. DĂ©crire l’architecture du Claude Agent SDK (Software Development Kit — kit de dĂ©veloppement logiciel) : Agent, Runner, outils, handoffs, guardrails, hooks, variables de contexte.
  2. Implémenter un agent avec outils typés via le décorateur @tool.
  3. Concevoir des handoffs entre agents et justifier ce choix face à l’appel d’outil classique.
  4. Poser des guardrails d’entrĂ©e et de sortie avec gestion des rejets.
  5. Orchestrer des patterns multi-agents : coordinateur + sous-agents, pipeline, exécution parallÚle.
  6. GĂ©rer les erreurs : Ă©chec d’outil, Ă©chec d’agent, timeout, stratĂ©gies de repli (fallback).

Lien certification : ces six objectifs couvrent les domaines « Agent Design » et « SDK Implementation » du rĂ©fĂ©rentiel Claude Certified Architect ⚠ (rĂ©fĂ©rentiel susceptible d’évoluer — vĂ©rifier la version en vigueur sur le site officiel Anthropic).


2. Déroulé minuté (120 min)

# Séquence Durée Format
0 Accueil + rappel session 2 5 min PléniÚre
1 Anatomie du SDK : Agent, Runner, boucle agentique 20 min Exposé + démo live
2 Outils : @tool, schémas, docstrings 15 min Live coding
3 Handoffs : transfert de contrÎle entre agents 15 min Exposé + démo
— Pause 10 min —
4 Guardrails et hooks 15 min Exposé + live coding
5 Patterns multi-agents : coordinateur, pipeline, parallÚle 20 min Exposé + page interactive
6 Gestion d’erreurs + anti-patterns 10 min ExposĂ© + discussion
7 Lancement des exercices (Ă  finir en autonomie) 8 min Ateliers
8 SynthĂšse + quiz d’ancrage 2 min PlĂ©niĂšre

3. Contenu détaillé

SĂ©quence 1 — Anatomie du SDK (20 min)

3.1.1 Pourquoi un SDK dédié ?

Point de dĂ©part : rappeler qu’appeler une API LLM « Ă  la main » impose de réécrire soi-mĂȘme la boucle agentique (envoi du message → dĂ©tection d’appel d’outil → exĂ©cution → renvoi du rĂ©sultat → itĂ©ration). Le Claude Agent SDK est le framework Python officiel qui industrialise cette boucle et y ajoute les briques de production : validation, observabilitĂ©, dĂ©lĂ©gation multi-agents.

Message clĂ© Ă  marteler : le SDK n’est pas une abstraction magique — c’est la boucle agentique de la session 1, packagĂ©e, testĂ©e et outillĂ©e.

3.1.2 La classe Agent

Un agent se définit par quatre attributs :

from claude_agent_sdk import Agent

agent_support = Agent(
    name="support-client",                      # identifiant unique
    model="claude-sonnet-4-5",                  # ⚠ nom de modùle volatile
    instructions=(
        "Tu es un agent de support de la société Acme. "
        "Réponds en français, cite toujours la source interne utilisée. "
        "Si la demande concerne un remboursement, transfĂšre Ă  l'agent facturation."
    ),
    tools=[chercher_kb, creer_ticket],           # liste de fonctions décorées @tool
)

Question Ă  poser Ă  la salle : « OĂč mettriez-vous la rĂšgle “ne jamais divulguer de donnĂ©es personnelles” : dans instructions ou dans un guardrail ? » — RĂ©ponse attendue en sĂ©quence 4 : les deux ; les instructions orientent, le guardrail garantit.

3.1.3 Le Runner : la boucle

from claude_agent_sdk import Runner

resultat = Runner.run(
    agent_support,
    "Mon abonnement a été facturé deux fois ce mois-ci.",
)
print(resultat.final_output)

Dérouler au tableau ce que Runner.run() fait réellement :

  1. Envoie le message utilisateur + instructions + schĂ©mas d’outils au modĂšle.
  2. Le modĂšle rĂ©pond : soit un texte final, soit un ou plusieurs appels d’outils.
  3. Le Runner exécute les outils, renvoie leurs résultats au modÚle.
  4. Boucle jusqu’à obtention d’une rĂ©ponse finale (ou dĂ©clenchement d’un handoff, ou dĂ©passement du plafond max_turns).

Schéma à dessiner (repris dans la page interactive) :

Utilisateur → [Guardrail entrĂ©e] → Agent (modĂšle)
                                      │
                    ┌─── appel outil ───── handoff ───→ Autre agent
                    â–Œ                 │
                ExĂ©cution outil       â–Œ
                    │           RĂ©ponse finale
                    └── rĂ©sultat ──→ (boucle)
                                      │
                              [Guardrail sortie] → Utilisateur

PiĂšge de certification : Runner.run() est synchrone ; Runner.run_async() (asyncio) est requis pour l’exĂ©cution parallĂšle (sĂ©quence 5). Une question type demande de choisir la bonne variante selon le scĂ©nario.


SĂ©quence 2 — Outils : @tool (15 min)

3.2.1 Le décorateur

from claude_agent_sdk import tool

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

    Args:
        requete: termes de recherche en langage naturel.
        max_resultats: nombre maximal de documents retournés.
    """
    docs = kb_client.search(requete, limit=max_resultats)
    return "\n---\n".join(d.snippet for d in docs)

Trois mécanismes à expliciter :

  1. La docstring devient la description de l’outil envoyĂ©e au modĂšle. C’est un artefact de prompt engineering, pas un commentaire : elle doit dire quand utiliser l’outil, pas seulement ce qu’il fait.
  2. Les annotations de type gĂ©nĂšrent le schĂ©ma JSON (JSON — JavaScript Object Notation, format d’échange de donnĂ©es) : str → "type": "string", int → "type": "integer", valeurs par dĂ©faut → paramĂštres optionnels. Types complexes : utiliser Pydantic ou TypedDict.
  3. La valeur de retour est renvoyĂ©e au modĂšle telle quelle (convertie en texte). Retourner du contenu structurĂ© et concis — pas un dump JSON de 50 Ko.

3.2.2 Bonnes pratiques (Ă  dicter)

Exercice Ă©clair (3 min) : faire critiquer cette docstring : """Cherche des trucs.""" — attendre : pas de cas d’usage, pas de description des paramĂštres, pas de limite.


SĂ©quence 3 — Handoffs (15 min)

3.3.1 Concept

Un handoff est un transfert de contrĂŽle : l’agent A dĂ©cide que l’agent B est mieux placĂ© et lui passe la conversation. DiffĂ©rence fondamentale avec l’appel d’outil :

Appel d’outil Handoff
Qui garde la main ? L’agent appelant L’agent cible
Retour au premier agent ? Oui, automatique Non (sauf handoff retour explicite)
Contexte transmis Arguments de l’outil Historique de conversation
Cas d’usage CapacitĂ© ponctuelle Changement de spĂ©cialitĂ©

3.3.2 Implémentation

Le SDK exprime le handoff via la liste handoffs de l’agent et, dans les signatures d’outils de routage, via l’annotation de type de retour pointant vers un agent :

from claude_agent_sdk import Agent, handoff

agent_facturation = Agent(
    name="facturation",
    model="claude-sonnet-4-5",  # ⚠ volatile
    instructions="Tu traites remboursements et litiges de facturation. "
                 "Tu as accĂšs Ă  l'historique complet de la conversation.",
    tools=[consulter_factures, initier_remboursement],
)

agent_triage = Agent(
    name="triage",
    model="claude-haiku-4-5",   # ⚠ volatile — modĂšle lĂ©ger pour router
    instructions="Analyse la demande et route vers le bon spécialiste. "
                 "Ne tente JAMAIS de rĂ©soudre toi-mĂȘme.",
    handoffs=[handoff(agent_facturation), handoff(agent_support)],
)

Points Ă  souligner :

3.3.3 Anti-pattern : le sous-agent amnésique

Mauvais (Ă  projeter) :

# ❌ Le coordinateur dĂ©lĂšgue sans contexte
Runner.run(agent_redacteur, "Rédige la section 2.")
# → l'agent ne sait ni de quel document il s'agit, ni le ton, ni le plan

Bon :

#  Contexte complet dans le prompt de délégation
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_sections}
Ton : formel, public : direction financiùre. Longueur : 400–600 mots.
Livrable : Markdown uniquement, sans préambule.
""")

RĂšgle Ă  faire noter : un sous-agent ne partage pas votre mĂ©moire de travail. Tout ce qu’il doit savoir doit ĂȘtre dans son prompt ou dans le contexte transmis. C’est la source n° 1 d’échec des systĂšmes multi-agents en production.


SĂ©quence 4 — Guardrails et hooks (15 min)

3.4.1 Guardrails

Validateurs exécutés avant (input guardrail) ou aprÚs (output guardrail) le passage par le modÚle.

from claude_agent_sdk import input_guardrail, output_guardrail, GuardrailTripwire

@input_guardrail
def bloquer_donnees_carte(ctx, agent, message: str):
    """Rejette tout message contenant un numéro de carte bancaire."""
    if re.search(r"\b\d{4}[ -]?\d{4}[ -]?\d{4}[ -]?\d{4}\b", message):
        return GuardrailTripwire(
            triggered=True,
            message="Ne transmettez jamais de numéro de carte. "
                    "Utilisez le portail sécurisé.",
        )
    return GuardrailTripwire(triggered=False)

@output_guardrail
def verifier_pas_de_promesse(ctx, agent, sortie: str):
    """EmpĂȘche l'agent de promettre un remboursement non validĂ©."""
    verdict = petit_modele_classifieur(sortie)   # LLM léger en juge
    return GuardrailTripwire(triggered=verdict == "promesse_engageante")

À expliciter :

DĂ©fense en profondeur (schĂ©ma Ă  dessiner) : instructions (souple) → guardrails (dur) → permissions d’outils (dur) → audit via hooks (a posteriori).

3.4.2 Hooks

Callbacks de cycle de vie pour l’observabilitĂ© et le contrĂŽle :

from claude_agent_sdk import RunHooks

class HooksAudit(RunHooks):
    async def on_tool_start(self, ctx, agent, tool):
        logger.info("agent=%s outil=%s args=%s", agent.name, tool.name, ctx.tool_args)

    async def on_tool_end(self, ctx, agent, tool, result):
        metrics.timing(f"tool.{tool.name}.latency", ctx.elapsed_ms)

    async def on_handoff(self, ctx, source, cible):
        logger.info("handoff %s → %s", source.name, cible.name)

resultat = Runner.run(agent_triage, message, hooks=HooksAudit())

Hooks principaux : on_agent_start, on_agent_end, on_tool_start, on_tool_end, on_handoff. Cas d’usage : logs d’audit (conformitĂ©), mĂ©triques (latence, coĂ»t), injection de contexte, kill-switch.

Distinction certification : guardrail = contrÎle bloquant sur le contenu ; hook = observation/instrumentation du cycle de vie. Un hook ne devrait pas porter la logique de sécurité principale.

3.4.3 Variables de contexte

État typĂ© partagĂ© entre agents, outils, guardrails et hooks d’un mĂȘme run — jamais envoyĂ© au modĂšle (contrairement au prompt) :

from dataclasses import dataclass
from claude_agent_sdk import Agent, Runner, RunContextWrapper

@dataclass
class ContexteClient:
    client_id: str
    tier: str            # "standard" | "premium"
    langue: str

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

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

Message sĂ©curitĂ© : l’identitĂ© du client vient du contexte applicatif (session authentifiĂ©e), jamais d’un paramĂštre que le modĂšle remplit — sinon un prompt injection peut lire les factures d’autrui. C’est un grand classique d’examen.


SĂ©quence 5 — Patterns multi-agents (20 min)

Projeter la page interactive (webpage/index.html) et dérouler le simulateur de flux.

3.5.1 Coordinateur + sous-agents

Le coordinateur dĂ©compose, dĂ©lĂšgue, agrĂšge. Dans l’environnement d’exĂ©cution Claude, la dĂ©lĂ©gation passe par l’outil Task : le coordinateur doit donc l’avoir dans ses outils autorisĂ©s.

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

Point d’examen : un coordinateur dont allowedTools n’inclut pas "Task" ne peut pas crĂ©er de sous-agents — il tentera de tout faire lui-mĂȘme, silencieusement. SymptĂŽme typique : « mon architecture multi-agents n’utilise qu’un agent ». Cause : permission manquante, pas bug du modĂšle.

3.5.2 Pipeline

ChaĂźne sĂ©quentielle : sortie de l’agent N = entrĂ©e de l’agent N+1.

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

Avantages : chaque Ă©tape testable isolĂ©ment, modĂšles dimensionnĂ©s par Ă©tape (extracteur lĂ©ger, analyste puissant). InconvĂ©nient : latence cumulĂ©e, erreur amont propagĂ©e — d’oĂč l’intĂ©rĂȘt d’un guardrail de sortie entre les Ă©tapes.

3.5.3 ParallĂšle

TĂąches indĂ©pendantes → exĂ©cution concurrente avec asyncio :

import asyncio
from claude_agent_sdk import Runner

async def analyser_dossier(chunks: list[str]):
    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 ok, echecs

À souligner : return_exceptions=True — un Ă©chec ne doit pas annuler les N−1 succĂšs. Puis un agent agrĂ©gateur fusionne les ok et signale les echecs.

Arbitrage coĂ»t/latence : le parallĂšle divise la latence perçue mais multiplie les tokens consommĂ©s simultanĂ©ment (attention aux limites de dĂ©bit — rate limits ⚠, variables selon le palier de compte).

3.5.4 Grille de choix (Ă  faire recopier)

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 dĂ©cidĂ©e Ă  l’exĂ©cution Coordinateur + Task

SĂ©quence 6 — Gestion d’erreurs (10 min)

Trois familles :

  1. Erreur d’outil. Exception dans le code de l’outil. Par dĂ©faut le SDK renvoie l’erreur au modĂšle, qui peut retenter ou contourner. Pour maĂźtriser le message : dĂ©corer d’un try/except et retourner un texte actionnable (« Le service factures est indisponible, rĂ©essaie dans 30 s ou informe l’utilisateur »).
  2. Échec d’agent. Boucle infinie ou dĂ©rive → borner avec max_turns ; sortie invalide → output guardrail + une relance contrĂŽlĂ©e, puis fallback (rĂ©ponse dĂ©gradĂ©e, escalade humaine).
  3. Timeout. Toujours envelopper : asyncio.wait_for(Runner.run_async(...), timeout=120). PrĂ©voir l’idempotence des outils Ă  effet de bord (un remboursement retentĂ© ne doit pas partir deux fois → clĂ© d’idempotence).
try:
    res = await asyncio.wait_for(Runner.run_async(agent, msg), timeout=120)
except asyncio.TimeoutError:
    res = reponse_degradee("Analyse trop longue, version abrégée fournie.")
except OutputGuardrailTripwireTriggered:
    res = escalade_humaine(msg)

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.


SĂ©quence 7 — Exercices (8 min)

Présenter les trois exercices (exercises/exercises.md) :

  1. Agent avec outils (@tool, schĂ©mas, docstrings) — 45 min estimĂ©es.
  2. Handoffs triage → spĂ©cialistes — 60 min.
  3. Guardrails entrĂ©e/sortie + hooks d’audit — 60 min.

BarÚme indicatif et solutions commentées inclus dans le document exercices.


4. Matériel et logistique

5. PiÚges fréquents des participants

PiĂšge Correction Ă  apporter
Docstrings d’outils vides ou vagues Rappeler : la docstring EST le prompt de l’outil
Confusion handoff / appel d’outil Revenir au tableau comparatif (qui garde la main ?)
IdentitĂ© utilisateur passĂ©e en paramĂštre d’outil Variables de contexte + dĂ©monstration d’injection
Coordinateur sans "Task" dans allowedTools Faire reproduire le symptĂŽme, puis corriger
asyncio.gather sans return_exceptions=True Simuler un échec sur 1 tùche parmi 5
Sous-agent délégué sans contexte Projeter le Bad vs Good de la séquence 3

6. Questions probables (FAQ formateur)

« Quelle différence entre guardrail et instructions systÚme ? » Les instructions influencent le modÚle (probabiliste) ; le guardrail est du code déterministe qui bloque. La conformité exige les deux.

« Peut-on faire un handoff retour ? » Oui — l’agent cible peut lister l’agent source dans ses propres handoffs. Attention aux boucles : borner avec max_turns et journaliser via on_handoff.

« Handoff ou agent-as-tool ? » Handoff = transfert dĂ©finitif de la conversation. Agent-as-tool = le coordinateur consulte un agent et garde la main. Si l’utilisateur doit continuer Ă  dialoguer avec le spĂ©cialiste → handoff.

« Les variables de contexte sont-elles visibles du modĂšle ? » Non, jamais sĂ©rialisĂ©es dans le prompt. C’est prĂ©cisĂ©ment leur intĂ©rĂȘt (secrets, identifiants). Seul ce que les outils retournent atteint le modĂšle.


Fin du guide formateur — Session 3, niveau avancĂ©.