# Aide-mémoire API Claude

Anatomie des requêtes et réponses de la Messages API, événements de streaming, tool use, sorties structurées, thinking, caching, batches, erreurs et retries — avec Python et TypeScript.

import { Tabs, TabItem, Steps } from '@prosefly/astro-components';

## Anatomie d'une requête

```json
{
  "model": "claude-opus-5",
  "max_tokens": 2048,
  "system": "You are a precise assistant. Answer only from <document>.",
  "messages": [
    { "role": "user", "content": [
      { "type": "text", "text": "<document>…</document>", "cache_control": { "type": "ephemeral" } },
      { "type": "text", "text": "Summarise the termination clause." }
    ]}
  ],
  "temperature": 0.2,
  "stop_sequences": ["</answer>"],
  "thinking": { "type": "adaptive" },
  "effort": "high",
  "tools": [],
  "tool_choice": { "type": "auto" },
  "metadata": { "user_id": "hashed-id" }
}
```

| Champ | Notes |
| --- | --- |
| `model` | ID épinglé : `claude-fable-5-1`, `claude-opus-5`, `claude-sonnet-5`, `claude-haiku-4-5` |
| `max_tokens` | Plafond dur de sortie ; `stop_reason: max_tokens` = tronqué |
| `system` | Chaîne de niveau supérieur ou blocs de contenu ; stable → cachable |
| `messages` | Alternance `user`/`assistant` ; le contenu est une chaîne ou un tableau de blocs (`text`, `image`, `document`, `tool_use`, `tool_result`, `thinking`) |
| `temperature` / `top_p` | Réglez l'un, pas les deux ; 0 réduit la variance, pas l'erreur |
| `thinking` | `{"type":"adaptive"}` (modèles actuels) ; `{"type":"enabled","budget_tokens":N}` uniquement Haiku 4.5 |
| `effort` | `low` / `medium` / `high` / `xhigh` |
| `tools` / `tool_choice` | Voir tool use ci-dessous ; le choix forcé est un 400 sur Fable 5.1 |
| `output_config.format` | JSON Schema pour la sortie structurée |

## Anatomie d'une réponse

```json
{
  "id": "msg_01…",
  "type": "message",
  "role": "assistant",
  "model": "claude-opus-5",
  "content": [
    { "type": "thinking", "thinking": "…", "signature": "…" },
    { "type": "text", "text": "The notice period is 60 days." }
  ],
  "stop_reason": "end_turn",
  "stop_sequence": null,
  "usage": {
    "input_tokens": 1200,
    "cache_creation_input_tokens": 0,
    "cache_read_input_tokens": 18000,
    "output_tokens": 42
  }
}
```

### stop_reason — branchez dessus à chaque fois

| Valeur | Signification | Action |
| --- | --- | --- |
| `end_turn` | Terminé naturellement | Fini |
| `tool_use` | Veut exécuter des outils | Exécuter, ajouter `tool_result`, rappeler |
| `max_tokens` | Tronqué | Continuer ou augmenter le plafond ; ne jamais traiter comme complet |
| `stop_sequence` | A atteint une chaîne d'arrêt | Fini (vérifiez `stop_sequence`) |
| `pause_turn` | Long tour d'outil côté serveur mis en pause | Renvoyer pour continuer |
| `refusal` | Refus de sécurité | Chemin de repli explicite ; ne réessayez pas aveuglément |

## Appels minimaux

<Tabs>
  <TabItem label="Python">
```python
from anthropic import Anthropic

client = Anthropic()  # reads ANTHROPIC_API_KEY

msg = client.messages.create(
    model="claude-sonnet-5",
    max_tokens=1024,
    system="You are a concise analyst.",
    messages=[{"role": "user", "content": "Three risks of vendor lock-in?"}],
)
print(msg.content[0].text, msg.stop_reason, msg.usage)
```
  </TabItem>
  <TabItem label="TypeScript">
