Claude API 2026
TL;DR En 2026 le Claude SDK est ton default workhorse. Messages API + system prompts XML + prompt caching (5min/1h, -90% sur tokens stables) + adaptive thinking (Opus 4.6+, Sonnet 4.6) couvrent 80% des besoins. Tool use + Files API + Citations te donnent une plateforme de RAG/agentique sans framework. Batch API (-50%) gère les workloads offline. MCP natif et Agent SDK (Managed Agents) remplacent LangChain pour les agents complexes. Tu maîtrises ça → tu factures 1 200-1 500€/j sans hésiter.
⚠️ Ruptures 2026 que tu DOIS connaître (elles tombent à chaque entretien et cassent ton code en prod) :
- Le flagship par défaut est
claude-opus-4-8(Opus 4.8) à 5 $ / 25 $ le M tokens (in/out), contexte 1M. Mid :claude-sonnet-4-6(3 $ / 15 $). Cheap :claude-haiku-4-5(1 $ / 5 $). Au-dessus d'Opus,claude-fable-5existe (10 $ / 50 $) pour le raisonnement le plus exigeant et l'agentique long-horizon — tu le choisis explicitement, jamais par défaut « pour l'upgrade ».thinking={"type": "enabled", "budget_tokens": N}est supprimé sur Opus 4.7/4.8 (et Fable 5) → HTTP 400. Utilisethinking={"type": "adaptive"}+output_config={"effort": "..."}.temperature/top_p/top_ksont rejetés (400) sur Opus 4.7/4.8 — tu pilotes par le prompt et l'effort.- Le prefill assistant (dernier tour
role: "assistant") renvoie 400 sur toute la famille 4.6+ → remplace par structured outputs (output_config.format).- Pour les outputs structurés : préfère
client.messages.parse()avec un schéma Pydantic plutôt que du XML/JSON prompté à la main.- Adaptive thinking : Opus 4.6/4.7/4.8 + Sonnet 4.6. Haiku 4.5 n'a pas de thinking. Sur Opus 4.7/4.8,
displayvaut"omitted"par défaut (blocsthinkingvides) — passe"summarized"si tu affiches le raisonnement.
Mental model
L'API Claude = un téléphone à un avocat brillant qui ne se souvient de rien. Chaque appel = un nouvel avocat (sans prompt_caching) OU le même avocat qui a un dossier déjà ouvert (avec cache_control). Tu ne paies pas l'appel, tu paies le temps qu'il faut pour lire ton dossier + temps qu'il prend à répondre.
┌────────────────────────────────────────────────────────┐
│ ANTHROPIC PLATFORM │
│ │
│ ┌─────────────┐ ┌──────────────┐ ┌────────────┐ │
│ │ Messages │ │ Batch API │ │ Files API │ │
│ │ (sync/ │ │ (50% off, │ │ (PDF, │ │
│ │ stream) │ │ ≤24h) │ │ images) │ │
│ └──────┬──────┘ └──────┬───────┘ └─────┬──────┘ │
│ │ │ │ │
│ ┌──────▼──────────────────▼────────────────▼──────┐ │
│ │ CLAUDE MODELS 2026 │ │
│ │ Opus 4.8 │ Sonnet 4.6 │ Haiku 4.5 │ │
│ │ Adaptive │ Adaptive │ Fast/cheap │ │
│ │ thinking │ thinking │ (200K ctx) │ │
│ │ 5$/25$ │ 3$/15$ │ 1$/5$ │ │
│ └──────┬──────────────────────────────────────────┘ │
│ │ │
│ ┌──────▼──────────────┐ ┌──────────────────────┐ │
│ │ Tool use │ │ Computer use │ │
│ │ - JSON tools │ │ (control desktop) │ │
│ │ - Text editor │ │ │ │
│ │ - Bash │ │ │ │
│ │ - MCP servers │ │ │ │
│ └─────────────────────┘ └──────────────────────┘ │
└────────────────────────────────────────────────────────┘
│
┌──────────▼───────────┐
│ PROMPT CACHING │
│ 5 min (default) │ ← marque la fin d'un bloc cachable
│ 1 h (ttl="1h") │ ← stable knowledge, sys prompts
│ 90% off sur reads │
└──────────────────────┘Analogie : prompt caching = bureau du stagiaire avec ses dossiers déjà ouverts. Le stagiaire ne relit pas les 200 pages à chaque question — il les a sous le coude, tu lui poses juste la question du jour. Tu paies 10% du prix.
L'équation mentale d'un staff engineer
Tu ne raisonnes jamais en « quel modèle est le meilleur » mais en coût × latence × qualité × risque sur ce use case précis. La formule de coût d'un appel :
coût = (input_uncached × P_in)
+ (cache_read × P_in × 0.1)
+ (cache_write × P_in × 1.25) # 1.25× pour TTL 5min, 2× pour TTL 1h
+ (output × P_out)Trois leviers que tu actionnes dans cet ordre avant de toucher au modèle :
- Cache le préfixe stable (system + tools + few-shot). -90% sur la partie répétée. C'est le levier #1, gratuit à implémenter.
- Descends d'un cran de modèle sur les sous-tâches simples (routing, extraction, classification → Haiku). Un agent bien conçu mélange les modèles.
- Batch ce qui n'est pas temps réel (-50%). Overnight = ton ami.
Choisir le modèle : la table de décision
| Critère | Haiku 4.5 (1$/5$) | Sonnet 4.6 (3$/15$) | Opus 4.8 (5$/25$) |
|---|---|---|---|
| Contexte max | 200K | 1M | 1M |
| Latence p50 | la plus basse | moyenne | la plus haute |
| Raisonnement multi-étapes | faible | bon | état de l'art |
| Tool use / agentique long-horizon | basique | solide | référence |
| Classification / extraction / routing | idéal | overkill | gaspillage |
| Coût/qualité sur 80% des prods | acceptable | sweet spot | quand la qualité prime sur le coût |
| Quand le sortir | volume énorme, tâche cadrée | défaut prod | refacto complexe, devis multi-critères, audit |
Mental model du staff : tu commences toujours par te demander « est-ce que Sonnet 4.6 suffit ? ». Tu ne montes à Opus que si une éval montre un gain mesurable. Tu ne descends à Haiku que si une éval montre que la qualité tient. Le choix de modèle est une décision data-driven, pas un réflexe.
Pricing par modèle (USD / M tokens, 2026)
| Modèle | Input | Cached read (~0.1×) | Cache write 5min (1.25×) | Cache write 1h (2×) | Output |
|---|---|---|---|---|---|
claude-opus-4-8 | 5 $ | 0,50 $ | 6,25 $ | 10 $ | 25 $ |
claude-sonnet-4-6 | 3 $ | 0,30 $ | 3,75 $ | 6 $ | 15 $ |
claude-haiku-4-5 | 1 $ | 0,10 $ | 1,25 $ | 2 $ | 5 $ |
Batch API : -50% sur input ET output (pas sur le cache write, qui n'a pas de sens en batch puisque chaque requête est indépendante).
Code minimal
Anatomie complète d'un appel : system prompt cached, multi-turn, tool use, streaming, error handling.
import anthropic
from anthropic import RateLimitError, APIStatusError
import time
import os
client = anthropic.Anthropic() # lit ANTHROPIC_API_KEY
SYSTEM_PROMPT = """Tu es un assistant juridique senior. Réponds en français, cite tes sources.
<rules>
- N'invente jamais une référence
- Si tu ne sais pas, dis-le
</rules>"""
def call_claude(messages: list[dict], max_retries: int = 3) -> str:
for attempt in range(max_retries):
try:
resp = client.messages.create(
model="claude-sonnet-4-6",
max_tokens=1024,
system=[
{"type": "text", "text": SYSTEM_PROMPT, "cache_control": {"type": "ephemeral"}}
],
messages=messages,
temperature=0.0,
)
return resp.content[0].text
except RateLimitError as e:
wait = int(e.response.headers.get("retry-after", 2 ** attempt))
time.sleep(wait)
except APIStatusError as e:
if e.status_code >= 500:
time.sleep(2 ** attempt)
continue
raise
raise RuntimeError("max retries reached")
# Multi-turn conversation
conv = [
{"role": "user", "content": "Qu'est-ce qu'un préavis de licenciement ?"},
]
ans1 = call_claude(conv)
conv.append({"role": "assistant", "content": ans1})
conv.append({"role": "user", "content": "Et pour un cadre dirigeant ?"})
ans2 = call_claude(conv)
print(ans2)Streaming :
with client.messages.stream(
model="claude-sonnet-4-6",
max_tokens=1024,
system=SYSTEM_PROMPT,
messages=[{"role": "user", "content": "Explique le préavis"}],
) as stream:
for text in stream.text_stream:
print(text, end="", flush=True)
final = stream.get_final_message() # message complet : usage, stop_reasonQuand streamer ? Dès que
max_tokensest élevé (> ~16k), le non-streaming risque de timeout HTTP côté SDK. Opus 4.8 va jusqu'à 128k d'output mais exige le streaming à ce niveau. Règle staff : non-streaming →max_tokens ≈ 16000; streaming →≈ 64000. Ne lowball jamaismax_tokens: si tu tapes le plafond,stop_reason == "max_tokens"et la réponse est tronquée silencieusement.
Ce qu'un serveur de prod utilise vraiment
L'exemple ci-dessus est synchrone et single-shot. En vrai, sur un backend NestJS/FastAPI tu veux : async, retries SDK typés, timeouts par appel, et logging de usage pour le coût.
from anthropic import (
AsyncAnthropic,
RateLimitError, APIStatusError, APITimeoutError, OverloadedError,
)
# max_retries : le SDK retry déjà 429/5xx/overload avec backoff exponentiel
client = AsyncAnthropic(max_retries=4, timeout=60.0)
async def call(messages: list[dict]) -> str:
try:
resp = await client.messages.create(
model="claude-sonnet-4-6",
max_tokens=1024,
system=[{"type": "text", "text": SYSTEM_PROMPT,
"cache_control": {"type": "ephemeral"}}],
messages=messages,
timeout=30.0, # override par appel (chemin chaud court)
)
except OverloadedError:
# 529 : l'API est saturée. Fallback Haiku, ou file d'attente.
raise
except (RateLimitError, APITimeoutError, APIStatusError) as e:
# déjà retry par le SDK ; ici tu logs + remontes proprement
raise
# OBSERVABILITÉ : logge le coût de CHAQUE appel
u = resp.usage
log.info("claude_call",
in_tok=u.input_tokens,
out_tok=u.output_tokens,
cache_read=getattr(u, "cache_read_input_tokens", 0),
cache_write=getattr(u, "cache_creation_input_tokens", 0),
stop=resp.stop_reason)
return resp.content[0].textAppels de tools en parallèle : quand Claude renvoie plusieurs tool_use dans un même tour, ne les exécute pas en série. asyncio.gather :
import asyncio
async def run_tools(blocks):
tool_uses = [b for b in blocks if b.type == "tool_use"]
results = await asyncio.gather(*(execute_tool(b.name, b.input) for b in tool_uses))
return [
{"type": "tool_result", "tool_use_id": b.id, "content": json.dumps(r)}
for b, r in zip(tool_uses, results)
]Outputs structurés : messages.parse() plutôt que du JSON prompté
Ne demande plus « réponds en JSON » dans le prompt et ne parse plus à la main avec find("{") (fragile, casse au premier } dans une string). Le prefill assistant ({"role": "assistant", "content": "{"}) renvoie 400 sur la famille 4.6+. La bonne méthode : un schéma Pydantic validé par l'API.
from pydantic import BaseModel
class Devis(BaseModel):
produit: str
prime_mensuelle_eur: float
franchise_eur: int
justification: str
resp = client.messages.parse(
model="claude-sonnet-4-6",
max_tokens=2000,
messages=[{"role": "user", "content": "Génère le devis pour ce profil…"}],
output_config={"format": Devis},
)
devis: Devis = resp.parsed_output # typé, validé, pas de json.loads fragileLimites à connaître : pas de minimum/maximum ni minLength/maxLength dans le schéma (le SDK Python/TS les retire et valide côté client), pas de schéma récursif, incompatible avec Citations (400). Premier appel d'un nouveau schéma = coût de compilation one-shot, puis cache 24h.
Cas d'usage concrets
LegalTech — Analyse de contrats avec citations exactes (cabinet d'affaires Lyon)
Problème : un cabinet en M&A analyse 30-40 SPA (Share Purchase Agreement)/mois, 100-200 pages chacun. Les juniors produisent des memos mais n'arrivent pas à citer les clauses exactes (paraphrasage involontaire, références imprécises). Risque pro : un memo qui dit "le vendeur garantit X" alors que la clause exacte dit "le vendeur déclare avoir connaissance de X" — différence majeure en cas de litige.
Solution : Claude Sonnet 4.6 avec Citations API native (sortie fin 2025). Le modèle attache automatiquement, dans sa réponse, des spans exacts du document source. Pas d'hallucination de citation possible.
import anthropic
client = anthropic.Anthropic()
with open("contrat_spa.pdf", "rb") as f:
pdf_bytes = f.read()
resp = client.messages.create(
model="claude-sonnet-4-6",
max_tokens=4000,
system="Tu es un avocat M&A. Analyse le contrat et produis un memo avec citations exactes.",
messages=[{
"role": "user",
"content": [
{
"type": "document",
"source": {"type": "base64", "media_type": "application/pdf", "data": pdf_bytes},
"citations": {"enabled": True},
"cache_control": {"type": "ephemeral", "ttl": "1h"},
},
{"type": "text", "text": "Liste les garanties du vendeur avec citations exactes."},
],
}],
)
for block in resp.content:
if block.type == "text":
print(block.text)
for c in (block.citations or []):
print(f" → page {c.start_page_number}-{c.end_page_number}: \"{c.cited_text}\"")Gains chiffrés :
- Production memo : 8h junior → 1h junior (validation IA)
- Risque erreur de citation : ~5% → ~0% (citations machine-vérifiées)
- Capacité analyse contrat : 4/mois/junior → 25/mois/junior
- Coût IA : ~5€/contrat (cache PDF 1h activé pour re-analyses)
- TJM mission : 1 400€/j × 22 j = 30 800€ + 1 500€/mois MCO
E-commerce premium — Génération de réponses customer support brand voice (marque maroquinerie Paris)
Problème : marque de maroquinerie haut de gamme, 800 tickets support/jour. Réponses standardisées tuent la marque (clients exigeants à 800€ d'AOV). Brand voice exige : ton chaleureux mais sobre, jamais d'émoji, vocabulaire spécifique ("votre objet", pas "votre produit"; "atelier", pas "usine"; "savoir-faire", pas "fabrication").
Solution : Claude Sonnet 4.6 + system prompt brand voice cached 1h + Haiku 4.5 pour les cas simples (tracking, FAQ) + escalade humaine si confidence < 0.7. Tool use pour query Shopify (order status) + Zendesk (history).
BRAND_VOICE = """Tu réponds aux clients de [Marque], maison française de maroquinerie depuis 1923.
<brand_voice>
- Ton : chaleureux, sobre, jamais familier
- Vocabulaire : "votre objet" (pas "produit"), "atelier" (pas "usine"), "patine" (pas "vieillissement"), "cuir" (jamais "matériau")
- Bannir : "no problem", "pas de souci", emojis, !!, abréviations
- Ouverture : "Madame, Monsieur," ou "Cher Monsieur/Chère Madame [Nom]" si connu
- Clôture : "Avec toute notre considération," + prénom du conseiller
- Phrases courtes (< 25 mots), pas de jargon technique
</brand_voice>
<exemples>
[20 exemples annotés par la directrice clientèle, mis à jour mensuellement]
</exemples>
<rules>
- Si réclamation > 200€ : escalade humaine
- Si demande retour > J+30 : escalade humaine
- Si client mécontent (sentiment négatif) : escalade humaine
- Si tu n'es pas sûr à 90% : escalade humaine
</rules>"""
TOOLS = [
{
"name": "get_order_status",
"description": "Récupère le statut d'une commande Shopify",
"input_schema": {
"type": "object",
"properties": {"order_id": {"type": "string"}},
"required": ["order_id"],
},
},
{
"name": "get_customer_history",
"description": "Récupère l'historique d'un client (CA, nb commandes, segment)",
"input_schema": {
"type": "object",
"properties": {"email": {"type": "string"}},
"required": ["email"],
},
},
]Gains chiffrés :
- Temps de réponse 1er ticket : 3h → 4 min
- 5 agents support → 2 agents (les 3 autres réaffectés à la fidélisation premium)
- NPS support : 32 → 51
- Coût IA : ~0.04€/ticket × 800/j = 32€/j (~960€/mois)
- ROI : 3 agents × 38k€ chargé/an = 114k€/an économisés
- TJM : 1 350€/j × 30 j = 40 500€ + 2 000€/mois MCO + ajustements brand voice mensuels
Assurance — Agent de devis auto avec adaptive thinking (courtier assurance Marseille)
Problème : devis auto demande à évaluer 18-25 critères (véhicule, conducteur, historique, kilométrage, garage, options) + matching contre 12 produits assureurs partenaires. Le courtier passe 30-45 min par devis, et 60% des devis n'aboutissent pas (mauvais matching, prix trop hauts).
Solution : Claude Opus 4.8 avec adaptive thinking + output_config.effort. Le modèle décide lui-même combien réfléchir (le budget fixe budget_tokens est supprimé sur Opus 4.7/4.8 → HTTP 400). On contrôle la profondeur par l'effort (low/medium/high/xhigh/max). Particulièrement adapté au matching multi-critères, et le display: "summarized" te rend un résumé du raisonnement pour l'audit ACPR/CNIL.
resp = client.messages.create(
model="claude-opus-4-8",
max_tokens=4000,
thinking={"type": "adaptive", "display": "summarized"}, # display: défaut "omitted" sur 4.7/4.8
output_config={"effort": "high"}, # pilote la profondeur, pas un budget de tokens
system=DEVIS_SYSTEM_PROMPT,
messages=[{
"role": "user",
"content": f"Profil client: {json.dumps(profile)}\nVéhicule: {json.dumps(vehicle)}\nProduits disponibles: {json.dumps(products)}",
}],
)
# Le content contient [thinking_block, text_block, ...]
for block in resp.content:
if block.type == "thinking":
log_to_audit(block.thinking) # résumé du raisonnement → trace ACPR/CNIL
elif block.type == "text":
devis = json.loads(block.text)Mieux : pour un devis structuré garanti, n'utilise pas
json.loads(block.text)(fragile). Utiliseclient.messages.parse()avec un schéma Pydantic (voir « Outputs structurés » plus bas) — la validation est faite par l'API, pas par toi.
Gains chiffrés :
- Temps par devis : 35 min → 4 min
- Taux conversion devis → contrat : 40% → 58% (meilleur matching)
- Capacité courtier : 12 devis/j → 60 devis/j
- Coût IA : ~0.50€/devis (Opus 4.8 + adaptive thinking) — assumable face à commission ~150€/devis vendu
- TJM : 1 500€/j × 35 j (intégration CRM Salesforce, conformité ACPR, audit logs) = 52 500€ + 3 000€/mois MCO
Exemple end-to-end
Assistant juridique batch qui analyse 1000 contrats overnight avec Batch API + Citations + Langfuse.
"""
overnight_legal_batch.py — Analyse de 1000 contrats en batch.
Workflow:
1. Lecture queue (S3 ou DB) des contrats à analyser
2. Création d'un batch job Anthropic (50% off)
3. Polling jusqu'à completion (jusqu'à 24h)
4. Téléchargement résultats, parsing
5. Persistence + Langfuse traces
6. Notification Slack
"""
from __future__ import annotations
import os
import json
import time
import asyncio
import base64
from pathlib import Path
from dataclasses import dataclass
import anthropic
import boto3
from anthropic.types.messages.batch_create_params import Request
from langfuse import Langfuse
import psycopg
from slack_sdk import WebClient
client = anthropic.Anthropic()
s3 = boto3.client("s3")
langfuse = Langfuse()
slack = WebClient(token=os.environ["SLACK_TOKEN"])
SYSTEM_PROMPT = """Tu es un avocat M&A senior. Analyse le contrat PDF et produis JSON structuré.
<output_schema>
{
"summary": "5-8 lignes",
"parties": [{"name": "...", "role": "vendeur|acheteur|garant"}],
"amount_eur": 0,
"key_dates": [{"date_iso": "YYYY-MM-DD", "description": "..."}],
"warranties": [{"party": "...", "description": "...", "cap_eur": 0, "duration_months": 0}],
"red_flags": [{"severity": "haute|moyenne|basse", "description": "...", "recommandation": "..."}]
}
</output_schema>
Utilise les citations PDF natives pour ancrer les claims. Si donnée absente, mets null."""
@dataclass
class ContractJob:
contract_id: str
s3_key: str
client_id: str
# ---------- 1. Fetch jobs ----------
def fetch_pending_contracts(limit: int = 1000) -> list[ContractJob]:
with psycopg.connect(os.environ["DATABASE_URL"]) as conn:
with conn.cursor() as cur:
cur.execute(
"SELECT id, s3_key, client_id FROM contracts "
"WHERE status = 'pending' LIMIT %s",
(limit,),
)
return [ContractJob(*r) for r in cur.fetchall()]
# ---------- 2. Build batch requests ----------
def build_request(job: ContractJob) -> Request:
obj = s3.get_object(Bucket=os.environ["S3_BUCKET"], Key=job.s3_key)
pdf_b64 = base64.b64encode(obj["Body"].read()).decode()
return Request(
custom_id=job.contract_id,
params={
"model": "claude-sonnet-4-6",
"max_tokens": 4000,
"system": [
{"type": "text", "text": SYSTEM_PROMPT, "cache_control": {"type": "ephemeral", "ttl": "1h"}}
],
"messages": [{
"role": "user",
"content": [
{
"type": "document",
"source": {"type": "base64", "media_type": "application/pdf", "data": pdf_b64},
"citations": {"enabled": True},
},
{"type": "text", "text": "Analyse ce contrat selon le schema."},
],
}],
},
)
# ---------- 3. Create batch ----------
def create_batch(jobs: list[ContractJob]) -> str:
print(f"[+] Building {len(jobs)} requests...")
requests = [build_request(j) for j in jobs]
batch = client.messages.batches.create(requests=requests)
print(f"[+] Batch created: {batch.id}")
return batch.id
# ---------- 4. Poll until done ----------
def wait_for_batch(batch_id: str, poll_interval: int = 60) -> dict:
while True:
batch = client.messages.batches.retrieve(batch_id)
counts = batch.request_counts
print(f" [{batch.processing_status}] proc={counts.processing} ok={counts.succeeded} err={counts.errored}")
if batch.processing_status in ("ended", "canceled", "expired"):
return batch
time.sleep(poll_interval)
# ---------- 5. Process results ----------
def process_results(batch_id: str) -> dict:
batch = client.messages.batches.retrieve(batch_id)
stats = {"ok": 0, "errored": 0, "parse_errors": 0}
for result in client.messages.batches.results(batch_id):
custom_id = result.custom_id
if result.result.type != "succeeded":
stats["errored"] += 1
mark_failed(custom_id, str(result.result))
continue
msg = result.result.message
try:
full_text = "".join(b.text for b in msg.content if b.type == "text")
j_start = full_text.find("{")
j_end = full_text.rfind("}")
parsed = json.loads(full_text[j_start : j_end + 1])
citations = [
{"text": c.cited_text, "page_start": c.start_page_number, "page_end": c.end_page_number}
for b in msg.content if b.type == "text"
for c in (b.citations or [])
]
save_analysis(custom_id, parsed, citations, msg.usage)
log_langfuse(custom_id, parsed, msg.usage)
stats["ok"] += 1
except (json.JSONDecodeError, ValueError) as e:
stats["parse_errors"] += 1
mark_failed(custom_id, f"parse_error: {e}")
return stats
def save_analysis(contract_id: str, analysis: dict, citations: list, usage):
with psycopg.connect(os.environ["DATABASE_URL"]) as conn:
with conn.cursor() as cur:
cur.execute(
"UPDATE contracts SET status='analyzed', analysis=%s, citations=%s, "
"tokens_in=%s, tokens_out=%s, analyzed_at=now() WHERE id=%s",
(json.dumps(analysis), json.dumps(citations),
usage.input_tokens, usage.output_tokens, contract_id),
)
def mark_failed(contract_id: str, reason: str):
with psycopg.connect(os.environ["DATABASE_URL"]) as conn:
with conn.cursor() as cur:
cur.execute(
"UPDATE contracts SET status='failed', error=%s WHERE id=%s",
(reason, contract_id),
)
def log_langfuse(contract_id: str, analysis: dict, usage):
langfuse.trace(
name="legal_batch_analysis",
id=f"contract-{contract_id}",
input={"contract_id": contract_id},
output=analysis,
metadata={
"model": "claude-sonnet-4-6",
"input_tokens": usage.input_tokens,
"output_tokens": usage.output_tokens,
"cache_read": getattr(usage, "cache_read_input_tokens", 0),
"cache_write": getattr(usage, "cache_creation_input_tokens", 0),
},
)
# ---------- 6. Main ----------
def main():
jobs = fetch_pending_contracts(limit=1000)
if not jobs:
print("No pending contracts.")
return
batch_id = create_batch(jobs)
wait_for_batch(batch_id, poll_interval=120)
stats = process_results(batch_id)
slack.chat_postMessage(
channel="#legal-ops",
text=(
f"Batch {batch_id} complete.\n"
f"OK: {stats['ok']}\nErrored: {stats['errored']}\nParse errors: {stats['parse_errors']}"
),
)
print(f"[+] Done. Stats: {stats}")
if __name__ == "__main__":
main()Tu lances ça à 22h, à 6h tu as 1000 contrats analysés avec citations et stockés en DB. Coût : 50% du tarif normal = ~$1.20 par contrat (vs $2.40 sync), soit ~$1200 pour 1000 contrats au lieu de $2400. Avec cache 1h sur le system prompt → encore -30%.
Patterns courants
1. Prompt caching multi-niveau
system=[
{"type": "text", "text": ROLE_PERSONA}, # non caché
{"type": "text", "text": LONG_KNOWLEDGE_BASE, "cache_control": {"type": "ephemeral", "ttl": "1h"}},
{"type": "text", "text": FEW_SHOT_EXAMPLES, "cache_control": {"type": "ephemeral"}}, # 5min
]Règle d'or : place le plus stable en haut, avec TTL le plus long. Anthropic match par préfixe — un seul caractère différent au début casse le cache.
Hiérarchie d'invalidation (ce qu'un senior sait et un junior ignore) : tous les changements ne cassent pas tout. Il y a trois tiers — tools → system → messages — et un changement n'invalide que son tier et ceux d'après. Concrètement :
| Tu changes… | Cache tools | Cache system | Cache messages |
|---|---|---|---|
| Définition d'un tool (ajout/retrait/réordre) | ❌ | ❌ | ❌ |
| Modèle | ❌ | ❌ | ❌ |
tool_choice, images, toggle thinking | ✅ | ✅ | ❌ |
| Contenu d'un message | ✅ | ✅ | ❌ |
Donc tu peux flipper tool_choice ou activer/désactiver thinking par requête sans perdre le cache tools+system. Mais changer le set d'outils ou le modèle force un rebuild complet — d'où la règle « ne change jamais le modèle en cours de conversation ; spawn un sous-agent Haiku à la place ».
Pre-warming (latence p99 du 1er appel) : si le 1er appel d'une session est visible par l'utilisateur (chat/voix) et que ton préfixe est gros, envoie un appel max_tokens=0 au démarrage. L'API fait le prefill, écrit le cache, et renvoie immédiatement (content: [], charge de cache-write normale, 0 token d'output facturé). Au prochain appel réel : cache chaud, TTFT minimal.
client.messages.create(
model="claude-opus-4-8",
max_tokens=0,
system=[{"type": "text", "text": SYSTEM_PROMPT, "cache_control": {"type": "ephemeral"}}],
messages=[{"role": "user", "content": "warmup"}],
)Place le cache_control sur le dernier bloc partagé avec la vraie requête (system/tools), pas sur le placeholder user. Inutile si ton trafic est continu (chaque requête < TTL réchauffe la suivante) ou si le préfixe est sous le minimum cacheable (4096 tok Opus, 2048 tok Sonnet).
Token counting : ne devine jamais un nombre de tokens avec tiktoken (tokenizer OpenAI, sous-compte Claude de 15-20%). Utilise client.messages.count_tokens(model=..., messages=...) — c'est gratuit, stateless, et model-specific. Indispensable pour un cost-estimator pre-flight ou un garde-fou « refuse si > N tokens » avant d'envoyer un PDF de 300 pages.
2. Tool use avec disable_parallel_tool_use=False
Claude 4.x peut appeler plusieurs tools en parallèle (Sonnet et Opus). Par défaut activé. À désactiver si tu veux du séquentiel strict.
resp = client.messages.create(
model="claude-sonnet-4-6",
tools=TOOLS,
tool_choice={"type": "auto", "disable_parallel_tool_use": False},
messages=[...],
)Tool use loop :
def agent_loop(initial_user: str, max_iters: int = 10):
messages = [{"role": "user", "content": initial_user}]
for _ in range(max_iters):
resp = client.messages.create(
model="claude-sonnet-4-6", max_tokens=2048,
tools=TOOLS, messages=messages,
)
messages.append({"role": "assistant", "content": resp.content})
if resp.stop_reason == "end_turn":
return resp
# Process tool calls
tool_results = []
for block in resp.content:
if block.type == "tool_use":
result = execute_tool(block.name, block.input)
tool_results.append({"type": "tool_result", "tool_use_id": block.id, "content": json.dumps(result)})
messages.append({"role": "user", "content": tool_results})3. Adaptive thinking + effort (PAS de budget_tokens)
⚠️ Rupture 2026 : sur Opus 4.7/4.8, thinking={"type": "enabled", "budget_tokens": N} renvoie HTTP 400. Le budget fixe est mort. Le modèle décide adaptativement, et tu pilotes la profondeur par effort.
resp = client.messages.create(
model="claude-opus-4-8",
max_tokens=4000,
thinking={"type": "adaptive", "display": "summarized"}, # "omitted" par défaut → thinking vide
output_config={"effort": "high"}, # low | medium | high | xhigh | max
messages=[...],
)
# resp.content[i].type == "thinking" — résumé du raisonnement (si display="summarized")
# resp.content[j].type == "text" — réponse finaleLe thinking est facturé en output tokens (cher), mais évite d'écrire un long prompt CoT. Utile sur raisonnement multi-étapes, math, code, matching.
| Effort | Quand | Note |
|---|---|---|
low | tâches courtes, latence-sensibles, sous-agents | scope minimal |
medium | cost-sensitive, compromis raisonnable | |
high | défaut pour le travail intelligence-sensitive | recommandé minimum |
xhigh | coding / agentique long-horizon | meilleur réglage, défaut de Claude Code |
max | correctness > coût | risque d'overthinking |
Détails modèles :
thinking={"type": "disabled"}est accepté sur Opus 4.8/4.7 mais renvoie 400 sur Fable 5 (où il faut omettre le champ). Sonnet 4.6 supporte adaptive ; Haiku 4.5 n'a pas de thinking budget. Ledisplaypar défaut est"omitted"sur 4.7/4.8 (les blocsthinkingont un texte vide) — passe"summarized"si tu affiches le raisonnement.
4. Files API
# Upload une fois
file = client.beta.files.upload(file=open("manual.pdf", "rb"))
# Référence dans plusieurs requêtes
resp = client.messages.create(
model="claude-sonnet-4-6",
messages=[{"role": "user", "content": [
{"type": "document", "source": {"type": "file", "file_id": file.id}, "cache_control": {"type": "ephemeral", "ttl": "1h"}},
{"type": "text", "text": "Question..."},
]}],
)Avantage vs base64 inline : le fichier est uploadé 1 fois, réutilisable 30 jours, et le cache 1h s'applique.
5. MCP integration
resp = client.messages.create(
model="claude-sonnet-4-6",
mcp_servers=[{
"type": "url",
"url": "https://mcp.linear.app",
"name": "linear",
"authorization_token": os.environ["LINEAR_TOKEN"],
}],
messages=[{"role": "user", "content": "Crée un ticket Linear pour le bug X"}],
)Claude accède aux outils du serveur MCP directement (Linear, GitHub, Jira, Notion via leurs MCP officiels).
6. Citations API
messages=[{"role": "user", "content": [
{
"type": "document",
"source": {"type": "base64", "media_type": "application/pdf", "data": pdf_b64},
"citations": {"enabled": True},
"title": "Contrat SPA v3",
},
{"type": "text", "text": "Liste les garanties avec citations."},
]}]La réponse contient des text blocks avec citations[] listant cited_text, start_page_number, end_page_number, document_index. Anti-hallucination par construction.
Versions & écosystème 2026
| Élément | Version 2026 | Note |
|---|---|---|
| anthropic Python SDK | récent | Support adaptive thinking, MCP, Files API, Citations, messages.parse() |
| anthropic TS SDK | récent | Idem (betaZodTool, messages.parse()) |
claude-fable-5 | most capable | Raisonnement extrême / long-horizon, $10/$50, 1M ctx, thinking toujours on. Choix explicite seulement. |
claude-opus-4-8 | flagship par défaut | Raisonnement / agents complexes, $5/$25, contexte 1M |
claude-sonnet-4-6 | mid | Default prod, $3/$15, contexte 1M |
claude-haiku-4-5 | cheap | Cheap/fast, $1/$5, contexte 200K |
| Computer use | GA | Sonnet/Opus, sandbox VM recommandée |
| Batch API | GA | 50% off, max 100k requests, ≤24h |
| Files API | GA | 500MB/fichier, 100GB/org, persistant |
| Citations API | GA | PDF + plain text |
| MCP support | Native | Plus de plugins HTTP custom |
| Agent SDK / Managed Agents | GA | claude-agent-sdk + sessions managées server-side |
Bedrock & Vertex : Claude disponible sur AWS Bedrock et GCP Vertex AI. Pricing aligné mais avec moins de features avancées (Citations parfois en retard, Files API non disponible, pas de Managed Agents ni de tools server-side). Préfère Anthropic API directe sauf contrainte cloud. À ne pas confondre avec Claude Platform on AWS (opéré par Anthropic, parité same-day, IDs de modèle bare sans préfixe anthropic.).
Le cas Fable 5 (à connaître même si tu ne l'utilises pas) : c'est le modèle le plus capable, mais avec une surface d'API différente de la famille Opus. Trois pièges :
- Thinking toujours actif : tu omets le champ
thinking(un{"type": "disabled"}explicite renvoie 400, contrairement à Opus 4.8/4.7). Le raw chain-of-thought n'est jamais renvoyé — seulement des résumés viadisplay: "summarized". stop_reason: "refusal": des classifieurs de sûreté peuvent refuser une requête → HTTP 200 avecstop_reason == "refusal"et uncontentvide (pré-output, non facturé) ou partiel (mid-stream, facturé — à jeter). Branche toujours surstop_reasonavant de lireresp.content[0], sinon IndexError sur les refus. Des faux positifs arrivent sur du travail légitime adjacent (sécurité, sciences du vivant).- Fallbacks server-side (beta, opt-in) : passe
fallbacks=[{"model": "claude-opus-4-8"}]+ le beta header pour qu'un refus soit re-servi par Opus 4.8 dans le même appel, avec repricing automatique. À mettre par défaut dans du code Fable 5. - Rétention 30 jours requise : pas disponible en zero-data-retention (sinon 400 sur toute requête).
Pitfalls
Oublier
cache_controlsur un long system prompt → tu paies 10x ce qu'il faut. Tout system prompt > 1024 tokens DOIT être caché.Cache breakpoints multiples mal placés : tu peux avoir 4
cache_controlblocks, mais s'ils sont mal ordonnés (volatile avant stable), seul le premier marche. Ordre = stable → volatile.Streaming + error : si l'erreur arrive après le
start, tu dois consommer le stream jusqu'au bout pour libérer la connexion. Utilisewith client.messages.stream(...)pas la version raw.Tool loop infini : sans
max_iters, un agent peut boucler sur des tools. Toujours timeboxer (10-20 iters max) + budget tokens cumulé.PDF > 32MB non supporté en base64 (limite Anthropic). Utilise Files API ou découpe.
max_tokenstrop bas → réponse tronquée silencieuse (stop_reason=max_tokens). Toujours checkstop_reasonaprès la réponse.Batch API : 24h pas garanti. Si urgent, sync. Sinon plan pour 4-8h en moyenne, jusqu'à 24h en pic.
Adaptive thinking facturé en output. Sur Opus 4.8, le raisonnement compte en output ($25/M). Tu ne fixes plus un budget (
budget_tokensest mort → 400) : tu pilotes pareffort. Si tu fais 100 req/h enhigh/max, ça monte vite. Metseffort: "low"/"medium"sur ce qui ne le mérite pas, et utilisetask_budget(beta) pour borner une boucle agentique entière.Rate limits par tier. Tier 1 = 50 RPM, 40k TPM. Pour batch, c'est 100k requests par batch mais batch concurrents limités. Demande upgrade tôt si projet B2B.
MCP server non-trusted = injection vectorielle. Le serveur MCP peut renvoyer du contenu malicieux qui sera interprété comme instructions par Claude. Sanitize / whitelist.
Pricing / ROI client
Prix 2026 (USD/M tokens) :
| Modèle | Input | Cache read (~0.1×) | Cache write 5min (1.25×) | Cache write 1h (2×) | Output |
|---|---|---|---|---|---|
claude-opus-4-8 | $5 | $0.50 | $6.25 | $10 | $25 |
claude-sonnet-4-6 | $3 | $0.30 | $3.75 | $6 | $15 |
claude-haiku-4-5 | $1 | $0.10 | $1.25 | $2 | $5 |
Batch API : -50% sur input et output.
Calcul ROI client typique (cabinet d'avocats, 100 contrats/mois) :
- 100 contrats × 90k tok input × $3/M (sans cache) = $27/mois
- Avec cache 1h system prompt (50k tok stable) : $5-8/mois
- Avec batch : $2.50-4/mois
- Coût IA dérisoire face à la valeur
Mission type :
- Audit + POC : 5 j × 1 200€ = 6 000€
- Production : 25 j × 1 400€ = 35 000€
- MCO + évolutions : 2 000€/mois
- ROI client visible en 2-3 mois sur un use case bien choisi.
Testing / Eval
1. Tests d'intégration avec VCR
# pip install vcrpy
import vcr
@vcr.use_cassette("tests/cassettes/legal_analysis.yaml", filter_headers=["x-api-key"])
def test_legal_analysis():
resp = call_claude([{"role": "user", "content": "Test"}])
assert "préavis" in resp.lower()Tu enregistres l'appel API une fois, puis tu rejoues offline en CI → pas de coût, pas de flakiness.
2. Eval avec Langfuse Datasets
from langfuse import Langfuse
lf = Langfuse()
dataset = lf.get_dataset("legal_analysis_v1") # 50 cas annotés
for item in dataset.items:
out = analyze_contract(item.input["pdf_path"])
lf.score(
trace_id=current_trace_id(),
name="accuracy",
value=compute_accuracy(out, item.expected_output),
)3. LLM-as-judge pour eval qualitatif
JUDGE_PROMPT = """Tu es un avocat senior. Compare la réponse {answer} à la réponse attendue {expected}.
Note de 1 à 5 : précision juridique, qualité des citations, complétude. Sors JSON."""4. Tests de régression sur prompts
À chaque modification de prompt → re-run eval set complet → diff vs baseline. Un seuil de -5% sur n'importe quelle métrique = blocage merge.
Quand utiliser / éviter
Utilise Claude API quand :
- FR de qualité (Claude excellent en FR)
- Tâches longues / agentiques (extended thinking)
- Tu as besoin de citations natives (legal, finance, santé)
- Tu veux le meilleur ratio qualité/coût sur le marché (Sonnet 4.6)
- Computer use ou Files API requis
Évite (ou complète avec) Claude quand :
- Tu as besoin de Realtime audio bidirectionnel → OpenAI Realtime API ou Gemini Live
- Latence p99 < 1 sec requise → Haiku ou Mistral Small 3
- Tu as besoin de souveraineté FR stricte → Mistral / Scaleway
- Tu veux fine-tuner → OpenAI (GPT-4o mini fine-tuning) ou Mistral
- Budget ultra-serré sur volume énorme → Haiku ou modèles open
🏋️ Exercices
Progressifs, du « ça marche » au « défends le chiffre en prod ». Fais-les dans l'ordre.
1. Le wrapper de prod minimal
Objectif : écrire un client AsyncAnthropic réutilisable qui survit à la prod.
Construis une fonction call_claude(messages, *, model, max_tokens) qui : (a) cache le system prompt via cache_control, (b) gère RateLimitError/OverloadedError/APITimeoutError avec max_retries SDK + un fallback Haiku sur 529, (c) logge usage (in/out/cache) en JSON structuré, (d) vérifie stop_reason et lève une erreur explicite si == "max_tokens".
Indice/Solution : pars de la section « Ce qu'un serveur de prod utilise vraiment ». Le piège : sur 529, le SDK ne fallback pas tout seul — c'est à toi de catcher OverloadedError et de relancer sur claude-haiku-4-5.
2. Prouver le cache (et le casser)
Objectif : démontrer empiriquement le -90% du prompt caching, puis l'invalider à dessein.
Envoie 3 requêtes identiques avec un system prompt de >4096 tokens caché. Vérifie que cache_read_input_tokens est ~0 à la 1re puis élevé aux suivantes. Ensuite, casse le cache en interpolant datetime.now() dans le system prompt et re-mesure.
Indice/Solution : le cache est un préfixe exact. Un seul byte qui change en tête invalide tout ce qui suit (tools → system → messages). Minimum cacheable : 4096 tok sur Opus 4.8/Haiku, 2048 sur Sonnet 4.6 — sous ce seuil, cache_creation_input_tokens: 0 sans erreur. Sur Opus 4.8 un prompt de 3k tok ne cachera pas.
3. Migrer du code 4.7 cassé
Objectif : prendre un appel qui renvoyait 400 et le rendre runnable sur 4.8.
On te donne : model="claude-opus-4-7", thinking={"type":"enabled","budget_tokens":16000}, temperature=0.3, et un dernier tour {"role":"assistant","content":"{"} pour forcer du JSON. Tout ça renvoie 400. Corrige.
Indice/Solution : budget_tokens → thinking={"type":"adaptive"} + output_config={"effort":"high"} ; supprime temperature/top_p/top_k (rejetés) ; remplace le prefill par messages.parse() avec un schéma Pydantic. Trois ruptures, trois fixes.
4. Devis structuré end-to-end, validé
Objectif : produire un devis garanti-conforme, sans json.loads fragile.
Reprends le cas Assurance. Définis un schéma Pydantic Devis (produit, prime, franchise, justification, niveau de confiance 0-1), appelle messages.parse() avec adaptive thinking, et logge le résumé du raisonnement (display:"summarized") pour l'audit. Si confidence < 0.7, route vers escalade humaine.
Indice/Solution : output_config={"format": Devis}. Attention : structured outputs est incompatible avec Citations (400) — si tu veux les deux, sépare en deux appels. Le thinking summary part en output tokens : mesure le surcoût.
5. Le batch overnight : défends le chiffre
Objectif : chiffrer précisément le coût d'un batch de 1000 contrats et tenir le nombre face à un CFO.
Prends le script overnight_legal_batch.py. Calcule le coût réel : 1000 contrats × ~90k tok input (PDF) sur Sonnet 4.6, avec cache 1h sur le system prompt + Batch API -50%. Sépare cache-write (2× la 1re fois), cache-read (0.1×), input non-caché, et output. Donne un €/contrat défendable.
Indice/Solution : le cache write 1h coûte $6/M sur Sonnet (2×), les reads $0.30/M (0.1×). Le PDF de chaque contrat n'est pas cachable entre contrats (contenu différent) — seul le system prompt l'est. Piège classique : croire que tout le contrat profite du -90%. Le batch ne facture pas le cache write (chaque requête indépendante).
6. Casse l'agent, puis blinde-le
Objectif : transformer la boucle de tools naïve en boucle de prod.
Prends agent_loop. Fais-le boucler à l'infini (un tool qui renvoie toujours « réessaie »). Puis blinde : max_iters + budget tokens cumulé, exécution parallèle des tools via asyncio.gather, timeout par appel, et arrêt propre sur stop_reason == "end_turn". Bonus : task_budget (beta) pour que le modèle se modère lui-même.
Indice/Solution : sans max_iters, un agent boucle. max_tokens borne une réponse ; task_budget (min 20k, beta task-budgets-2026-03-13) borne toute la boucle et le modèle le voit. Le parallélisme : ne jamais exécuter des tool_use en série quand ils sont indépendants.
7. Le refus qui crashe ta prod (Fable 5)
Objectif : rendre un pipeline Fable 5 robuste à un stop_reason: "refusal" — d'abord en le cassant, puis en le blindant.
Écris un appel claude-fable-5 qui lit resp.content[0].text sans précaution. Construis une requête sur un sujet adjacent à la sécurité (légitime mais sensible) qui déclenche un refus du classifieur → observe l'IndexError/AttributeError (le content est vide). Puis blinde : (a) branche sur stop_reason == "refusal" avant de lire content, (b) ajoute le fallback server-side fallbacks=[{"model": "claude-opus-4-8"}] + le beta header pour que le refus soit re-servi par Opus 4.8 dans le même appel, (c) logge quand le fallback a servi (via usage.iterations).
Indice/Solution : sur Fable 5 le refus est un HTTP 200, pas une exception — d'où le crash silencieux du code naïf. Pré-output → content vide, non facturé ; mid-stream → output partiel facturé, à jeter. Le fallbacks est opt-in : sans lui, un refus fait juste échouer la requête. Distingue refus de classifieur (où le fallback a du sens) du refus du modèle lui-même via stop_details.category.
8. Attribution de coût sous charge : défends la facture par tenant
Objectif : construire une observabilité de coût qui survit à un audit FinOps multi-tenant.
Sur un backend qui sert N clients via un system prompt caché partagé, tu dois facturer chaque tenant au token près. Écris un middleware qui, pour chaque appel, calcule le coût réel à partir de resp.usage : input_tokens × P_in + cache_read × P_in × 0.1 + cache_creation × P_in × (1.25 ou 2) + output × P_out, avec le bon prix selon le modèle effectivement servi (resp.model — pas celui demandé, à cause des fallbacks). Émets une métrique par tenant + par modèle. Piège à résoudre : sur une conversation longue, input_tokens ne montre que le reste non-caché — le vrai volume de prompt = input_tokens + cache_creation + cache_read.
Indice/Solution : ne facture jamais sur le modèle demandé — un fallback Fable 5 → Opus 4.8 change le prix. La source de vérité du per-attempt est usage.iterations (chaque tentative facturée à son propre tarif ; un refus pré-output n'est pas facturé). Le cache-write 1h est 2× et arrive une seule fois — si tu l'amortis sur tous les appels du tenant tu te trompes ; attribue-le au 1er appel ou lisse-le explicitement et documente le choix.
🎤 En entretien
« Pourquoi
budget_tokensne marche plus, et qu'est-ce qui le remplace ? » Supprimé sur Opus 4.7/4.8 (HTTP 400). Adaptive thinking décide la profondeur ; on la pilote paroutput_config.effort(low→max), ettask_budgetborne une boucle agentique entière.« Comment réduire le coût d'un appel sans toucher au modèle ? » Dans cet ordre : cache le préfixe stable (system+tools+few-shot, -90% sur les reads), descends d'un cran de modèle sur les sous-tâches (routing/extraction → Haiku), batch ce qui n'est pas temps réel (-50%). Le choix de modèle vient en dernier et doit être data-driven (éval).
« Pourquoi
cache_read_input_tokensreste à 0 alors que j'ai miscache_control? » Invalidateur silencieux dans le préfixe :datetime.now()/UUID en tête de system, JSON non trié (sort_keys), set d'outils qui varie, ou préfixe sous le minimum cacheable (4096 tok sur Opus 4.8). Le cache est un match de préfixe exact byte-à-byte.« Sync ou async sur un backend, et comment gères-tu un 529 ? »
AsyncAnthropicpour un serveur (non-bloquant). Le SDK retry déjà 429/5xx/overload avec backoff (max_retries), mais surOverloadedError(529) persistant je catch et je fallback sur un modèle moins chargé (Haiku) ou je mets en file. Et je loggeusageà chaque appel pour le coût.« Comment garantis-tu un JSON structuré sans
json.loadsfragile ni prefill ? »client.messages.parse()avec un schéma Pydantic/zod (output_config.format). Le prefill assistant ({"role":"assistant","content":"{"}) renvoie 400 sur la famille 4.6+. La validation est faite par l'API, et le schéma est caché 24h après le 1er appel. Limite : incompatible avec Citations (400) — si tu veux les deux, deux appels.« Comment choisis-tu entre Sonnet, Opus et Fable 5 ? » Je pars toujours de « Sonnet 4.6 suffit-il ? » et je ne monte qu'avec une éval qui montre un gain mesurable. Opus 4.8 quand la qualité prime (refacto complexe, matching multi-critères). Fable 5 seulement pour le raisonnement extrême / long-horizon, jamais « pour l'upgrade » — il est 2× le prix d'Opus et a une API différente (thinking toujours on,
refusalà gérer, rétention 30j). Le choix est data-driven, pas un réflexe.« Sur Fable 5, ton code lit
resp.content[0].textet crashe en prod par intermittence. Pourquoi ? »stop_reason: "refusal": un classifieur de sûreté décline avec un HTTP 200 et uncontentvide. Mon code naïf fait IndexError. Le fix : brancher surstop_reasonavant de lirecontent, et opt-in au fallback server-side (fallbacks=[{"model":"claude-opus-4-8"}]) pour que le refus soit re-servi dans le même appel.
Liens
- Anthropic docs : https://docs.anthropic.com/en/api/getting-started
- Models overview : https://docs.anthropic.com/en/docs/about-claude/models
- Prompt caching : https://docs.anthropic.com/en/docs/build-with-claude/prompt-caching
- Extended thinking : https://docs.anthropic.com/en/docs/build-with-claude/extended-thinking
- Tool use : https://docs.anthropic.com/en/docs/build-with-claude/tool-use/overview
- Batch API : https://docs.anthropic.com/en/docs/build-with-claude/batch-processing
- Files API : https://docs.anthropic.com/en/docs/build-with-claude/files
- Citations : https://docs.anthropic.com/en/docs/build-with-claude/citations
- MCP : https://modelcontextprotocol.io
- Agent SDK : https://github.com/anthropics/claude-agent-sdk-python
- Pricing : https://www.anthropic.com/pricing