title: "Claude API : plongée en profondeur"
subtitle: "Applied AI — Niveau Avancé · Session 1"
author: "Yann Isola"
theme: "ink #1A2230 / teal #0F7A6C / copper #B4612A / light-teal #E9F6F3 / bg #F4F7F6"

Slide 1 — Titre

Claude API : plongée en profondeur

Applied AI — Niveau Avancé · Session 1
Yann Isola · Préparation Claude Certified Architect

Notes : Cadrer d'emblée — cette session n'est pas du prompting, c'est de l'architecture. Contrat d'API, économie de tokens, modes de défaillance.

Slide 2 — Objectifs de la session

  • Construire une requête API complète et justifier chaque paramètre
  • Interpréter les 4 valeurs de stop_reason → logique applicative
  • Raisonner sur la fenêtre de contexte comme mémoire de travail
  • Maîtriser le prompt caching (cache_control) et son ROI
  • Implémenter le streaming SSE (SSE = Server-Sent Events)
  • Arbitrer synchrone / streaming / Batches API
  • Concevoir la gestion d'erreurs : 429, backoff, retry-after

Notes : 7 objectifs = 7 blocs. Tout est évalué en QCM certification.

Slide 3 — Le contrat minimal

Trois champs obligatoires : model, max_tokens, messages

curl https://api.anthropic.com/v1/messages \
  -H "x-api-key: $ANTHROPIC_API_KEY" \
  -H "anthropic-version: 2023-06-01" \
  -H "content-type: application/json" \
  -d '{
    "model": "claude-sonnet-4-5",
    "max_tokens": 1024,
    "messages": [{"role": "user", "content": "Bonjour"}]
  }'

⚠ Noms de modèles volatils — toujours vérifier docs.anthropic.com

Notes : anthropic-version épingle le contrat d'API. Sans lui → erreur. max_tokens n'a PAS de défaut (piège Q1 du quiz).

Slide 4 — max_tokens : un plafond, pas une cible

  • Le modèle peut s'arrêter avant le plafond
  • S'il l'atteint → réponse tronquée avec HTTP 200 ✅😱
  • Facturation = tokens d'entrée + tokens de sortie (tarifs différents, sortie ≈ 5× ⚠)
"usage": {"input_tokens": 58, "output_tokens": 1024}

output_tokens == max_tokens → suspicion de troncature

Notes : Anecdote terrain : systèmes en prod livrant des JSON tronqués pendant des semaines faute de tester stop_reason. Transition vers slide 8.

Slide 5 — L'appel via SDK Python

import anthropic
client = anthropic.Anthropic()  # lit ANTHROPIC_API_KEY

response = client.messages.create(
    model="claude-sonnet-4-5",        # ⚠
    max_tokens=1024,
    temperature=0.2,                   # tâche technique
    stop_sequences=["FIN_RAPPORT"],
    system="Analyste financier. Français, concis.",
    messages=[{"role": "user", "content": "..."}],
)
print(response.content[0].text)
print(response.usage, response.stop_reason)

Notes : SDK = Software Development Kit. Insister : logger usage systématiquement (observabilité des coûts). content est un TABLEAU de blocs.

Slide 6 — Paramètres d'échantillonnage

Paramètre Rôle Reco architecte
temperature 0–1 aléa 0–0.3 extraction · 0.7–1 créatif
top_p nucleus sampling ne pas combiner avec temperature
top_k k tokens les + probables cas avancés uniquement
stop_sequences arrêts personnalisés sorties délimitées

Piège certif : temperature: 0 ≠ déterminisme parfait

Notes : Formulation correcte : « réduit fortement la variabilité ». Non-déterminisme numérique résiduel.

Slide 7 — Trois rôles, trois privilèges

  1. system — canal privilégié (hors du tableau messages)
    → persona, règles, politique de sortie. Pas « un premier message déguisé »
  2. user — l'appelant : questions, documents, tool_result
  3. assistant — le modèle… et le prefilling

