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

Guide du formateur — Niveau AvancĂ©, Session 1

« Claude API : plongée en profondeur »

Programme : Applied AI — Yann Isola Public : Architectes solutions prĂ©parant la certification Claude Certified Architect DurĂ©e : 2 h 00 (+ 10 min de pause recommandĂ©e Ă  mi-parcours) PrĂ©requis : Python intermĂ©diaire, notions HTTP/REST (REST = Representational State Transfer), avoir dĂ©jĂ  appelĂ© une API (API = Application Programming Interface, interface de programmation applicative) quelconque. MatĂ©riel : clĂ© API Anthropic de dĂ©monstration, terminal avec curl et Python 3.10+, SDK (SDK = Software Development Kit, kit de dĂ©veloppement logiciel) anthropic installĂ©, page web interactive de la session (webpage/index.html).


Objectifs pédagogiques

À la fin de la session, chaque participant sait :

  1. Construire une requĂȘte API Claude complĂšte et justifier chaque paramĂštre (model, max_tokens, messages, system, temperature, stop_sequences).
  2. Interpréter chaque valeur de stop_reason et concevoir la logique applicative correspondante.
  3. Raisonner sur la fenĂȘtre de contexte comme une mĂ©moire de travail : coĂ»t linĂ©aire, effet « lost in the middle », stratĂ©gies de placement.
  4. Mettre en Ɠuvre le prompt caching avec cache_control et calculer le retour sur investissement (ROI = Return On Investment).
  5. ImplĂ©menter le streaming SSE (SSE = Server-Sent Events, Ă©vĂ©nements envoyĂ©s par le serveur) et nommer les Ă©vĂ©nements du cycle de vie d’un message.
  6. Choisir entre appel synchrone, streaming et Batches API selon le cas d’usage, avec les arguments coĂ»t/latence/SLA (SLA = Service Level Agreement, engagement de niveau de service).
  7. Concevoir une stratĂ©gie de gestion d’erreurs robuste : 429, backoff exponentiel, en-tĂȘtes retry-after.

Plan minuté

Bloc Durée Contenu
0. Ouverture 5 min Cadrage certification, tour de table express
1. Anatomie d’une requĂȘte 20 min model, max_tokens, messages, system — dĂ©mo curl + Python
2. RÎles & prefilling 15 min user/assistant/system, prefilling de réponse
3. stop_reason 10 min Les 4 valeurs, logique applicative
4. FenĂȘtre de contexte 15 min MĂ©moire de travail, coĂ»t linĂ©aire, lost in the middle
Pause 10 min
5. Prompt caching 20 min cache_control, économie 90 %, surcoût 25 %, calculs ROI
6. Comptage de tokens 10 min Endpoint count_tokens, tokenizers par famille
7. Streaming SSE 15 min Cycle d’évĂ©nements, dĂ©mo live
8. Batches API 10 min Async, −50 %, SLA 24 h, 100k requĂȘtes
9. Gestion d’erreurs 10 min 429, backoff exponentiel, retry-after
10. ClĂŽture 5 min Exit tickets, annonce des exercices

Bloc 0 — Ouverture (5 min)

Message d’accroche : « Aujourd’hui on ne parle pas de prompting. On parle de ce qui distingue un dĂ©veloppeur qui appelle Claude d’un architecte qui conçoit un systĂšme autour de Claude : le contrat d’API, l’économie des tokens, et les modes de dĂ©faillance. »

Question flash au groupe : « Qui a dĂ©jĂ  reçu une erreur 429 en production ? Qu’avez-vous fait ? » — collecter 2-3 rĂ©ponses, y revenir au bloc 9.


Bloc 1 — Anatomie d’une requĂȘte API (20 min)

1.1 Le contrat minimal

Trois champs sont obligatoires : model, max_tokens, messages.

Démo curl (à taper en live) :

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": "Explique le protocole FIX en une phrase."}
    ]
  }'

⚠ Le nom de modĂšle (claude-sonnet-4-5) est volatil : vĂ©rifier la documentation officielle avant tout support de cours ou dĂ©ploiement.

Points Ă  marteler :

1.2 Équivalent Python (SDK officiel)

import anthropic

client = anthropic.Anthropic()  # lit ANTHROPIC_API_KEY dans l'environnement

response = client.messages.create(
    model="claude-sonnet-4-5",          # ⚠ volatil
    max_tokens=1024,
    temperature=0.2,                     # déterminisme relatif pour tùches techniques
    stop_sequences=["FIN_RAPPORT"],      # arrĂȘt personnalisĂ©
    system="Tu es un analyste financier. Réponds en français, style concis.",
    messages=[
        {"role": "user", "content": "Résume les risques d'un stablecoin adossé à des matiÚres premiÚres."}
    ],
)

