# D8 · Eval, Testing and Debugging

Identifier les erreurs de la couche d’intégration vs de la sortie du modèle, récupération, analyse de traces, journalisation des request ID, reproductibilité, bases de l’éval, tests de régression en CI et métriques par segment.

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

Ce domaine représente environ **1 item sur 53**, mais il relie les autres entre eux. Il vérifie que vous savez distinguer une défaillance de la **couche d’intégration** d’une défaillance de la **sortie du modèle**, reproduire et tracer les problèmes, et évaluer la qualité sans vous tromper vous-même. Le thème : **mesurez par segment, reproduisez de manière déterministe, et jugez la sortie dans une session séparée.**

## Objectifs d’apprentissage
À la fin de cette page, vous devriez être capable de :

1. Distinguer les erreurs de la **couche d’intégration** de celles de la **sortie du modèle** et choisir la récupération.
2. Faire de l’**analyse de traces** et journaliser les **request ID**.
3. **Reproduire** les problèmes avec un modèle épinglé et `temperature: 0` là où c’est approprié.
4. Appliquer les **bases de l’éval** : jeu de référence, rubrique, LLM-as-judge dans une session séparée.
5. Ajouter des **tests de régression en CI** et utiliser des **métriques par segment**.

---

## 8.1 Integration-layer vs model-output errors

La première question dans tout incident : **quelle couche a échoué ?** Les erreurs de la couche d’intégration vivent dans la tuyauterie (auth, transport, forme de requête, limites de débit, parsing). Les erreurs de sortie du modèle vivent dans ce que Claude a produit (hallucination, dérive de format, refus, troncature). Les retries et timeouts corrigent les premières ; les changements de prompt/contexte/éval corrigent les secondes. Appliquer le mauvais correctif à la mauvaise couche est le distracteur classique.

### Error taxonomy

| Layer | Error | Diagnostic signal | Recovery |
| --- | --- | --- | --- |
| Intégration | **Auth** | `401 authentication` / `403 permission` | Vérifier la clé d’API, l’org, les scopes ; ne pas réessayer aveuglément |
| Intégration | **Schéma / mauvaise requête** | `400 invalid_request` (p. ex. `tool_choice:"any"` sur Fable 5.1) | Corriger la forme de requête ; utiliser `auto` + `strict:true` / sorties structurées |
| Intégration | **Timeout** | Timeout client, aucune réponse | Régler des timeouts sains ; réessayer avec backoff ; envisager le streaming |
| Intégration | **Limite de débit** | `429 rate_limit`, en-tête `retry-after` | Backoff exponentiel + jitter ; respecter `retry-after` ; batch/file |
| Intégration | **Parsing** | Erreur de décodage JSON sur le corps de réponse | Valider/réparer ; demander des sorties structurées ; validation-retry |
| Sortie du modèle | **Hallucination** | Affirmation fluide sans ancrage dans les entrées | Ancrage, citations, RAG, validation ; vérifier les affirmations |
| Sortie du modèle | **Dérive de format** | Sortie presque valide mais hors schéma | Schéma d’outil `strict:true` / sorties structurées ; validation-retry |
| Sortie du modèle | **Refus** | `stop_reason: "refusal"` | Reformuler une requête légitime ; ajuster le prompt système ; escalader |
| Sortie du modèle | **Troncature** | `stop_reason: "max_tokens"` | Augmenter `max_tokens` ; découper la tâche ; streamer et continuer |

:::tip[Signal d’examen]
Demandez d’abord **quelle couche**. Un `429`, un timeout, ou une erreur de décodage JSON relève de l’intégration — corrigez avec backoff, timeouts, ou changements de requête/schéma. Une hallucination, une sortie hors schéma, ou un refus relève de la sortie du modèle — corrigez avec des changements de prompt/contexte/modèle et de la validation. Le `stop_reason` et le code de statut HTTP vous disent quelle couche.
:::

---

## 8.2 Trace analysis walkthrough

Journalisez le **`request-id`** et un ID de corrélation pour chaque appel, plus `model`, `stop_reason`, `usage` et la latence. Pour les agents, journalisez la **séquence d’outils**. Les traces vous permettent de localiser où une exécution multi-étapes a mal tourné et donnent au support Anthropic une prise.

```python
resp = client.messages.create(model=MODEL, max_tokens=512, messages=msgs)
log.info("claude_call", extra={
    "request_id": resp._request_id, "model": resp.model,
    "stop_reason": resp.stop_reason, "in": resp.usage.input_tokens,
    "out": resp.usage.output_tokens})
```

Lire une trace d’un agent bloqué :

```text
corr_id=abc-123
 step 1  request_id=req_01A  stop_reason=tool_use   tools=[search_orders]        in=1,240 out=180
 step 2  request_id=req_01B  stop_reason=tool_use   tools=[get_order(id=9981)]   in=1,910 out=95
 step 3  request_id=req_01C  stop_reason=tool_use   tools=[get_order(id=9981)]   in=2,600 out=95   ← repeat
 step 4  request_id=req_01D  stop_reason=tool_use   tools=[get_order(id=9981)]   in=3,290 out=95   ← loop
 ...
 step N  request_id=req_01Z  stop_reason=max_tokens                              in=9,900 out=512  ← truncated
```