```typescript
import Anthropic from '@anthropic-ai/sdk';

const client = new Anthropic();

const msg = await client.messages.create({
  model: 'claude-sonnet-5',
  max_tokens: 1024,
  system: 'You are a concise analyst.',
  messages: [{ role: 'user', content: 'Three risks of vendor lock-in?' }],
});
console.log(msg.content[0].type === 'text' ? msg.content[0].text : '', msg.stop_reason);
```
  </TabItem>
</Tabs>

## Streaming (SSE)

Ordre des événements : `message_start` → (`content_block_start` → `content_block_delta`* → `content_block_stop`)* → `message_delta` (porte `stop_reason`, l'usage de sortie) → `message_stop`.

<Tabs>
  <TabItem label="Python">
```python
with client.messages.stream(
    model="claude-sonnet-5", max_tokens=1024,
    messages=[{"role": "user", "content": "Write a haiku about latency."}],
) as stream:
    for text in stream.text_stream:
        print(text, end="", flush=True)
    final = stream.get_final_message()
    print(final.stop_reason, final.usage)
```
  </TabItem>
  <TabItem label="TypeScript">
```typescript
const stream = client.messages.stream({
  model: 'claude-sonnet-5', max_tokens: 1024,
  messages: [{ role: 'user', content: 'Write a haiku about latency.' }],
});
stream.on('text', (t) => process.stdout.write(t));
const final = await stream.finalMessage();
console.log(final.stop_reason, final.usage);
```
  </TabItem>
</Tabs>

## Aller-retour du tool use

```json
// 1. Request with tools
{ "model": "claude-opus-5", "max_tokens": 1024,
  "tools": [{
    "name": "get_order",
    "description": "Look up one order by ID. Use when the user references an order number. Returns status, items and total. Does not modify anything.",
    "input_schema": { "type": "object",
      "properties": { "order_id": { "type": "string", "description": "Order ID, e.g. ORD-12345" } },
      "required": ["order_id"] },
    "strict": true
  }],
  "messages": [{ "role": "user", "content": "Where is ORD-12345?" }] }

// 2. Response: stop_reason = "tool_use"
{ "content": [{ "type": "tool_use", "id": "toolu_01", "name": "get_order", "input": { "order_id": "ORD-12345" } }],
  "stop_reason": "tool_use" }

// 3. Follow-up with tool_result (in a USER message)
{ "messages": [
  { "role": "user", "content": "Where is ORD-12345?" },
  { "role": "assistant", "content": [{ "type": "tool_use", "id": "toolu_01", "name": "get_order", "input": { "order_id": "ORD-12345" } }] },
  { "role": "user", "content": [{ "type": "tool_result", "tool_use_id": "toolu_01",
      "content": "{\"status\":\"shipped\",\"eta\":\"2026-09-17\"}" }] }
] }

// Error result – structured, never empty-success
{ "type": "tool_result", "tool_use_id": "toolu_01", "is_error": true,
  "content": "{\"category\":\"not_found\",\"retryable\":false,\"message\":\"No order ORD-12345\"}" }
```

### La boucle agentique (Python)

```python
def run(messages, tools, model="claude-opus-5"):
    while True:
        r = client.messages.create(model=model, max_tokens=4096, tools=tools, messages=messages)
        messages.append({"role": "assistant", "content": r.content})
        if r.stop_reason == "tool_use":
            results = []
            for block in r.content:
                if block.type == "tool_use":
                    try:
                        out = TOOLS[block.name](**block.input)
                        results.append({"type": "tool_result", "tool_use_id": block.id, "content": json.dumps(out)})
                    except ToolError as e:
                        results.append({"type": "tool_result", "tool_use_id": block.id, "is_error": True,
                                        "content": json.dumps({"category": e.category, "retryable": e.retryable, "message": str(e)})})
            messages.append({"role": "user", "content": results})
            continue
        if r.stop_reason == "max_tokens":
            messages.append({"role": "user", "content": "Continue."}); continue
        if r.stop_reason == "pause_turn":
            continue
        if r.stop_reason == "refusal":
            return handle_refusal(r)
        return r  # end_turn / stop_sequence
```

### tool_choice

| Valeur | Comportement | Fable 5.1 |
| --- | --- | --- |
| `{"type":"auto"}` | Le modèle décide (défaut) | ✓ |
| `{"type":"any"}` | Doit appeler un outil | **400** |
| `{"type":"tool","name":"x"}` | Doit appeler l'outil x | **400** |
| `{"type":"none"}` | Aucun outil ce tour | ✓ |
| `disable_parallel_tool_use: true` | Un outil par tour | ✓ |

## Sorties structurées

```json
{ "model": "claude-sonnet-5", "max_tokens": 1024,
  "output_config": { "format": { "type": "json_schema", "schema": {
    "type": "object",
    "properties": {
      "vendor": { "type": "string" },
      "total": { "type": "number" },
      "currency": { "type": "string", "enum": ["USD", "EUR", "GBP"] },
      "line_items": { "type": "array", "items": { "type": "object",
        "properties": { "sku": { "type": "string" }, "qty": { "type": "integer" } },
        "required": ["sku", "qty"], "additionalProperties": false } }
    },
    "required": ["vendor", "total", "currency", "line_items"],
    "additionalProperties": false } } },
  "messages": [{ "role": "user", "content": [
    { "type": "document", "source": { "type": "base64", "media_type": "application/pdf", "data": "…" } },
    { "type": "text", "text": "Extract the invoice." } ] }] }
```

Validez toujours en aval et exécutez une boucle de **validation-retry** qui réinjecte l'erreur spécifique.

## Prompt caching

```json
{ "system": [{ "type": "text", "text": "<20k-token style guide>", "cache_control": { "type": "ephemeral" } }],
  "tools": [ … ],
  "messages": [ … dynamic content last … ] }
```

- Ordre : `tools` → `system` → `messages` ; les breakpoints de cache marquent la fin d'un préfixe stable.
- Minimum ~1024 tokens (2048 sur Haiku 4.5). Jusqu'à 4 breakpoints.
- TTL de 5 minutes par défaut (écriture 1,25×) ; option 1 heure (écriture 2×). Lectures 0,1×.
- `usage.cache_read_input_tokens` confirme les hits.

## Message Batches

```python
batch = client.messages.batches.create(requests=[
    {"custom_id": f"doc-{i}", "params": {"model": "claude-haiku-4-5", "max_tokens": 512,
     "messages": [{"role": "user", "content": doc}]}} for i, doc in enumerate(docs)])
# poll batch.processing_status until "ended", then stream results
for res in client.messages.batches.results(batch.id):
    if res.result.type == "succeeded": ...
```

50 % de réduction ; résultats sous 24 h ; succès/erreur par élément ; idéal pour « du jour au lendemain, le coût compte ».

## Erreurs et retries

| HTTP | Type | Retry ? |
| --- | --- | --- |
| 400 | `invalid_request_error` | Non — corrigez la requête (p. ex. `tool_choice` forcé sur Fable 5.1, `budget_tokens` sur Opus 5) |
| 401 | `authentication_error` | Non — clé |
| 403 | `permission_error` | Non — droit d'accès |
| 404 | `not_found_error` | Non — modèle/ressource |
| 413 | `request_too_large` | Non — réduire |
| 429 | `rate_limit_error` | Oui — backoff, respectez `retry-after` |
| 500 | `api_error` | Oui — backoff |
| 529 | `overloaded_error` | Oui — backoff, envisagez un modèle de repli |

Backoff exponentiel avec jitter ; clés d'idempotence pour les outils à effet de bord ; journalisez `request_id` depuis les en-têtes de réponse.

## Autres entrées et fonctionnalités

| Fonctionnalité | Forme |
| --- | --- |
| Vision | Bloc de contenu `type: image` avec une `source` de type `base64` ou `url` |
| PDF | Bloc de contenu `type: document` avec une source `base64` ou `url` et `media_type: application/pdf` |
| Files API | Uploadez une fois, référencez par `file_id` dans un bloc de contenu |
| Citations | Activez sur les documents pour obtenir les spans sources |
| Outils côté serveur | `web_search`, `code_execution`, `text_editor`, `bash`, `memory`, `computer` |
| Recherche d'outils | Outil `tool_search` plus `defer_loading: true` sur les outils de catalogue |
| Connecteur MCP | Tableau `mcp_servers` de niveau supérieur d'objets avec `type: url`, `url` et `name` |
| Context editing | Stratégies `context_management` qui effacent les anciens tool results côté serveur |
| Compaction | Résumé côté serveur préservant le fil narratif |
| Memory tool | Stockage persistant de type fichier entre sessions |

```json
// Vision and PDF content blocks
{ "type": "image",    "source": { "type": "url", "url": "https://example.com/chart.png" } }
{ "type": "document", "source": { "type": "base64", "media_type": "application/pdf", "data": "…" } }

// MCP connector
{ "mcp_servers": [{ "type": "url", "url": "https://mcp.example.com/mcp", "name": "orders" }] }
```

## Séquence complète d'événements de streaming

Le format filaire est SSE. Un tour complet avec un bloc de texte et un appel d'outil ressemble à ceci (deltas élidés marqués `…`) :

```text
event: message_start
data: {"type":"message_start","message":{"id":"msg_01","role":"assistant","model":"claude-opus-5","content":[],"stop_reason":null,"usage":{"input_tokens":1200,"output_tokens":1}}}

event: content_block_start
data: {"type":"content_block_start","index":0,"content_block":{"type":"thinking","thinking":""}}

event: content_block_delta
data: {"type":"content_block_delta","index":0,"delta":{"type":"thinking_delta","thinking":"Checking the order…"}}

event: content_block_delta
data: {"type":"content_block_delta","index":0,"delta":{"type":"signature_delta","signature":"Er8B…"}}

event: content_block_stop
data: {"type":"content_block_stop","index":0}

event: content_block_start
data: {"type":"content_block_start","index":1,"content_block":{"type":"text","text":""}}

event: content_block_delta
data: {"type":"content_block_delta","index":1,"delta":{"type":"text_delta","text":"Looking that up"}}

event: content_block_stop
data: {"type":"content_block_stop","index":1}

event: content_block_start
data: {"type":"content_block_start","index":2,"content_block":{"type":"tool_use","id":"toolu_01","name":"get_order","input":{}}}

event: content_block_delta
data: {"type":"content_block_delta","index":2,"delta":{"type":"input_json_delta","partial_json":"{\"order_id\":\"ORD-12345\"}"}}

event: content_block_stop
data: {"type":"content_block_stop","index":2}

event: message_delta
data: {"type":"message_delta","delta":{"stop_reason":"tool_use","stop_sequence":null},"usage":{"output_tokens":57}}

event: message_stop
data: {"type":"message_stop"}

event: ping        (may arrive at any time; ignore)
```

| Événement | Porte | Notes |
| --- | --- | --- |
| `message_start` | Message coquille, usage d'entrée | `content` est vide ; `stop_reason` null |
| `content_block_start` | Type de bloc à un `index` | Un par bloc text/thinking/tool_use |
| `content_block_delta` | `text_delta`, `thinking_delta`, `signature_delta`, `input_json_delta` | L'entrée d'outil arrive en JSON partiel — bufferisez par index et parsez au stop |
| `content_block_stop` | Bloc terminé | – |
| `message_delta` | **`stop_reason`** et usage de sortie cumulé | Branchez ici, pas sur la prose |
| `message_stop` | Tour terminé | – |
| `ping` | Keep-alive | Ignorer |
| `error` | `overloaded_error` etc. en cours de flux | Gérer comme l'erreur HTTP |

:::caution[Piège du streaming]
L'entrée d'outil arrive en fragments `input_json_delta` ; ne faites jamais `JSON.parse` sur un fragment partiel. Accumulez `partial_json` par index de bloc et parsez seulement après `content_block_stop`. Le `stop_reason` faisant foi est sur `message_delta`.
:::

## tool_result avec images

Un outil peut renvoyer une image (p. ex. un graphique rendu) en plus du texte. Le `content` d'un `tool_result` accepte un tableau de blocs :

```json
{ "role": "user", "content": [
  { "type": "tool_result", "tool_use_id": "toolu_09", "content": [
    { "type": "text", "text": "Chart rendered for Q3 revenue." },
    { "type": "image", "source": { "type": "base64", "media_type": "image/png", "data": "iVBORw0KGgo…" } }
  ] }
]}
```

## Appels d'outils parallèles — aller-retour complet

Le modèle peut émettre plusieurs blocs `tool_use` en un tour. Exécutez-les en concurrence et renvoyez **tous** les blocs `tool_result` dans le message utilisateur unique suivant.

```json
// Assistant turn: three parallel calls (stop_reason: tool_use)
{ "role": "assistant", "content": [
  { "type": "tool_use", "id": "toolu_a", "name": "get_weather", "input": { "city": "Paris" } },
  { "type": "tool_use", "id": "toolu_b", "name": "get_weather", "input": { "city": "Tokyo" } },
  { "type": "tool_use", "id": "toolu_c", "name": "get_fx",      "input": { "pair": "EURJPY" } }
]}

// Your next user turn: all results, matched by tool_use_id, order-independent
{ "role": "user", "content": [
  { "type": "tool_result", "tool_use_id": "toolu_a", "content": "{\"c\":18}" },
  { "type": "tool_result", "tool_use_id": "toolu_b", "content": "{\"c\":26}" },
  { "type": "tool_result", "tool_use_id": "toolu_c", "content": "{\"rate\":171.2}" }
]}
```

Utilisez `disable_parallel_tool_use: true` dans `tool_choice` pour forcer un appel par tour lorsque les appels ont des effets de bord qui doivent être ordonnés.

## Sortie structurée avec validation-retry

```python
import json, jsonschema
from anthropic import Anthropic

client = Anthropic()
SCHEMA = { "type": "object",
  "properties": { "vendor": {"type": "string"}, "total": {"type": "number"},
                  "currency": {"type": "string", "enum": ["USD","EUR","GBP"]} },
  "required": ["vendor","total","currency"], "additionalProperties": False }

def extract(doc_text, max_attempts=3):
    messages = [{"role": "user", "content": f"Extract the invoice.\n<doc>{doc_text}</doc>"}]
    for attempt in range(max_attempts):
        r = client.messages.create(
            model="claude-sonnet-5", max_tokens=1024,
            output_config={"format": {"type": "json_schema", "schema": SCHEMA}},
            messages=messages)
        text = r.content[0].text
        try:
            data = json.loads(text)
            jsonschema.validate(data, SCHEMA)      # schema + business rules
            if data["total"] < 0:
                raise ValueError("total must be non-negative")
            return data
        except (json.JSONDecodeError, jsonschema.ValidationError, ValueError) as e:
            messages += [{"role": "assistant", "content": text},
                         {"role": "user", "content": f"That failed validation: {e}. Return corrected JSON only."}]
    raise RuntimeError("extraction failed after retries")   # escalate, never silently return bad data
```

La boucle réinjecte l'erreur **spécifique** et plafonne les tentatives, puis escalade — elle ne renvoie jamais d'objet vide ou non validé (anti-pattern de silent-failure).

## Prompt caching — disposition multi-breakpoint

Jusqu'à quatre breakpoints. Ordonnez du plus stable au moins stable pour que le plus long préfixe possible reste en cache quand seule la fin change.

```json
{
  "system": [
    { "type": "text", "text": "<static company style guide, 15k tokens>", "cache_control": { "type": "ephemeral" } }
  ],
  "tools": [
    { "name": "search_kb", "description": "…", "input_schema": { }, "cache_control": { "type": "ephemeral" } }
  ],
  "messages": [
    { "role": "user", "content": [
      { "type": "text", "text": "<retrieved policy docs, changes per session>", "cache_control": { "type": "ephemeral", "ttl": "1h" } },
      { "type": "text", "text": "Now: the user's current question (never cached)." }
    ]}
  ]
}
```

| Breakpoint | Contenu | TTL | Justification |
| --- | --- | --- | --- |
| 1 | Guide de style système | 5 min | Ne change jamais ; préfixe le plus profond |
| 2 | Définitions d'outils | 5 min | Stable dans toute l'application |
| 3 | Documents de session | 1 heure | Réutilisés toute la session ; le TTL plus long amortit l'écriture 2× |
| — | Question actuelle | aucun | Unique par requête |

Règle : un breakpoint met en cache tout ce qui le **précède**. Placer un bloc volatile tôt invalide tout le caching plus profond.

## États du cycle de vie d'un Batch

```text
create → in_progress ──► (per request: succeeded | errored | canceled | expired)
                     └─► ended   (all requests terminal; results retrievable)
        cancel ─► canceling ─► ended
```

| `processing_status` | Signification |
| --- | --- |
| `in_progress` | Toujours en cours ; sondez `request_counts` |
| `canceling` | Annulation demandée |
| `ended` | Terminal ; récupérez le flux de résultats |

| `result.type` par requête | Traitement |
| --- | --- |
| `succeeded` | Utilisez `result.message` |
| `errored` | Inspectez `result.error` ; peut re-soumettre cet élément |
| `canceled` | Le batch a été annulé avant l'exécution |
| `expired` | Non terminé dans la fenêtre de 24 h ; re-soumettre |

Les résultats sont disponibles pendant 29 jours. Associez les éléments par `custom_id` ; ne présumez pas de l'ordre.

## Gestion des erreurs avec backoff

<Tabs>
  <TabItem label="Python">
```python
import time, random
from anthropic import Anthropic, APIStatusError, RateLimitError, APIConnectionError

client = Anthropic()
RETRYABLE = {429, 500, 502, 503, 529}

def call_with_retry(**params):
    for attempt in range(6):
        try:
            return client.messages.create(**params)
        except RateLimitError as e:
            wait = float(e.response.headers.get("retry-after", 0)) or min(60, 2 ** attempt)
            time.sleep(wait + random.uniform(0, 0.5))
        except APIStatusError as e:
            if e.status_code in RETRYABLE:
                time.sleep(min(60, 2 ** attempt) + random.uniform(0, 0.5))
            else:
                raise                              # 400/401/403/404/413 → fix, don't retry
        except APIConnectionError:
            time.sleep(min(60, 2 ** attempt) + random.uniform(0, 0.5))
    raise RuntimeError("exhausted retries")
```
  </TabItem>
  <TabItem label="TypeScript">
```typescript
import Anthropic, { APIError } from '@anthropic-ai/sdk';

const client = new Anthropic();
const RETRYABLE = new Set([429, 500, 502, 503, 529]);
const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms));

async function callWithRetry(params: Anthropic.MessageCreateParamsNonStreaming) {
  for (let attempt = 0; attempt < 6; attempt++) {
    try {
      return await client.messages.create(params);
    } catch (err) {
      if (err instanceof APIError && (RETRYABLE.has(err.status ?? 0))) {
        const retryAfter = Number(err.headers?.['retry-after']) || Math.min(60, 2 ** attempt);
        await sleep((retryAfter + Math.random() * 0.5) * 1000);
        continue;
      }
      throw err; // 4xx client errors → fix the request
    }
  }
  throw new Error('exhausted retries');
}
```
  </TabItem>
</Tabs>

Les SDK réessaient automatiquement avec backoff ; une boucle personnalisée importe quand vous réglez le plafond, ajoutez du jitter, ou basculez vers un modèle de repli sur des 529 répétés.

## En-têtes de limite de débit et idempotence

| En-tête | Signification |
| --- | --- |
| `anthropic-ratelimit-requests-remaining` | Budget RPM restant |
| `anthropic-ratelimit-input-tokens-remaining` | Budget ITPM restant |
| `anthropic-ratelimit-output-tokens-remaining` | Budget OTPM restant |
| `anthropic-ratelimit-*-reset` | Quand chaque compartiment se recharge (RFC 3339) |
| `retry-after` | Secondes à attendre après un 429/529 — respectez-le |
| `request-id` | Journalisez-le pour chaque requête ; incluez-le dans les tickets de support |

Réduisez proactivement le débit quand un en-tête `*-remaining` approche de zéro plutôt que d'attendre le 429.

```python
# Idempotency: safe retries for side-effecting requests
client.messages.create(**params, extra_headers={"idempotency-key": f"charge-{order_id}"})
```

Réutilisez la même clé sur les retries pour qu'une livraison en double ne double pas la facturation. Concevez les **outils** à effet de bord de la même façon (acceptez un argument de clé d'idempotence).