print(response.content[0].text)
print(response.usage)        # input_tokens / output_tokens → pilotage des coĂ»ts
print(response.stop_reason)  # toujours l'inspecter en production

Décryptage de la réponse :

{
  "id": "msg_01XFDUDYJgAACzvnptvVoYEL",
  "type": "message",
  "role": "assistant",
  "content": [{"type": "text", "text": "..."}],
  "model": "claude-sonnet-4-5",
  "stop_reason": "end_turn",
  "usage": {"input_tokens": 58, "output_tokens": 212}
}

1.3 Les paramĂštres d’échantillonnage

ParamĂštre RĂŽle Recommandation architecte
temperature (0–1) AlĂ©a de l’échantillonnage 0–0.3 pour extraction/classification, 0.7–1 pour crĂ©ativitĂ©
top_p Nucleus sampling (Ă©chantillonnage par noyau de probabilitĂ©) Ne pas combiner avec temperature — choisir l’un des deux
top_k Restreint aux k tokens les plus probables Rarement nécessaire, cas avancés
stop_sequences ChaĂźnes d’arrĂȘt personnalisĂ©es Utile pour dĂ©limiter des sorties structurĂ©es

PiÚge certification : temperature: 0 ne garantit pas un déterminisme parfait (non-déterminisme numérique résiduel possible). Formulation correcte : « réduit fortement la variabilité ».


Bloc 2 — Rîles de messages & prefilling (15 min)

2.1 Trois canaux, trois niveaux de privilĂšge

  1. system — canal privilĂ©giĂ©. Ce n’est PAS « un premier message user dĂ©guisĂ© ». Il est traitĂ© avec une prioritĂ© particuliĂšre par le modĂšle. Usages : persona, rĂšgles de sĂ©curitĂ©, politique de sortie (format, langue, refus), contexte mĂ©tier stable.
  2. user — le tour de l’appelant. Contenu : question, documents, rĂ©sultats d’outils (tool_result).
  3. assistant — les tours du modùle. Mais aussi : le prefilling.

RĂšgle structurelle : le tableau messages doit alterner user/assistant et commencer par user. Deux messages consĂ©cutifs de mĂȘme rĂŽle → erreur 400.

2.2 Prefilling : mettre des mots dans la bouche du modĂšle

Le dernier message peut ĂȘtre un assistant partiel : le modĂšle continue Ă  partir de lĂ .

response = client.messages.create(
    model="claude-sonnet-4-5",  # ⚠
    max_tokens=500,
    messages=[
        {"role": "user", "content": "Liste 3 risques de contrepartie en JSON."},
        {"role": "assistant", "content": "{"}   # prefill : force l'ouverture JSON
    ],
)
# La rĂ©ponse commence directement aprĂšs "{" → pas de prĂ©ambule "Voici le JSON :"

Cas d’usage architecte :

Attention : le texte du prefill ne fait pas partie de la rĂ©ponse retournĂ©e — pensez Ă  le re-prĂ©fixer cĂŽtĂ© client ("{" + response.content[0].text).

DĂ©mo live suggĂ©rĂ©e : mĂȘme question avec et sans prefill, comparer les sorties. Effet immĂ©diat, trĂšs parlant.


Bloc 3 — stop_reason : les 4 signaux (10 min)

Chaque rĂ©ponse indique pourquoi le modĂšle s’est arrĂȘtĂ©. Un architecte Ă©crit une branche de code pour chacun.

stop_reason Signification Réaction applicative
end_turn Fin naturelle du tour Cas nominal — traiter la rĂ©ponse
max_tokens Plafond atteint → rĂ©ponse tronquĂ©e Alerter/relancer avec plafond plus haut, ou continuer la gĂ©nĂ©ration
stop_sequence Une stop_sequences a été rencontrée Lire response.stop_sequence pour savoir laquelle ; parser la sortie délimitée
tool_use Le modĂšle demande l’exĂ©cution d’un outil ExĂ©cuter l’outil, renvoyer un tool_result, reboucler

Code de garde canonique (à faire écrire aux participants) :

match response.stop_reason:
    case "end_turn":
        return extract_text(response)
    case "max_tokens":
        logger.warning("RĂ©ponse tronquĂ©e — usage=%s", response.usage)
        raise TruncatedResponseError(partial=extract_text(response))
    case "stop_sequence":
        return parse_delimited(extract_text(response), response.stop_sequence)
    case "tool_use":
        return handle_tool_loop(response)
    case _:
        raise UnexpectedStopReason(response.stop_reason)