Diagnostic : la boucle répète le même appel d’outil et les tokens d’entrée grimpent à chaque tour — le harnais ne renvoie pas le **résultat** de l’outil, ou la terminaison est pilotée par autre chose que `stop_reason`. C’est les anti-patterns nº 1/nº 2 (terminaison en langage naturel et plafonds d’itérations) plutôt qu’un défaut du modèle. Le `max_tokens` à la fin est un symptôme en aval, pas la cause. Corrigez la boucle : après chaque `tool_use`, ajoutez le résultat de l’outil et continuez jusqu’à ce que `stop_reason` soit `end_turn`.

---

## 8.3 Reproducibility

Pour reproduire un problème de sortie du modèle, contrôlez chaque variable possible :

1. **Épinglez le snapshot de modèle exact** — pas un alias flottant.
2. **Réglez `temperature: 0`** là où c’est approprié pour minimiser la variance.
3. **Figez le prompt système et les outils** et rejouez les **mêmes messages** dans l’ordre.

```python
resp = client.messages.create(
    model="claude-sonnet-5",   # snapshot épinglé
    temperature=0,             # minimiser la variance
    max_tokens=512,
    system=FROZEN_SYSTEM_PROMPT,
    tools=FROZEN_TOOLS,
    messages=RECORDED_MESSAGES,  # rejeu exact
)
```

Le non-déterminisme signifie que la sortie n’est toujours pas identique octet par octet, mais l’épinglage + `temperature: 0` + un prompt figé rendent les problèmes bien plus reproductibles et les comparaisons équitables. Note : les harnais Fable 5.x / Opus 5 / Sonnet 5 doivent être **append-only** — éditer ou réordonner des tours antérieurs invalide les blocs de thinking ultérieurs, donc rejouez en ajoutant, jamais en réécrivant l’historique.

---

## 8.4 Eval basics and an eval harness

| Element | What it is |
| --- | --- |
| **Jeu de référence** | Entrées curées avec des sorties connues-bonnes |
| **Correspondance exacte** | Pour les sorties déterministes (labels, extractions) |
| **Rubrique** | Critères de notation explicites pour la sortie ouverte |
| **LLM-as-judge** | Un modèle/une session *différent* note la sortie par rapport à la rubrique |

:::danger[Auto-revue en même session]
Ne faites jamais noter au modèle sa propre sortie dans la même session — il conserve le biais de raisonnement qui l’a produite (anti-pattern nº 9). Utilisez une session séparée et idéalement un modèle différent comme juge.
:::

Un harnais minimal combinant correspondance exacte et un LLM-as-judge dans un **appel séparé** :

```python
import json
from anthropic import Anthropic

client = Anthropic()
GEN_MODEL = "claude-sonnet-5"      # système sous test
JUDGE_MODEL = "claude-opus-5"      # modèle différent, appel séparé

def generate(case):
    r = client.messages.create(
        model=GEN_MODEL, temperature=0, max_tokens=512,
        system="Extract the invoice total as JSON: {'total_cents': int}.",
        messages=[{"role": "user", "content": case["input"]}],
    )
    return r.content[0].text

def exact_match(pred, expected):
    try:
        return json.loads(pred).get("total_cents") == expected["total_cents"]
    except json.JSONDecodeError:
        return False  # un échec de parsing compte comme un raté, jamais passé silencieusement

RUBRIC = (
    "Score 1 if the answer is faithful to the source and correctly formatted, "
    "else 0. Return JSON: {'score': 0 or 1, 'reason': str}."
)

def llm_judge(case, pred):
    # Session/modèle séparé — aucun contexte partagé avec le générateur.
    r = client.messages.create(
        model=JUDGE_MODEL, temperature=0, max_tokens=256,
        system=RUBRIC,
        messages=[{"role": "user",
                   "content": f"SOURCE:\n{case['input']}\n\nANSWER:\n{pred}"}],
    )
    return json.loads(r.content[0].text)

def run(golden):
    results = []
    for case in golden:
        pred = generate(case)
        results.append({
            "id": case["id"], "segment": case["segment"],
            "exact": exact_match(pred, case["expected"]),
            "judge": llm_judge(case, pred)["score"],
        })
    return results
```

---

## 8.5 Regression tests in CI

Exécutez le jeu de référence en **CI** à chaque changement de prompt/modèle/config et faites échouer le build en cas de régression. Épinglez le modèle et `temperature: 0` pour des comparaisons stables.

```yaml
name: eval-regression
on:
  pull_request:
    paths: ["prompts/**", "src/**", "evals/**"]
jobs:
  eval:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with:
          python-version: "3.12"
      - run: pip install anthropic
      - name: Run golden-set eval
        env:
          ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
        run: python evals/run.py --golden evals/golden.jsonl --min-score 0.95
      # run.py sort avec un code non nul si le score global OU un score par segment tombe sous le seuil
```