## Files API et citations

```python
# Upload once, reference by file_id across many requests
f = client.files.upload(file=("contract.pdf", open("contract.pdf","rb"), "application/pdf"))

r = client.messages.create(
    model="claude-sonnet-5", max_tokens=1024,
    messages=[{"role": "user", "content": [
        {"type": "document", "source": {"type": "file", "file_id": f.id},
         "citations": {"enabled": True}},
        {"type": "text", "text": "What is the termination notice period? Cite the clause."}
    ]}])
```

Avec les citations activées, les blocs de texte portent un tableau `citations` de spans sources :

```json
{ "type": "text", "text": "The notice period is 60 days.",
  "citations": [{ "type": "page_location", "cited_text": "…sixty (60) days…",
                  "document_index": 0, "start_page_number": 4, "end_page_number": 4 }] }
```

Les citations activent le test de provenance : chaque affirmation renvoie à un span que vous pouvez ouvrir.

## Formes de requête pour context editing et compaction

```json
// Context editing: clear stale tool results server-side, keep the turn valid
{ "model": "claude-opus-5", "max_tokens": 4096,
  "context_management": {
    "edits": [{ "type": "clear_tool_uses", "trigger": { "type": "input_tokens", "value": 100000 },
                "keep": { "type": "tool_uses", "value": 3 } }]
  },
  "messages": [ … ] }
```