PiĂšge certification : max_tokens n’est pas une erreur HTTP — la requĂȘte retourne 200. C’est un Ă©tat mĂ©tier Ă  dĂ©tecter soi-mĂȘme. Beaucoup de systĂšmes en production livrent silencieusement des JSON tronquĂ©s parce que personne ne teste stop_reason.


Bloc 4 — La fenĂȘtre de contexte comme mĂ©moire de travail (15 min)

4.1 Changer de mental model

La fenĂȘtre de contexte (⚠ 200 000 tokens sur la plupart des modĂšles Claude actuels, certains modĂšles proposent 1M en bĂȘta) n’est pas « une limite Ă  ne pas dĂ©passer ». C’est la mĂ©moire de travail du modĂšle : tout ce qu’il « sait » pour cette requĂȘte s’y trouve — system prompt, historique, documents, dĂ©finitions d’outils, rĂ©sultats d’outils.

Trois consĂ©quences d’architecture :

  1. CoĂ»t linĂ©aire. Chaque token d’entrĂ©e est facturĂ© Ă  chaque appel. Une conversation qui accumule 150k tokens d’historique coĂ»te 150k tokens d’entrĂ©e par tour. Sans stratĂ©gie (rĂ©sumĂ©, troncature, caching), le coĂ»t d’une conversation croĂźt quadratiquement avec sa longueur (somme des longueurs de prĂ©fixes).
  2. Effet « lost in the middle ». Les modĂšles rappellent mieux l’information placĂ©e en dĂ©but et en fin de contexte qu’au milieu. Placement stratĂ©gique : instructions critiques et question au plus prĂšs de la fin ; documents volumineux en tĂȘte ; ne jamais enterrer une consigne clĂ© au milieu de 80k tokens de logs.
  3. Latence. Le temps jusqu’au premier token (TTFT = Time To First Token) croĂźt avec la taille de l’entrĂ©e.

4.2 Exercice mental (2 min, à l’oral)

« Un assistant RAG (RAG = Retrieval-Augmented Generation, gĂ©nĂ©ration augmentĂ©e par rĂ©cupĂ©ration) injecte 40 chunks de 1 000 tokens. OĂč placez-vous la question de l’utilisateur ? » → RĂ©ponse attendue : aprĂšs les documents, en fin de prompt, Ă©ventuellement rĂ©pĂ©tĂ©e si les documents sont trĂšs longs.

4.3 Calcul de coût en direct

# Ordre de grandeur — tarifs ⚠ volatils, vĂ©rifier la grille officielle
PRIX_INPUT_PAR_MTOK = 3.00    # $ / million de tokens d'entrĂ©e ⚠
PRIX_OUTPUT_PAR_MTOK = 15.00  # $ / million de tokens de sortie ⚠

def cout_appel(input_tokens: int, output_tokens: int) -> float:
    return (input_tokens * PRIX_INPUT_PAR_MTOK
            + output_tokens * PRIX_OUTPUT_PAR_MTOK) / 1_000_000

# Conversation de 20 tours, historique moyen 30k tokens, réponses 500 tokens
total = sum(cout_appel(30_000, 500) for _ in range(20))
print(f"{total:.2f} $")   # ≈ 1.95 $ pour UNE conversation

Multiplier par 10 000 utilisateurs/jour → l’économie du contexte devient un sujet de direction technique, pas un dĂ©tail.


Bloc 5 — Prompt caching (20 min)

5.1 Le principe

Le prompt caching permet de rĂ©utiliser le prĂ©fixe d’un prompt dĂ©jĂ  traitĂ©. On pose des points de rupture cache_control ; tout ce qui prĂ©cĂšde (et correspond exactement) est servi depuis le cache.

Économie : ⚠

5.2 Syntaxe

response = client.messages.create(
    model="claude-sonnet-4-5",  # ⚠
    max_tokens=1024,
    system=[
        {
            "type": "text",
            "text": GROS_CONTEXTE_METIER,   # ex. 50k tokens de documentation
            "cache_control": {"type": "ephemeral"}   # ← point de rupture
        }
    ],
    messages=[{"role": "user", "content": question_utilisateur}],
)

u = response.usage
print(u.cache_creation_input_tokens)  # tokens Ă©crits au cache (surcoĂ»t 25 % ⚠)
print(u.cache_read_input_tokens)      # tokens lus du cache (−90 % ⚠)
print(u.input_tokens)                 # tokens non cachés, plein tarif