---

## 8.6 Per-segment metrics

Rapportez les métriques **par segment** (par type de document, par langue, par intention). **La précision agrégée masque les défaillances par segment** (anti-pattern nº 10) — le piège d’éval le plus courant à l’examen.

| Segment | Cases | Accuracy | Verdict |
| --- | --- | --- | --- |
| **Overall (aggregate)** | 1 000 | **92 %** | Semble correct — trompeur |
| invoices | 600 | 98 % | Sain |
| receipts | 300 | 95 % | Sain |
| handwritten | 100 | **61 %** | Le vrai problème, caché par l’agrégat |

Les 92 % agrégés passent un contrôle naïf, pourtant les formulaires manuscrits échouent 4 fois sur 10. Contrôlez la CI sur le **pire segment**, pas la moyenne, et décomposez toujours les métriques avant de livrer.

---

## 8.7 Choosing the right eval method for the output type

Toutes les sorties ne se notent pas de la même façon. Choisir la mauvaise méthode de notation est un piège d’examen subtil.

| Output type | Best scoring method | Why |
| --- | --- | --- |
| Labels / classifications | **Correspondance exacte** (accuracy, F1 par classe) | Vérité de terrain déterministe |
| Extraction structurée | **Correspondance au niveau champ** par rapport au JSON attendu | Crédit partiel et diagnostic par champ |
| Prose ouverte | **Rubrique + LLM-as-judge** (session séparée) | Aucune chaîne unique correcte |
| Qualité comparative | **Paire A/B** (laquelle des deux est meilleure) | Humains/juges comparent plus fiablement qu’un score absolu |
| Récupération/ancrage | **Contrôles de citation/fidélité** | Attrape les affirmations non ancrées |

```python
# Paire A/B : demander à un juge séparé quelle réponse est meilleure, en randomisant l'ordre pour éviter le biais de position.
import random

def pairwise(judge_client, question, ans_a, ans_b):
    first, second = ("A", ans_a, "B", ans_b) if random.random() < 0.5 else ("B", ans_b, "A", ans_a)
    label1, text1, label2, text2 = first
    r = judge_client.messages.create(
        model="claude-opus-5", max_tokens=128, temperature=0,
        system="Reply with the label of the better answer: 'A' or 'B'. JSON: {'winner': 'A'|'B'}.",
        messages=[{"role": "user",
                   "content": f"Q:{question}\n\n{label1}:\n{text1}\n\n{label2}:\n{text2}"}])
    import json
    return json.loads("".join(b.text for b in r.content if b.type == "text"))["winner"]
```

:::tip[Signal d’examen]
« Labels déterministes » → correspondance exacte. « Qualité ouverte » → rubrique + LLM-as-judge dans une session séparée. « Quelle version est meilleure » → paire A/B. « Ancrage » → citation/fidélité. Associez la méthode au type de sortie.
:::

---

## 8.8 Debugging decision tree: which layer, which fix

Un flux de triage rapide transforme un vague « c’est cassé » en un correctif ciblé. Le statut HTTP et le `stop_reason` sont vos deux premiers signaux.

```text
Failure observed
   │
   ├─ HTTP 4xx/5xx?  ──► INTEGRATION layer
   │     ├─ 429 / 500 / 529 ──► transient: backoff + jitter, honour retry-after
   │     ├─ 400 invalid_request ──► fix request shape (e.g. forced tool on Fable 5.1)
   │     ├─ 401 / 403 ──► auth/permissions: fix key/org/scopes (do NOT retry)
   │     └─ timeout / JSON decode ──► timeouts/streaming; schema + validation-retry
   │
   └─ HTTP 200 but bad answer?  ──► MODEL-OUTPUT layer
         ├─ stop_reason == max_tokens ──► truncation: continue / raise cap / chunk
         ├─ stop_reason == refusal ──► safety decline: reframe / policy path
         ├─ off-schema-but-close ──► strict/structured outputs + validation-retry
         └─ confident but wrong ──► grounding: docs-first, citations, RAG, claim validation
```

| Symptom | Layer | Wrong fix (distractor) | Right fix |
| --- | --- | --- | --- |
| `429` sous charge | Intégration | Réécrire le prompt | Backoff + `retry-after` ; batch/file |
| `400` sur outil forcé (Fable 5.1) | Intégration | Réessayer avec backoff | `auto`/sorties structurées |
| JSON tronqué, `max_tokens` | Sortie du modèle | Faire tourner la clé d’API | Augmenter le plafond / continuer / découper |
| Valeur fabriquée | Sortie du modèle | Augmenter `temperature` | Ancrage + validation des affirmations |

:::tip[Signal d’examen]
Lisez d’abord le **code de statut** et le **`stop_reason`**. Un `429`/timeout/erreur JSON relève de l’intégration ; une hallucination/hors schéma/refus relève de la sortie du modèle. Appliquer un correctif d’intégration à un problème de sortie du modèle (ou vice-versa) est le distracteur classique.
:::