```json
// Compaction: summarise older turns while preserving the narrative
{ "context_management": {
    "edits": [{ "type": "compact", "trigger": { "type": "input_tokens", "value": 150000 } }] } }
```

| Stratégie | Supprime | Conserve | À utiliser quand |
| --- | --- | --- | --- |
| Context editing (`clear_tool_uses`) | Anciens tool results verbeux | Les N derniers tool uses, tout le texte | Longues exécutions d'agent à forte densité d'outils |
| Compaction (`compact`) | Anciens tours → résumé | Continuité narrative | Longues sessions conversationnelles |

Les deux s'exécutent côté serveur, donc ils gardent valide l'historique en ajout seul de Fable 5.1 (un élagage côté client ne le ferait pas).

## Memory tool

```json
{ "model": "claude-opus-5", "max_tokens": 2048,
  "tools": [{ "type": "memory_20250818", "name": "memory" }],
  "messages": [{ "role": "user", "content": "Remember I prefer metric units, then convert 5 miles." }] }
```

Le modèle lit/écrit dans un stockage persistant de type fichier (via des appels à l'outil `memory` que vous exécutez sur votre stockage sous-jacent) qui survit à la compaction et aux nouvelles sessions. Utilisez-le pour des préférences et un état durables ; ne le bourrez pas dans le prompt système.

## Connecteur MCP (côté serveur)

```python
r = client.messages.create(
    model="claude-opus-5", max_tokens=1024,
    mcp_servers=[{ "type": "url", "url": "https://mcp.example.com/mcp", "name": "orders",
                   "authorization_token": user_scoped_token }],
    extra_headers={"anthropic-beta": "mcp-client-2025-04-04"},
    messages=[{"role": "user", "content": "Where is ORD-12345?"}])
```

L'infrastructure d'Anthropic se connecte au serveur MCP distant pour vous — aucun harnais client local. Passez un token **au périmètre de l'utilisateur** pour que les outils agissent en tant qu'utilisateur final, pas en tant que super-utilisateur partagé.

## Managed Agents vs Agent SDK

| | Managed Agents | Agent SDK (`claude-agent-sdk`) |
| --- | --- | --- |
| Qui exécute la boucle | Anthropic (boucle + sandbox hébergés) | Vous (votre infra) |
| Charge d'exploitation | Minimale | Vous gérez le scaling, le sandboxing, les secrets |
| Contrôle sur runtime/réseau/localité des données | Limité | Complet |
| Tools/MCP/hooks/subagents | Configurés | Contrôle programmatique complet |
| Choisir quand | « moindre charge opérationnelle », « ne veut pas héberger » | « contrôler le runtime », « les données doivent rester dans notre VPC », « harnais personnalisé » |

```python
# Agent SDK sketch – you host the harness
from claude_agent_sdk import ClaudeAgent
agent = ClaudeAgent(model="claude-opus-5", tools=[...], mcp_servers=[...],
                    permission_mode="acceptEdits")
result = agent.run("Refactor the auth module and run the tests.")
```

## Idées reçues courantes

| Idée reçue | Réalité | Pourquoi cela compte à l'examen |
| --- | --- | --- |
| « tool_result va dans un message assistant » | Il va dans un message **user**, associé par `tool_use_id` | Distracteur de mauvais rôle |
| « Parser le texte pour ‘done’ afin de terminer la boucle » | Branchez sur `stop_reason` | Anti-pattern de parsing de prose |
| « Une troncature `max_tokens` est une réponse terminée » | C'est une troncature — continuez ou augmentez le plafond | Distracteur de silent-failure |
| « Réessayer chaque erreur » | Seulement 429/5xx/529 avec backoff ; corrigez les 4xx | Distracteur de tempête de retries |
| « Sortie structurée signifie qu'on peut sauter la validation » | Validez quand même + réessayez les règles métier | Distracteur de sur-confiance |
| « Mettre en cache un bloc volatile tôt fait économiser » | Cela invalide tout cache plus profond ; le stable en premier | Distracteur de disposition de cache |
| « Un résultat vide convient quand un outil ne trouve rien » | Renvoyez un `is_error`/`not_found` structuré | Anti-pattern de silent empty-success |

## Analyse de scénario

Une équipe exécute un agent qui appelle 3–4 outils par tour (certains parallèles), sur Opus 5, dans une longue session qui atteint parfois des 529 et dépasse 150k tokens. Les outils de paiement ne doivent pas double-facturer. À quoi ressemble une implémentation correcte ?

<Steps>
1. **Contrôle de boucle** — branchez sur `stop_reason` ; sur `tool_use`, exécutez tous les blocs `tool_use` parallèles en concurrence et renvoyez chaque `tool_result` dans un seul message user.
2. **Ordonner les effets de bord** — pour l'outil de paiement, activez `disable_parallel_tool_use` quand il doit être séquencé, et passez une clé d'idempotence pour qu'un appel réessayé soit sûr.
3. **Gestion des 529** — backoff exponentiel avec jitter respectant `retry-after` ; après des 529 répétés, basculez vers un modèle plus récent ou équivalent (Opus 5 → Fable 5.1 est sûr vers le haut ; jamais vers un modèle plus ancien en milieu de session si les blocs de thinking comptent).
4. **Croissance du contexte** — configurez le context editing `clear_tool_uses` à ~100k tokens d'entrée en conservant les 3 derniers tool uses ; côté serveur pour que l'historique reste valide.
5. **Erreurs venant des outils** — résultats `is_error` structurés avec category/retryable, jamais un succès vide.
6. **Observabilité** — journalisez `request-id` et `usage` par appel ; surveillez `anthropic-ratelimit-*-remaining` et réduisez le débit avant le 429.
</Steps>

Alternatives rejetées : parser la prose pour s'arrêter (parsing de prose), réessayer les 400 (tempête de retries), élaguer l'historique côté client (casse l'ajout seul), et « j'ai fini » auto-déclaré comme sortie de boucle (recours à l'auto-déclaration).