Règle : alternance stricte, premier message = user
Deux rôles identiques consécutifs → 400

Notes : Le system prompt est traité avec une priorité particulière par le modèle — résistance accrue aux contournements dans les tours user. API sans état : tout est renvoyé à chaque appel.

Slide 8 — Prefilling : mettre des mots dans la bouche du modèle

messages=[
    {"role": "user", "content": "Liste 3 risques en JSON."},
    {"role": "assistant", "content": "{"}   # ← prefill
]
  • Le modèle continue à partir de {
  • Supprime les préambules (« Bien sûr ! Voici… »)
  • ⚠ Le prefill n'est pas dans la réponse → re-préfixer côté client

Notes : Démo live : même question avec/sans prefill. JSON = JavaScript Object Notation. Autre usage : forcer un choix de QCM avec prefill « La réponse est ( ».

Slide 9 — stop_reason : les 4 signaux

stop_reason Signification Réaction
end_turn fin naturelle ✅ nominal
max_tokens tronqué (HTTP 200 !) alerter / relancer
stop_sequence séquence rencontrée lire response.stop_sequence
tool_use outil demandé exécuter → tool_result → boucler

Notes : LE slide certification. Un architecte écrit une branche par valeur. max_tokens n'est PAS une erreur HTTP — état métier à détecter soi-même.

Slide 10 — Le garde-fou canonique

match response.stop_reason:
    case "end_turn":
        return extract_text(response)
    case "max_tokens":
        raise TruncatedResponseError(partial=...)
    case "stop_sequence":
        return parse_delimited(..., response.stop_sequence)
    case "tool_use":
        return handle_tool_loop(response)
    case _:
        raise UnexpectedStopReason(...)

Notes : Le case _ protège contre les futures valeurs de l'API. À faire écrire en exercice 1.

Slide 11 — La fenêtre de contexte = mémoire de travail

  • ⚠ ~200 000 tokens (certains modèles : 1M en bêta)
  • Contient TOUT : system + historique + docs + outils + résultats
  • Ce n'est pas « une limite » — c'est ce que le modèle sait pour cette requête

3 conséquences : coût linéaire · lost in the middle · latence (TTFT)

Notes : TTFT = Time To First Token. Changement de mental model : de « contrainte » à « ressource à architecturer ».

Slide 12 — Coût : la conversation qui devient quadratique

API sans état → l'historique complet est refacturé à chaque tour

# 20 tours, 30k tokens d'historique moyen, 500 tokens de sortie
total = sum(cout(30_000, 500) for _ in range(20))
# ≈ 1,95 $ pour UNE conversation ⚠

× 10 000 utilisateurs/jour → sujet de direction technique

Parades : résumé · troncature glissante · prompt caching

Notes : Somme des préfixes = croissance quadratique. Transition naturelle vers le caching.

Slide 13 — « Lost in the middle »

Rappel de l'information selon sa position dans le contexte :

DÉBUT  ████████████  excellent
MILIEU ██████        dégradé   ← ne rien enterrer ici
FIN    ███████████   excellent

Placement stratégique :

  • Documents volumineux → en tête
  • Instructions critiques + question → en fin
  • Consigne clé au milieu de 80k tokens de logs → ❌

Notes : Question au groupe : « RAG avec 40 chunks — où va la question utilisateur ? » Réponse : après les docs, éventuellement répétée.

Slide 14 — Prompt caching : le principe

Réutiliser un préfixe déjà traité via cache_control

⚠ Économie (volatile) :

  • Lecture (hit) : ~10 % du tarif → −90 %
  • Écriture : surcoût ~25 %
  • TTL ~5 min ⚠, rafraîchi à chaque hit (option 1 h ⚠)

Rentable dès 2 requêtes : 1,25 + 0,10 = 1,35 < 2,00

Notes : TTL = Time To Live. Faire faire le calcul de rentabilité au groupe avant de donner la réponse.

Slide 15 — cache_control : syntaxe

system=[{
    "type": "text",
    "text": GROS_CONTEXTE,          # 50k tokens stables
    "cache_control": {"type": "ephemeral"}   # ← breakpoint
}]
u = response.usage
u.cache_creation_input_tokens   # écrits (×1,25 ⚠)
u.cache_read_input_tokens       # lus (×0,10 ⚠)
u.input_tokens                  # non cachés, plein tarif

Notes : Trois compteurs distincts dans usage — les montrer, c'est ce qui permet de vérifier que le cache fonctionne réellement en prod.

Slide 16 — Règles d'invalidation du cache

  • Préfixe exact : un octet modifié en amont → invalidé
  • Ordre : toolssystemmessages
  • Minimum cachable : ⚠ 1 024 tokens (sinon ignoré silencieusement)
  • Max 4 breakpoints ⚠ par requête

Design cible : stable en tête, variable en queue

Notes : Piège Q7 du quiz : modifier le dernier message user N'invalide PAS le cache (il est après le breakpoint).

Slide 17 — Anti-pattern : le cache qui coûte plus cher

system = f"[{datetime.now()}] Tu es un assistant..."  # ❌
  • Horodatage en tête → préfixe jamais identique
  • 0 % de hit + surcoût d'écriture 25 % à chaque appel
  • Résultat : on paie plus cher que sans cache

Notes : Cas réel fréquent. Idem avec un ID de session, un nonce, un compteur. Tout ce qui varie va APRÈS le breakpoint.

Slide 18 — Compter avant de payer : count_tokens

count = client.messages.count_tokens(
    model="claude-sonnet-4-5",   # ⚠ tokenizer par modèle
    system="...",
    messages=[{"role": "user", "content": doc}],
)
print(count.input_tokens)   # sans génération
  • Chaque famille de modèles a son tokenizer
  • ❌ Ne jamais réutiliser un comptage tiktoken (OpenAI) pour Claude
  • Ordres de grandeur : ~3,5–4 car./token (anglais), un peu plus dense en français

Notes : Usages : valider la fenêtre AVANT de payer, dimensionner max_tokens, chargeback interne. Passer la requête COMPLÈTE (system+messages+tools).

Slide 19 — Streaming : pourquoi

  • UX : premier token en ~1 s au lieu d'attendre 30 s
  • Longues générations : indispensable (timeouts HTTP)
  • SSE = Server-Sent Events : flux HTTP unidirectionnel text/event-stream
event: content_block_delta
data: {"delta":{"type":"text_delta","text":"Le"}}

Notes : UX = User eXperience. SSE ≠ WebSocket : unidirectionnel, simple HTTP, compatible proxys (sauf buffering — voir exercice 2C).

Slide 20 — Le cycle d'événements SSE

message_start          ← id, model, usage d'ENTRÉE
content_block_start    ← ouverture bloc 0
content_block_delta ×n ← les morceaux de texte
content_block_stop     ← fermeture bloc 0
message_delta          ← stop_reason + output_tokens ★
message_stop           ← fin du flux

(+ ping keep-alive, + error possible en cours de flux)

Piège certif : les métadonnées finales sont dans message_delta

Notes : Démo : visualiseur de la page web de session (offline). Lancer la version « troncature » pour montrer stop_reason=max_tokens en streaming.

Slide 21 — Streaming : implémentation

with client.messages.stream(
    model="claude-sonnet-4-5", max_tokens=1024,   # ⚠
    messages=[...],
) as stream:
    for text in stream.text_stream:      # helper haut niveau
        print(text, end="", flush=True)
    final = stream.get_final_message()
    print(final.stop_reason)             # toujours vérifier !

Version bas niveau : stream=True + boucle sur event.type
→ exigée en certification (exercice 2)

Notes : Même en streaming, la troncature max_tokens existe. Un front qui affiche joliment un texte tronqué reste un bug.

Slide 22 — Batches API : le 3e mode d'exécution

Mode Latence Coût Usage
Synchrone secondes 100 % interactif
Streaming TTFT ~1 s 100 % interactif long
Batch SLA 24 h ⚠ −50 % ⚠ masse, non urgent
  • Jusqu'à 100 000 requêtes / ~256 Mo ⚠ par batch
  • En pratique : souvent < 1 h
  • Résultats non ordonnés → corrélation par custom_id obligatoire

Notes : SLA = Service Level Agreement. Cas d'usage à faire trouver : classification nocturne, enrichissement CRM, éval massive de prompts.

Slide 23 — Batch : code

batch = client.messages.batches.create(requests=[
    {"custom_id": f"doc-{i}",            # clé de corrélation
     "params": {"model": "claude-haiku-4-5",   # ⚠ petit modèle
                "max_tokens": 512,
                "messages": [...]}}
    for i, doc in enumerate(documents)
])

for r in client.messages.batches.results(batch.id):
    if r.result.type == "succeeded": traiter(r.custom_id, ...)
    else: rejouer(r.custom_id, r.result)  # errored/expired/canceled

Notes : États finaux : succeeded / errored / expired / canceled — une politique de rejeu pour chacun. Bonus : batch + caching se CUMULENT (hits non garantis).

Slide 24 — Gestion d'erreurs : taxonomie

Code Cause Retry ?
400 requête malformée ❌ corriger
401 / 403 clé invalide / sans accès ❌
413 requête trop grosse ❌ réduire
429 rate limit (RPM/ITPM/OTPM) ✅ backoff
500 / 529 interne / surcharge ✅ backoff

RPM = Requests Per Minute · ITPM/OTPM = Input/Output Tokens Per Minute

Notes : Trois compteurs de rate limit indépendants — on peut être limité en tokens de sortie tout en étant sous la limite de requêtes.

Slide 25 — Backoff exponentiel avec jitter

except anthropic.RateLimitError as e:
    retry_after = e.response.headers.get("retry-after")
    if retry_after:
        delai = float(retry_after)        # le serveur PRIME
    else:
        delai = min(60, 2**tentative) * random.uniform(0.5, 1.5)
    time.sleep(delai)
  1. retry-after prime sur votre formule
  2. Jitter obligatoire (effet « thundering herd »)
  3. Le SDK retry déjà 2× par défaut → attention aux couches multiplicatives

Notes : Bonus : en-têtes anthropic-ratelimit-*-remaining pour throttling PROACTIF. Rappeler la question d'ouverture du cours (qui a vécu un 429 ?).

Slide 26 — Arbre de décision de l'architecte

Besoin temps réel ?
├── OUI → réponse longue ? → streaming SSE
│         réponse courte ? → synchrone
└── NON → volume massif ? → Batches API (−50 % ⚠)
                            + petit modèle
                            + prompt caching si préfixe commun

Toujours : garde stop_reason + backoff 429 + log de usage

Notes : Synthèse opérationnelle. Les trois réflexes du bas s'appliquent aux TROIS modes.

Slide 27 — Les 7 pièges de certification vus aujourd'hui

  1. max_tokens obligatoire, sans valeur par défaut
  2. Troncature = HTTP 200 + stop_reason: "max_tokens"
  3. Le prefill n'est pas inclus dans la réponse
  4. temperature: 0 ≠ déterminisme parfait
  5. Cache : préfixe exact, tools → system → messages
  6. stop_reason final en streaming → message_delta
  7. Résultats batch non ordonnés → custom_id

Notes : Faire relire cette liste à voix haute par un participant. C'est la check-list de révision.

Slide 28 — Prochaines étapes

  • Exercices : client robuste · streaming bas niveau · pipeline batch 80k emails
  • Quiz : 10 QCM certification (seuil 7/10)
  • Page interactive : constructeur de requête + visualiseur SSE + compteur de tokens (offline)
  • Session 2 : tool use & agents — la boucle tool_usetool_result en profondeur

⚠ Réflexe permanent : revalider tarifs, limites et noms de modèles sur docs.anthropic.com

Notes : Distribuer les exit tickets (5 questions, 3 min). Ramasser à la sortie — ils calibrent l'ouverture de la session 2.