---

## 8.9 Common misconceptions

| Misconception | Reality | Why it matters on the exam |
| --- | --- | --- |
| Toute défaillance peut être réessayée | Seules les erreurs d’intégration transitoires (`429`/`5xx`/`529`) sont réessayables | Correctif de mauvaise couche |
| Un `400` pourrait être transitoire | Il est déterministe ; corrigez la requête | Distingue les couches |
| La précision agrégée suffit | Elle masque les défaillances par segment (anti-pattern nº 10) | Le principal piège d’éval |
| L’auto-revue en même session est efficace | Le juge hérite du biais du générateur (anti-pattern nº 9) | Piège de conception du juge |
| `temperature: 0` donne des octets reproductibles | Il réduit la variance ; épinglez aussi le snapshot | Nuance de reproductibilité |
| Une fin `max_tokens` est la cause racine d’une boucle | C’est un symptôme en aval ; corrigez la gestion de la boucle/`stop_reason` | Piège de lecture de trace |
| Une sortie vide signifie « aucun résultat » | Ce peut être une erreur supprimée silencieusement (anti-pattern nº 7) | Piège d’échec silencieux |
| La correspondance exacte convient à toute sortie | La sortie ouverte nécessite une rubrique/un juge LLM ; les comparaisons nécessitent une paire | Sélection de la méthode d’éval |

---

## 8.10 Scenario walkthrough: an eval that lied

**Scénario.** Un service de classification de documents rapporte **93 % de précision** sur son jeu de référence et passe la CI, pourtant les incidents de production impliquent sans cesse des **formulaires manuscrits** et des **documents non anglophones**. L’investigation révèle : (1) la CI ne contrôle que la précision agrégée ; (2) l’éval faisait noter par le modèle *générateur* ses propres justifications ouvertes dans la même session ; (3) un `400 invalid_request` intermittent en production est réessayé avec backoff exponentiel et ne réussit jamais ; et (4) un helper renvoie une liste vide quand un appel lève une exception, ce que l’appelant traite comme « aucune correspondance ». Corrigez l’éval et le débogage.

**Trace de raisonnement d’expert.**

1. **Cassez l’agrégat.** 93 % global cache que les segments manuscrits et non anglophones échouent gravement (**anti-pattern nº 10**). Rapportez des **métriques par segment** (par type de document et langue) et **contrôlez la CI sur le pire segment**, pas la moyenne.
2. **Corrigez le juge.** L’auto-notation en même session (**anti-pattern nº 9**) tamponne le générateur. Passez à un **LLM-as-judge dans une session séparée, idéalement un modèle différent**, par rapport à une rubrique explicite ; pour les justifications ouvertes, envisagez une **paire A/B** avec ordre randomisé.
3. **Corrigez le triage du `400`.** Un `400 invalid_request` est une erreur d’**intégration/schéma** et **déterministe** — réessayer avec backoff boucle indéfiniment. Lisez l’erreur : probablement un paramètre interdit (p. ex. `tool_choice` forcé sur Fable 5.1). Corrigez la forme de requête (`auto`/sorties structurées) ; ne réessayez pas.
4. **Corrigez la suppression silencieuse.** Renvoyer une liste vide sur exception (**anti-pattern nº 7**) rapporte l’échec comme un succès. Propagez l’erreur avec un contexte de diagnostic, ou échouez bruyamment ; ne laissez jamais un vide-comme-succès s’écouler en aval.
5. **Rendez-le reproductible.** Épinglez le snapshot de modèle et réglez `temperature: 0` pour l’éval ; journalisez `request_id`, `stop_reason`, `usage` et la latence pour que les incidents soient traçables.
6. **Rejetez les alternatives tentantes.** « Monter le seuil agrégé à 95 % » — masque toujours les défaillances par segment. « Réessayer le 400 plus agressivement » — erreur déterministe. « Faire confiance au résultat vide comme aucune correspondance » — suppression silencieuse. « Faire re-noter par le même modèle pour économiser » — réintroduit le biais.

**Décision correcte.** Métriques par segment avec contrôle CI sur le pire segment ; un juge LLM en session séparée/modèle différent (paire A/B pour l’ouvert) ; corriger le `400` à la couche de requête (pas de backoff) ; remonter bruyamment l’erreur supprimée ; snapshot épinglé + `temperature: 0` et journaliser les request ID pour la traçabilité.

---

