Exercices — Niveau Avancé, Session 1
« Claude API : plongée en profondeur »
Programme : Applied AI — Yann Isola
Public : Architectes solutions (préparation Claude Certified Architect)
Durée totale estimée : 2 h 30 – 3 h (hors classe ou en atelier encadré)
Prérequis : Python 3.10+, SDK (SDK = Software Development Kit) anthropic installé, clé API (API = Application Programming Interface) de test.
⚠ Tous les noms de modèles, tarifs et limites cités sont volatils : vérifiez la documentation officielle (docs.anthropic.com) avant de coder en dur quoi que ce soit.
Exercice 1 — Construction de requête API : le client robuste (60 min)
Contexte
Vous êtes architecte chez un courtier en instruments tokenisés. L’équipe produit veut un module Python claude_client.py réutilisable par tous les services internes. Votre mission : concevoir la fonction d’appel de référence, avec tous les paramètres maîtrisés et tous les stop_reason gérés.
Consignes
Partie A — La requête complète (20 min)
Écrivez une fonction appeler_claude() qui :
- Accepte :
question: str,persona: str,format_json: bool = False,max_tokens: int = 1024. - Construit la requête avec :
- un system prompt (canal privilégié : persona + règles de sortie — jamais dans un message
user) ; temperature=0.2(tâche technique) ;- si
format_json=True: un prefillingassistantavec"{"pour forcer l’ouverture JSON (JSON = JavaScript Object Notation), et unestop_sequences=["```"]de sécurité.
- un system prompt (canal privilégié : persona + règles de sortie — jamais dans un message
- Retourne un objet structuré
ReponseClaude(texte, stop_reason, input_tokens, output_tokens, cout_estime).
Squelette de départ :
from dataclasses import dataclass
import anthropic
PRIX_INPUT_PAR_MTOK = 3.00 # $ / Mtok ⚠ volatil
PRIX_OUTPUT_PAR_MTOK = 15.00 # $ / Mtok ⚠ volatil
@dataclass
class ReponseClaude:
texte: str
stop_reason: str
input_tokens: int
output_tokens: int
cout_estime: float
def appeler_claude(question: str, persona: str,
format_json: bool = False,
max_tokens: int = 1024) -> ReponseClaude:
client = anthropic.Anthropic()
# ... à compléter ...
Partie B — La garde stop_reason (20 min)
Complétez la fonction pour traiter chacun des quatre stop_reason :
end_turn→ retour nominal ;max_tokens→ leverReponseTronqueeErroren incluant le texte partiel ET le compte de tokens (le client appelant décidera de relancer) ;stop_sequence→ retour nominal + log de la séquence rencontrée (response.stop_sequence) ;tool_use→ leverNotImplementedError("tool loop hors périmètre session 1").
N’oubliez pas : si format_json=True, re-préfixez le "{" du prefill (il n’est pas inclus dans la réponse).
Partie C — Le pré-comptage (20 min)
Avant l’appel réel, utilisez l’endpoint client.messages.count_tokens(...) pour :
- Compter les tokens d’entrée de la requête complète (system + messages).
- Si
input_tokens + max_tokens > 200_000⚠ (fenêtre de contexte), leverContexteDebordeErrorsans consommer d’appel de génération. - Logger l’estimation de coût avant l’appel.
Livrables
claude_client.pycomplet et exécutable.- Un bloc de tests manuels (
if __name__ == "__main__":) démontrant : un appel nominal, un appel JSON avec prefill, une troncature provoquée (max_tokens=20).
Critères d’évaluation
| Critère | Points |
|---|---|
| Requête complète et paramètres justifiés (commentaires) | /6 |
Les 4 stop_reason traités correctement |
/6 |
| Prefill JSON re-préfixé côté client | /3 |
| Pré-comptage + garde de fenêtre de contexte | /3 |
| Qualité du code (typage, dataclass, logs) | /2 |
| Total | /20 |
Piège à éviter (indice)
stop_reason: "max_tokens" arrive avec un HTTP 200. Si votre gestion d’erreur ne regarde que les exceptions HTTP, vous livrerez des JSON tronqués en production.
Exercice 2 — Implémentation du streaming SSE (50 min)
Contexte
Le front-end de votre plateforme affiche les réponses de Claude en temps réel. Vous devez implémenter le consommateur de flux côté serveur, en traitant les événements bruts SSE (SSE = Server-Sent Events) — pas seulement le helper haut niveau — car la certification l’exige et votre équipe front a besoin des métadonnées fines.
Consignes
Partie A — Le consommateur d’événements (25 min)
Implémentez streamer_reponse() qui consomme le flux bas niveau et maintient un état complet :
def streamer_reponse(client, question: str) -> dict:
"""Consomme le flux SSE et retourne l'état final :
{texte, stop_reason, output_tokens, evenements_recus (liste des types), ttft_ms}
"""
import time
etat = {"texte": "", "stop_reason": None, "output_tokens": None,
"evenements_recus": [], "ttft_ms": None}
debut = time.monotonic()
with client.messages.stream(
model="claude-sonnet-4-5", # ⚠
max_tokens=800,
messages=[{"role": "user", "content": question}],
) as stream:
for event in stream:
etat["evenements_recus"].append(event.type)
# ... à compléter : traiter chaque type d'événement ...
return etat
Exigences :
message_start→ capturer l’iddu message.content_block_delta(sous-typetext_delta) → accumuler le texte ; au premier delta, enregistrer le TTFT (TTFT = Time To First Token) en millisecondes.message_delta→ capturerstop_reasonetoutput_tokens(rappel : ils n’arrivent QUE dans cet événement).message_stop→ clore proprement.- Ignorer les
pingsans planter ; sur un événementerror, lever une exception avec le détail.
Partie B — L’assertion de séquence (15 min)
Écrivez verifier_sequence(evenements: list[str]) -> bool qui valide l’ordre canonique :
message_start
→ (content_block_start → content_block_delta* → content_block_stop)+
→ message_delta
→ message_stop
Testez-la sur la liste evenements_recus de la partie A. Cette fonction sert de test d’intégration : si Anthropic change le protocole ou si votre parseur perd des événements, elle échoue bruyamment.
Partie C — Question d’architecture (10 min, rédigé)
En 10 lignes max : votre front-end est derrière un proxy inverse (nginx) qui bufferise les réponses HTTP. Quel est l’impact sur votre streaming, quel symptôme observera l’utilisateur, et quelles directives de configuration corrigent le problème ? (Indice : proxy_buffering, X-Accel-Buffering, text/event-stream.)
Critères d’évaluation
| Critère | Points |
|---|---|
| Tous les types d’événements traités (y compris ping/error) | /7 |
| TTFT mesuré au bon endroit (premier text_delta) | /3 |
| stop_reason + output_tokens extraits de message_delta | /4 |
| Validateur de séquence correct | /4 |
| Question proxy : buffering identifié + correctifs | /2 |
| Total | /20 |
Piège à éviter (indice)
Chercher stop_reason dans message_start ou message_stop = 0 point sur le critère 3. Relisez la séquence.
Exercice 3 — Conception d’un traitement par lots (Batches API) (60 min)
Contexte
Votre société doit classifier 80 000 courriels clients archivés (conformité) : catégorie, sentiment, présence de réclamation réglementaire. Pas d’exigence de latence — le rapport est mensuel. Budget serré. C’est un cas d’école pour la Batches API : traitement asynchrone, −50 % ⚠ sur les coûts, SLA (SLA = Service Level Agreement) de 24 h ⚠, jusqu’à 100 000 requêtes ⚠ par batch.
Consignes
Partie A — Document de conception (25 min, rédigé)
Produisez une note d’architecture (1–2 pages) couvrant :
- Découpage : un seul batch de 80k ou plusieurs ? Justifiez (limites ⚠ : 100k requêtes / ~256 Mo ⚠ par batch ; granularité de reprise sur erreur).
- Schéma de
custom_id: proposez un format traçable (ex.mail-{lot}-{id_source}) et expliquez pourquoi la corrélation parcustom_idest obligatoire (les résultats ne reviennent pas dans l’ordre). - Choix de modèle : quel modèle pour de la classification simple à 80k exemplaires, et pourquoi ? (coût vs capacité)
- Cumul caching + batch : votre prompt de classification contient 3 000 tokens de taxonomie identiques pour les 80k requêtes. Expliquez comment
cache_controlse combine avec le batch et estimez l’économie supplémentaire (hits non garantis ⚠). - Gestion des états finaux :
succeeded,errored,expired,canceled— politique de rejeu pour chacun. - Estimation de coût complète : avec entrée ~3 300 tokens/requête et sortie ~150 tokens/requête, chiffrez le coût total avec et sans batch, avec et sans cache (tarifs ⚠ de la grille du jour, montrez vos calculs).
Partie B — Implémentation du pipeline (35 min)
Codez pipeline_batch.py avec quatre fonctions :
def construire_requetes(emails: list[dict]) -> list[dict]:
"""Génère les requêtes batch avec custom_id traçables,
system prompt de taxonomie marqué cache_control,
et prefill assistant '{' pour forcer le JSON."""
def soumettre(client, requetes: list[dict]) -> str:
"""Crée le batch, retourne son id. Découpe en plusieurs
batches si > 100_000 requêtes.""" # ⚠
def surveiller(client, batch_id: str, intervalle_s: int = 60) -> None:
"""Polling de processing_status jusqu'à 'ended'.
Logge request_counts à chaque itération.
Backoff : ne pas marteler l'API."""
def recolter(client, batch_id: str) -> tuple[list, list]:
"""Itère les résultats JSONL (JSONL = JSON Lines).
Retourne (succes, echecs). Les echecs incluent custom_id
+ type d'erreur pour rejeu ciblé."""
Exigences :
- Les résultats
erroredsont écrits dansrejeu.jsonl, prêts à être resoumis dans un batch correctif. - Chaque réponse
succeededest validée : JSON parsable ETstop_reason == "end_turn"(une classification tronquée parmax_tokensest un échec silencieux à intercepter). - Le polling utilise un intervalle croissant (60 s → 120 s → 300 s max) : un batch peut durer des heures, inutile d’interroger chaque seconde.
Critères d’évaluation
| Critère | Points |
|---|---|
| Note d’architecture : les 6 points traités avec chiffres | /8 |
| custom_id traçable + corrélation correcte | /3 |
| cache_control combiné au batch | /3 |
| Validation stop_reason sur chaque résultat | /3 |
| Pipeline de rejeu des échecs | /3 |
| Total | /20 |
Piège à éviter (indice)
Deux pièges classiques : (1) supposer que les résultats reviennent dans l’ordre de soumission ; (2) compter uniquement les erreurs HTTP et laisser passer les classifications tronquées (stop_reason: "max_tokens") comme des succès.
Barème global
| Exercice | Poids |
|---|---|
| 1 — Client robuste | 35 % |
| 2 — Streaming SSE | 30 % |
| 3 — Conception batch | 35 % |
Seuil de validation session : 60 %. Les corrigés types sont fournis par le formateur après remise.