---
marp: true
theme: default
paginate: true
style: |
  :root { --ink:#1A2230; --teal:#0F7A6C; --copper:#B4612A; --teal-light:#E9F6F3; --bg:#F4F7F6; }
  section { background: var(--bg); color: var(--ink); font-family: "Segoe UI", system-ui, sans-serif; }
  h1, h2 { color: var(--teal); }
  strong { color: var(--copper); }
  code { background: var(--teal-light); color: var(--ink); }
  pre code { background: var(--ink); color: #E6EDF3; }
  blockquote { border-left: 5px solid var(--copper); padding-left: 14px; color: #4A5568; }
  table { font-size: 0.85em; }
---

<!-- _class: lead -->

# 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`

```python
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

```python
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`

```python
@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

```python
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_X` — **c'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**
```python
Runner.run(agent_redacteur, "Rédige la section 2.")
```

✅ **Bon**
```python
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

```python
@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

```python
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é

```python
@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.

```python
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

```python
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

```python
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'agent** → `max_turns` (boucles), output guardrail + relance contrôlée, puis **fallback**
3. **Timeout** → `asyncio.wait_for(..., timeout=120)` + **idempotence** des outils à effet de bord

```python
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

---

<!-- _class: lead -->

## 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**.