## Pièges de l’examen dans ce domaine
| Piège | Pourquoi c’est faux |
| --- | --- |
| Réessayer une erreur de sortie du modèle comme une erreur réseau | Mauvaise couche ; corrigez le prompt/l’éval, pas le backoff |
| Traiter une erreur de schéma `400` comme transitoire | Elle est déterministe ; corrigez la forme de requête, ne réessayez pas |
| Auto-revue en même session | Anti-pattern nº 9 ; utilisez une session/un modèle de juge séparé |
| Ne rapporter que la précision agrégée | Anti-pattern nº 10 ; masque les défaillances par segment |
| Ne pas journaliser les request ID | Aucune prise de trace pour le débogage/support |
| Reproduire avec une température aléatoire et un modèle non épinglé | Non reproductible ; épinglez snapshot + température 0 |
| Traiter une sortie vide comme un succès | Suppression silencieuse (anti-pattern nº 7) |
| Aucune suite de régression en CI | Les changements de prompt/modèle régressent silencieusement |
| Juger avec le même modèle dans le même contexte | Biais ; utilisez une session/un modèle différent |
| Lire la troncature `max_tokens` comme la cause racine d’une boucle | C’est un symptôme en aval ; corrigez la gestion de la boucle/`stop_reason` |
| Monter le seuil agrégé au lieu de contrôler par segment | Masque toujours le segment défaillant ; contrôlez sur le pire segment |
| Utiliser la correspondance exacte pour une sortie ouverte | Utilisez une rubrique + LLM-as-judge (session séparée) ; paire A/B pour les comparaisons |
| Ne pas lire le code de statut et le `stop_reason` avant de choisir un correctif | Ils vous disent la couche ; le correctif de mauvaise couche est le distracteur classique |
| Ignorer le biais de position dans le jugement par paire | Randomisez l’ordre A/B pour que le juge ne soit pas influencé par la position |
| Traiter un `400` déterministe comme instable et réessayer | Corrigez la forme de requête ; le backoff bouclera indéfiniment |

---