RĂšgles Ă  connaĂźtre (certification) :

5.3 Calcul de rentabilité (à faire faire)

« System prompt + docs = 60k tokens, question = 300 tokens, 500 requĂȘtes/heure. »

Anti-pattern Ă  citer : placer un horodatage ou un identifiant de session au dĂ©but du system prompt → 0 % de cache hit, surcoĂ»t de 25 % Ă  chaque appel. On paie plus cher qu’en dĂ©sactivant le cache.


Bloc 6 — Comptage de tokens (10 min)

6.1 Pourquoi compter avant d’envoyer

6.2 L’endpoint dĂ©diĂ©

count = client.messages.count_tokens(
    model="claude-sonnet-4-5",   # ⚠ le comptage dĂ©pend du modĂšle
    system="Tu es un assistant juridique.",
    messages=[{"role": "user", "content": contrat_complet}],
)
print(count.input_tokens)   # gratuit, pas de gĂ©nĂ©ration ⚠ (soumis Ă  rate limit dĂ©diĂ©)

Points clés :


Bloc 7 — Streaming SSE (15 min)

7.1 Pourquoi streamer

7.2 Le cycle d’évĂ©nements SSE

SSE = Server-Sent Events : flux HTTP unidirectionnel text/event-stream, événements event: + data: séparés par des lignes vides.

Séquence pour un message simple :