## Questions d’entraînement
<Accordions>
  <AccordionItem title="Q1 · Un service échoue par intermittence avec des erreurs de décodage JSON et des 429, et séparément extrait parfois le mauvais total de facture. Comment l’équipe devrait-elle trier ? (Sélectionnez une réponse)">
    A. Traiter les deux comme des problèmes de modèle et réécrire le prompt.
    B. Séparer les couches : corriger les 429 avec backoff et les erreurs JSON avec validation-retry (intégration) ; corriger la mauvaise extraction avec du prompt/context engineering et de la validation (sortie du modèle).
    C. Traiter les deux comme des problèmes réseau et ajouter des retries.
    D. Augmenter `max_tokens` pour les deux.

    **Réponse : B.** Les défaillances sont dans des couches différentes et nécessitent des correctifs différents. Les réécritures de prompt généralisées (A) ignorent les erreurs d’intégration ; les retries (C) ne font rien pour la mauvaise extraction ; augmenter `max_tokens` (D) ne traite ni un 429 ni une mauvaise valeur.
  </AccordionItem>

  <AccordionItem title="Q2 · Un modèle obtient 91 % global à l’éval, mais un incident de production implique des formulaires manuscrits. Quelle pratique d’éval aurait fait remonter cela, et comment la sortie devrait-elle être jugée ? (Sélectionnez deux réponses)">
    A. Rapporter des métriques par segment (par type de document) au lieu du seul agrégat.
    B. Faire noter par la même session sa propre sortie.
    C. Utiliser un LLM-as-judge dans une session/un modèle séparé par rapport à une rubrique.
    D. Ne suivre que la précision globale.
    E. Sauter les évals en CI.

    **Réponse : A et C.** Les métriques par segment exposent l’échec des formulaires manuscrits (anti-pattern nº 10), et un juge en session séparée évite le biais d’auto-revue (anti-pattern nº 9). L’agrégat seul (D), la notation en même session (B) et l’absence de CI (E) sont les anti-patterns.
  </AccordionItem>

  <AccordionItem title="Q3 · Chaque requête à Claude Fable 5.1 renvoie `400 invalid_request` ; le code règle `tool_choice: {'type':'any'}`. De quelle couche s’agit-il et quel est le correctif ? (Sélectionnez une réponse)">
    A. Erreur de sortie du modèle ; réécrire le prompt pour être plus clair.
    B. Erreur d’intégration/schéma ; Fable 5.1 rejette `any`/`{type:tool}` forcé, donc utilisez `auto` avec une instruction, `strict:true`, ou les sorties structurées.
    C. Erreur de limite de débit ; ajouter un backoff exponentiel.
    D. Erreur transitoire ; réessayer avec jitter.

    **Réponse : B.** Un `400` sur un paramètre interdit est une erreur d’intégration/schéma déterministe propre à la restriction de choix d’outil de Fable 5.1 ; le correctif est de changer la requête. Il ne s’agit pas de la qualité du prompt (A) ; ce n’est pas un `429` (C) ; et réessayer un `400` déterministe (D) échouera toujours à nouveau.
  </AccordionItem>

  <AccordionItem title="Q4 · Une trace montre un agent appelant le même outil avec une entrée identique à chaque étape, avec des tokens d’entrée qui grimpent à chaque tour, se terminant par `stop_reason: max_tokens`. Quelle est la cause racine ? (Sélectionnez une réponse)">
    A. Le modèle a manqué de tokens de sortie ; augmenter `max_tokens`.
    B. Le harnais ne renvoie pas le résultat de l’outil et/ou la terminaison n’est pas pilotée par `stop_reason` ; corrigez la boucle pour ajouter les résultats et s’arrêter sur `end_turn`.
    C. Limitation de débit ; ajouter un backoff.
    D. Une hallucination ; ajouter des citations.

    **Réponse : B.** Répéter le même appel avec un contexte croissant est un défaut de contrôle de boucle (anti-patterns nº 1/nº 2). Le `max_tokens` final est un symptôme en aval, pas la cause (A). Ce n’est pas un problème de transport (C) ni d’ancrage (D).
  </AccordionItem>

  <AccordionItem title="Q5 · Une réponse revient avec `stop_reason: 'max_tokens'` et le JSON est coupé au milieu d’un objet. Quelle couche, et quelles sont DEUX récupérations valides ? (Sélectionnez deux réponses)">
    A. Troncature de sortie du modèle ; augmenter `max_tokens` pour l’appel.
    B. Découper la tâche ou streamer et continuer la génération.
    C. C’est une erreur d’auth ; faire tourner la clé d’API.
    D. Renvoyer silencieusement le JSON partiel comme un succès.
    E. Réessayer inchangé avec backoff.

    **Réponse : A et B.** `max_tokens` signifie que la sortie a été tronquée ; augmenter la limite ou découper/streamer le travail la récupère. Ce n’est pas de l’auth (C) ; renvoyer une sortie partielle comme un succès est une suppression silencieuse, anti-pattern nº 7 (D) ; réessayer inchangé (E) tronque à nouveau.
  </AccordionItem>

  <AccordionItem title="Q6 · Pour reproduire de manière fiable un bug de sortie du modèle, quelle combinaison l’équipe devrait-elle utiliser ? (Sélectionnez une réponse)">
    A. Le dernier alias de modèle flottant, température 1, prompt paraphrasé.
    B. Snapshot de modèle épinglé, `temperature: 0`, prompt système et outils figés, rejeu exact des messages.
    C. N’importe quel modèle, tant que le prompt est similaire.
    D. Un modèle différent à chaque exécution pour lisser le bruit.

    **Réponse : B.** Contrôler le snapshot de modèle, la température, le prompt, les outils, et l’historique des messages maximise la répétabilité. Les alias flottants et une température non nulle (A), les prompts lâches (C), et varier le modèle (D) injectent tous de la variance qui défait la reproduction.
  </AccordionItem>

  <AccordionItem title="Q7 · Un pipeline d’éval fait noter par le modèle générateur ses propres réponses dans la même conversation. Qu’est-ce qui ne va pas, et quel est le correctif ? (Sélectionnez une réponse)">
    A. Rien ; l’auto-notation est efficace.
    B. L’auto-revue en même session conserve le biais de raisonnement qui a produit la réponse ; exécutez un LLM-as-judge dans une session séparée, idéalement un modèle différent.
    C. Utiliser la correspondance exacte pour tout à la place.
    D. Ne noter que l’agrégat.

    **Réponse : B.** L’auto-revue en même session (anti-pattern nº 9) est biaisée car le juge partage le contexte du générateur. Un juge en session séparée, modèle différent supprime ce biais. La correspondance exacte (C) ne convient pas à une sortie ouverte ; la notation agrégée seule (D) est un anti-pattern différent.
  </AccordionItem>

  <AccordionItem title="Q8 · Quels artefacts devraient être journalisés pour chaque appel à Claude afin de permettre l’analyse de traces ? (Sélectionnez deux réponses)">
    A. `request_id` et un ID de corrélation.
    B. Le mot de passe de l’utilisateur.
    C. `model`, `stop_reason`, les tokens `usage`, la latence, et (pour les agents) la séquence d’outils.
    D. Rien, pour économiser le stockage.
    E. Seulement le texte de la réponse finale.

    **Réponse : A et C.** Les IDs de requête/corrélation plus le modèle, le stop reason, l’usage de tokens, la latence, et la séquence d’outils donnent une prise de trace complète pour le débogage et le support. Journaliser des secrets (B) est une violation de sécurité ; ne rien journaliser (D) ou seulement la réponse (E) vous rend aveugle pendant les incidents.
  </AccordionItem>

  <AccordionItem title="Q9 · Un job CI exécute le jeu de référence mais ne fait échouer le build que si la précision agrégée baisse. Une régression par segment sur les « documents juridiques » part en production. Que devrait faire la CI à la place ? (Sélectionnez une réponse)">
    A. Continuer à contrôler sur l’agrégat ; c’est plus simple.
    B. Contrôler sur le pire segment ainsi que sur l’agrégat, en échouant si un segment tombe sous son seuil.
    C. Retirer l’éval de la CI.
    D. N’exécuter les évals que mensuellement.

    **Réponse : B.** Contrôler sur des seuils par segment attrape les défaillances qu’un agrégat cache (anti-pattern nº 10). Le contrôle agrégat-seulement (A) est exactement ce qui a laissé passer la régression ; retirer (C) ou ralentir (D) les évals empire la situation.
  </AccordionItem>

  <AccordionItem title="Q10 · Une fonction renvoie une liste vide quand l’appel à Claude lève une exception, et l’appelant traite le vide comme « aucun résultat trouvé ». De quel anti-pattern s’agit-il et comment est-il corrigé ? (Sélectionnez une réponse)">
    A. Métriques agrégées ; ajouter un rapport par segment.
    B. Suppression silencieuse des erreurs (anti-pattern nº 7) ; remonter l’erreur avec un contexte de diagnostic au lieu de renvoyer un vide-comme-succès.
    C. Auto-revue en même session ; utiliser un juge séparé.
    D. Plafond d’itérations ; piloter depuis `stop_reason`.

    **Réponse : B.** Renvoyer un vide sur échec masque les erreurs et rapporte l’échec comme un succès — suppression silencieuse (anti-pattern nº 7). Le correctif est de propager l’erreur avec du contexte. Les autres options nomment des anti-patterns sans rapport.
  </AccordionItem>

  <AccordionItem title="Q11 · La sortie est presque du JSON valide mais émet occasionnellement un champ supplémentaire en fin, absent du schéma. Quelle couche, et quel est le correctif le plus robuste ? (Sélectionnez une réponse)">
    A. Timeout d’intégration ; ajouter des retries.
    B. Dérive de format de sortie du modèle ; imposer le schéma avec `strict: true` sur l’outil ou les sorties structurées, plus une validation-retry.
    C. Limite de débit ; ajouter un backoff.
    D. Erreur d’auth ; faire tourner la clé.

    **Réponse : B.** Une sortie hors schéma-mais-proche est une dérive de format, un problème de sortie du modèle ; `strict:true`/les sorties structurées plus la validation-retry imposent le schéma. Ce n’est pas un timeout (A), une limite de débit (C), ou un problème d’auth (D).
  </AccordionItem>

  <AccordionItem title="Q12 · Une équipe veut empêcher les changements de prompt et de modèle de dégrader silencieusement la qualité. Quelle pratique est essentielle ? (Sélectionnez une réponse)">
    A. Des vérifications ponctuelles manuelles quand quelqu’un s’en souvient.
    B. Une suite de régression sur un jeu de référence exécutée automatiquement en CI à chaque changement de prompt/modèle/config, avec des seuils par segment qui font échouer le build.
    C. Faire confiance à la confiance auto-déclarée du modèle.
    D. Ne mesurer que la latence.

    **Réponse : B.** La régression CI automatisée sur un jeu de référence avec contrôle par segment est la discipline qui attrape les chutes de qualité silencieuses. Les vérifications manuelles ad hoc (A) ne sont pas fiables ; la confiance auto-déclarée (C) est un anti-pattern ; la latence (D) ne mesure pas la qualité.
  </AccordionItem>

  <AccordionItem title="Q13 · Une éval note des justifications ouvertes. Quelle méthode de notation est appropriée, et laquelle ne l’est pas ? (Sélectionnez une réponse)">
    A. Correspondance exacte de chaîne par rapport à une réponse de référence unique.
    B. Une rubrique avec un LLM-as-judge dans une session séparée (et une paire A/B pour les comparaisons), pas la correspondance exacte.
    C. La précision agrégée uniquement.
    D. Le générateur se notant lui-même pour la vitesse.

    **Réponse : B.** La sortie ouverte n’a pas de chaîne unique correcte, donc utilisez une rubrique + un juge en session séparée, avec une paire A/B pour les comparaisons. La correspondance exacte (A) convient aux labels déterministes, pas à la prose ; l’agrégat seul (C) masque les segments ; l’auto-notation (D) est l’anti-pattern nº 9.
  </AccordionItem>

  <AccordionItem title="Q14 · Un appel de production renvoie par intermittence `400 invalid_request` et est réessayé avec backoff exponentiel, sans jamais réussir. Quel est le triage correct ? (Sélectionnez une réponse)">
    A. Ajouter plus de retries avec un backoff plus long.
    B. Reconnaître qu’un `400` est une erreur d’intégration/schéma déterministe (p. ex. un paramètre interdit comme `tool_choice` forcé sur Fable 5.1) ; corriger la forme de requête plutôt que de réessayer.
    C. Le traiter comme une hallucination du modèle et réécrire le prompt.
    D. Faire tourner la clé d’API.

    **Réponse : B.** Un `400` ne réussira jamais au retry ; lisez-le et corrigez la requête. Plus de backoff (A) boucle indéfiniment ; ce n’est pas un problème de sortie du modèle (C) ; la rotation d’auth (D) traite les `401`/`403`, pas un `400`.
  </AccordionItem>

  <AccordionItem title="Q15 · Une trace montre un agent répétant le même appel d’outil avec des tokens d’entrée croissants, se terminant par `stop_reason: max_tokens`. Quelle est la cause racine ? (Sélectionnez une réponse)">
    A. Le modèle a manqué de tokens de sortie ; augmenter `max_tokens`.
    B. Un défaut de contrôle de boucle : le harnais ne renvoie pas le résultat de l’outil et/ou la terminaison n’est pas pilotée par `stop_reason` ; corrigez la boucle. Le `max_tokens` final est un symptôme en aval.
    C. Limitation de débit ; ajouter un backoff.
    D. Une hallucination ; ajouter des citations.

    **Réponse : B.** Répéter le même appel avec un contexte croissant est un problème de contrôle de boucle (anti-patterns nº 1/nº 2) ; la fin `max_tokens` est un symptôme, pas la cause (A). Ce n’est pas du transport (C) ni de l’ancrage (D).
  </AccordionItem>

  <AccordionItem title="Q16 · Une éval CI ne contrôle que sur la précision agrégée (93 %), et une régression sur les formulaires manuscrits part. Quels DEUX changements corrigent le processus d’éval ? (Sélectionnez deux réponses)">
    A. Rapporter et contrôler sur des métriques par segment (par type de document), en échouant si un segment tombe sous son seuil.
    B. Utiliser un LLM-as-judge dans une session/un modèle séparé par rapport à une rubrique pour les parties ouvertes.
    C. Monter le seuil agrégé à 96 %.
    D. Faire noter par la même session sa propre sortie.
    E. N’exécuter les évals que mensuellement.

    **Réponse : A et B.** Le contrôle par segment fait remonter l’échec caché (anti-pattern nº 10) et un juge en session séparée évite le biais d’auto-revue (anti-pattern nº 9). Un agrégat plus élevé (C) masque toujours les segments ; l’auto-notation (D) est le nº 9 ; les évals mensuelles (E) ralentissent la détection.
  </AccordionItem>

  <AccordionItem title="Q17 · Un helper renvoie une liste vide quand l’appel à Claude lève une exception, et l’appelant traite le vide comme « aucune correspondance ». De quel anti-pattern s’agit-il et quel est le correctif ? (Sélectionnez une réponse)">
    A. Métriques agrégées ; ajouter un rapport par segment.
    B. Suppression silencieuse des erreurs (anti-pattern nº 7) ; propager l’erreur avec un contexte de diagnostic au lieu de renvoyer un vide-comme-succès.
    C. Auto-revue en même session ; utiliser un juge séparé.
    D. Plafond d’itérations ; piloter depuis `stop_reason`.

    **Réponse : B.** Renvoyer un vide sur échec rapporte l’échec comme un succès — suppression silencieuse (anti-pattern nº 7). Remontez l’erreur avec du contexte. Les autres nomment des anti-patterns sans rapport.
  </AccordionItem>

  <AccordionItem title="Q18 · En comparant deux versions de prompt avec un juge LLM, les résultats s’inversent selon la réponse affichée en premier. Quel est le problème et le correctif ? (Sélectionnez une réponse)">
    A. Le modèle juge est cassé ; changer de modèle.
    B. Biais de position dans le jugement par paire ; randomisez l’ordre A/B (et éventuellement moyennez les deux ordres) pour que la position ne décide pas du gagnant.
    C. La température est trop basse ; l’augmenter.
    D. Utiliser la correspondance exacte à la place.

    **Réponse : B.** Les juges par paire peuvent être influencés par la position de la réponse ; randomiser l’ordre (ou noter les deux ordres) supprime le biais. Ce n’est pas un modèle cassé (A) ; la température (C) n’est pas la cause ; la correspondance exacte (D) ne convient pas à une comparaison ouverte.
  </AccordionItem>
</Accordions>

## À retenir
- Triez par couche : les erreurs d’intégration (429/5xx/timeouts/JSON/rôles) reçoivent des retries et des correctifs de requête ; les erreurs de sortie du modèle reçoivent des changements de prompt/contexte/modèle et de la validation.
- Journalisez les request ID, le modèle, `stop_reason`, `usage` et la latence pour l’analyse de traces.
- Reproduisez avec un snapshot épinglé et `temperature: 0`, en acceptant le non-déterminisme résiduel.
- Évaluez avec un jeu de référence et une rubrique ; utilisez le LLM-as-judge dans une session *séparée* (jamais d’auto-revue en même session).
- Exécutez des tests de régression en CI et rapportez des métriques par segment – les agrégats cachent les défaillances qui comptent.
- Associez la méthode d’éval à la sortie : correspondance exacte pour les labels, correspondance au niveau champ pour l’extraction, rubrique + juge LLM en session séparée pour la prose, paire A/B (avec ordre randomisé) pour les comparaisons.
- Triez avec le code de statut et le `stop_reason` d’abord : `429`/timeout/JSON = intégration ; hallucination/hors schéma/refus = sortie du modèle — le correctif de mauvaise couche est le distracteur classique.
- Contrôlez la CI sur le pire segment, pas l’agrégat ; monter le seuil agrégé masque toujours un segment défaillant.
- Un `400` déterministe ne réussit jamais au retry — corrigez la forme de requête (p. ex. le choix d’outil forcé sur Fable 5.1) plutôt que d’ajouter un backoff.