message_start          → enveloppe du message (id, model, usage d'entrĂ©e)
content_block_start    → ouverture du bloc n°0 (type: text)
content_block_delta    → {"delta": {"type": "text_delta", "text": "Le"}}
content_block_delta    → {"delta": {"type": "text_delta", "text": " protocole"}}
...                       (des dizaines/centaines de deltas)
content_block_stop     → fermeture du bloc n°0
message_delta          → stop_reason + usage de sortie finaux
message_stop           → fin du flux

(+ événements ping de keep-alive à ignorer, + error possible en cours de flux.)

PiÚge certification : stop_reason et le compte final de tokens de sortie arrivent dans message_delta, pas dans message_start. Un client qui ne lit pas message_delta ne saura jamais si la réponse a été tronquée.

7.3 Implémentation

# Version haut niveau (SDK)
with client.messages.stream(
    model="claude-sonnet-4-5",  # ⚠
    max_tokens=1024,
    messages=[{"role": "user", "content": "Explique le netting bilatéral."}],
) as stream:
    for text in stream.text_stream:
        print(text, end="", flush=True)
    final = stream.get_final_message()
    print("\n→", final.stop_reason, final.usage)
# Version bas niveau (Ă©vĂ©nements bruts) — celle qu'il faut connaĂźtre pour la certif
stream = client.messages.create(..., stream=True)
for event in stream:
    match event.type:
        case "message_start":
            msg_id = event.message.id
        case "content_block_delta":
            if event.delta.type == "text_delta":
                buffer += event.delta.text
        case "message_delta":
            stop_reason = event.delta.stop_reason
            output_tokens = event.usage.output_tokens
        case "message_stop":
            break

DĂ©mo : ouvrir le visualiseur de streaming de la page web de session (webpage/index.html) — chaque Ă©vĂ©nement s’affiche avec son type colorĂ©. TrĂšs efficace pour ancrer la sĂ©quence.


Bloc 8 — Batches API (10 min)

8.1 Le troisiĂšme mode d’exĂ©cution

Mode Latence CoĂ»t Cas d’usage
Synchrone secondes plein tarif interactif
Streaming premier token en ~1 s plein tarif interactif, longues sorties
Batch jusqu’à 24 h (SLA) ⚠ −50 % ⚠ traitement de masse, non urgent

8.2 Code

batch = client.messages.batches.create(
    requests=[
        {
            "custom_id": f"doc-{i}",                      # clĂ© de corrĂ©lation — OBLIGATOIRE
            "params": {
                "model": "claude-haiku-4-5",              # ⚠ petit modùle pour la masse
                "max_tokens": 512,
                "messages": [{"role": "user", "content": f"Classifie : {doc}"}],
            },
        }
        for i, doc in enumerate(documents)
    ]
)

# Polling de l'état
status = client.messages.batches.retrieve(batch.id)
print(status.processing_status)        # in_progress → ended
print(status.request_counts)           # succeeded / errored / canceled / expired

# RĂ©cupĂ©ration (JSONL — JSON Lines, un objet JSON par ligne)
for result in client.messages.batches.results(batch.id):
    if result.result.type == "succeeded":
        traiter(result.custom_id, result.result.message)
    else:
        rejouer(result.custom_id, result.result)   # errored / expired / canceled

Cas d’usage Ă  faire trouver au groupe : classification nocturne de tickets, enrichissement de CRM (CRM = Customer Relationship Management), Ă©valuation massive de prompts, gĂ©nĂ©ration de mĂ©tadonnĂ©es documentaires, backtesting de prompts sur historiques.

PiĂšge certification : batch + prompt caching se combinent — les requĂȘtes partageant un long prĂ©fixe dans le mĂȘme batch peuvent bĂ©nĂ©ficier du cache, cumulant les remises (les hits de cache ne sont pas garantis en batch).


Bloc 9 — Gestion d’erreurs (10 min)

9.1 Taxonomie

Code Nom Cause Retry ?
400 invalid_request_error RequĂȘte malformĂ©e (rĂŽles, JSON
) ❌ corriger le code
401 authentication_error ClĂ© invalide ❌
403 permission_error ClĂ© sans accĂšs Ă  la ressource ❌
404 not_found_error Modùle/ressource inexistant ❌
413 request_too_large RequĂȘte trop grosse ❌ rĂ©duire
429 rate_limit_error RPM/ITPM/OTPM dépassés backoff
500 api_error Erreur interne backoff
529 overloaded_error Surcharge du service backoff

(RPM = Requests Per Minute ; ITPM/OTPM = Input/Output Tokens Per Minute — les trois compteurs de rate limit.)

9.2 Backoff exponentiel avec jitter

import random, time
import anthropic

def appel_robuste(client, max_retries=5, **kwargs):
    for tentative in range(max_retries):
        try:
            return client.messages.create(**kwargs)
        except anthropic.RateLimitError as e:
            # Priorité au serveur : respecter retry-after s'il est fourni
            retry_after = e.response.headers.get("retry-after")
            if retry_after is not None:
                delai = float(retry_after)
            else:
                delai = min(60, (2 ** tentative)) * random.uniform(0.5, 1.5)  # jitter
            time.sleep(delai)
        except anthropic.APIStatusError as e:
            if e.status_code in (500, 529):
                time.sleep(min(60, 2 ** tentative) * random.uniform(0.5, 1.5))
            else:
                raise    # 4xx ≠ 429 : inutile de rĂ©essayer
    raise ExhaustedRetriesError()

Trois points d’architecte :

  1. retry-after prime sur votre formule — le serveur sait mieux que vous quand rĂ©essayer.
  2. Jitter obligatoire — sans alĂ©a, tous vos workers rĂ©essaient au mĂȘme instant (« thundering herd », effet troupeau).
  3. Le SDK officiel fait dĂ©jĂ  2 retries par dĂ©faut — connaĂźtre ce comportement avant d’empiler votre propre couche (risque de retries multiplicatifs).

Mentionner aussi : surveiller les en-tĂȘtes anthropic-ratelimit-*-remaining pour du throttling proactif plutĂŽt que rĂ©actif.


Bloc 10 — Clîture (5 min)


Exit tickets (5)

À remplir en 3 minutes, ramassĂ©s Ă  la sortie :

  1. stop_reason : votre application reçoit stop_reason: "max_tokens" sur une extraction JSON. Que s’est-il passĂ© et que fait votre code ? (2 phrases)
  2. Caching : pourquoi placer un horodatage au début du system prompt ruine-t-il le prompt caching, et combien cela coûte-t-il en plus (%) ?
  3. Streaming : dans quel événement SSE trouve-t-on le stop_reason final ? (nom exact)
  4. Batch : citez deux conditions qui rendent la Batches API préférable à des appels synchrones, et la remise associée.
  5. Erreurs : on reçoit un 429 avec en-tĂȘte retry-after: 12. Quel dĂ©lai appliquer et pourquoi ne pas utiliser sa propre formule de backoff dans ce cas ?

CorrigĂ© express : 1) plafond max_tokens atteint, JSON tronquĂ© → dĂ©tecter et relancer avec plafond supĂ©rieur ou continuation. 2) Le cache exige un prĂ©fixe exact ; l’horodatage change Ă  chaque appel → 0 hit, mais on paie le surcoĂ»t d’écriture ~25 % ⚠. 3) message_delta. 4) Volume massif + pas d’exigence de latence (SLA 24 h ⚠) → −50 % ⚠. 5) 12 s : l’en-tĂȘte serveur prime sur toute heuristique client.


Annexes formateur