Aller au contenu principal
Leçon 9 sur 9 · Évaluation de prompts

Juge personnalisé, choix du modèle et évaluations en production

Leçon 9 sur 9 Mis à jour le 5 novembre 2026 Intermédiaire
Sur cette page
  1. La tâche : résumer pour des écoliers
  2. Le juge, écrit à la main
  3. Exécuter
  4. Choisir le modèle au coût par tâche réussie
  5. Corriger le prompt avant de changer de modèle
  6. Après le lancement : CI, lot, dérive
  7. À vous de jouer
  8. Quiz de fin de formation
  9. Bilan du cours
  10. Points clés à retenir

Dernière leçon, en deux temps. D’abord le dernier carnet du cours : écrire son propre juge, une fonction Python qui appelle Claude avec une grille à trois critères pour noter des résumés d’articles, branchée dans promptfoo comme une assertion ; le code est relu pour les modèles actuels, avec un verdict structuré à la place du préremplissage de 2024. Ensuite ce que le cours de 2024 ne couvrait pas et que Claude Academy enseigne aujourd’hui : choisir un modèle au coût par tâche réussie, corriger le prompt avant de changer de modèle, et faire vivre l’évaluation après le lancement, sur chaque pull request, en lot, et sur un échantillon du trafic réel. Le quiz de fin de formation clôt la leçon.

La tâche : résumer pour des écoliers

Le carnet veut transformer des articles Wikipédia longs et techniques (réseaux de neurones convolutifs, par exemple) en résumés courts pour un public d’école primaire. Huit articles servent d’entrées, trois prompts sont comparés, tous volontairement médiocres et sans exemples pour limiter les jetons :

# prompts.py
def basic_summarize(context):
    return f"Summarize this article {context['vars']['article']}"

def better_summarize(context):
    return f"Summarize this article for a grade-school audience: {context['vars']['article']}"

def best_summarize(context):
    return f"""
    You are tasked with summarizing long wikipedia articles for a grade-school audience.
    Write a short summary, keeping it as concise as possible.
    The summary is intended for a non-technical, grade-school audience.
    This is the article: {context['vars']['article']}"""

Les articles vivent dans des fichiers texte, trop longs pour le YAML ; promptfoo sait charger une variable depuis un fichier :

description: 'Summarization Evaluation'

prompts:
  - file://prompts.py:basic_summarize
  - file://prompts.py:better_summarize
  - file://prompts.py:best_summarize

providers:
  - id: anthropic:messages:claude-sonnet-5-5
    label: "Sonnet 5.5"

tests:
  - vars:
      article: file://articles/article1.txt
  - vars:
      article: file://articles/article2.txt
  # … jusqu'à article8.txt

defaultTest:
  assert:
    - type: python
      value: file://custom_llm_eval.py

Huit cas, c’est bien trop peu pour une évaluation réelle ; le carnet le redit, et la leçon 2 a donné la bonne taille.

Le juge, écrit à la main

La grille note trois critères de 1 à 5 : concision, exactitude par rapport à l’article, ton adapté à un écolier sans bagage technique. Chaque niveau est décrit (ce que vaut un 1, un 3, un 5), deux exemples complets de résumés notés sont fournis, et le seuil de réussite est une moyenne d’au moins 4,5. Le carnet décrivait le ton sur trois niveaux seulement (1, 2, 3) pour une échelle de 1 à 5 ; la version ci-dessous aligne les trois critères sur les niveaux 1, 3 et 5. Le carnet de 2024 obtenait le JSON du verdict en préremplissant la réponse par <json> et en coupant sur </json>, avec temperature=0 ; les deux techniques sont obsolètes (le préremplissage renvoie une erreur 400 depuis la génération 4.6, et la version d’API 2026-10-21 refuse temperature sur tous les modèles). Un schéma Pydantic et messages.parse() font mieux : le verdict revient typé, et la réflexion adaptative du juge raisonne avant de noter, ce que la documentation recommande.

# custom_llm_eval.py
import anthropic
from pydantic import BaseModel, Field

client = anthropic.Anthropic()
JUDGE = "claude-opus-5-5"   # un modèle différent de celui qui a écrit les résumés

class SummaryGrades(BaseModel):
    conciseness: int = Field(ge=1, le=5)
    accuracy: int = Field(ge=1, le=5)
    tone: int = Field(ge=1, le=5)
    explanation: str

RUBRIC = """Evaluate the following summary based on these criteria:
1. Conciseness (1-5) - is the summary as concise as possible?
   - 1: unnecessarily long, repetitions or irrelevant details.
   - 3: captures most key points but could be more focused.
   - 5: condenses the main ideas into a brief, focused text with nothing superfluous.
2. Accuracy (1-5) - is the summary completely accurate based on the article?
   - 1: significant errors or omissions that alter the meaning.
   - 3: some key points correct, minor inaccuracies.
   - 5: faithfully represents the article without errors.
3. Tone (1-5) - is the summary appropriate for a grade school student with no technical training?
   - 1: language or concepts too complex, technical or mature.
   - 3: mostly suitable, occasional terms that may be challenging.
   - 5: consistently simple, clear and engaging for young readers.
Also give a short explanation of your evaluation."""

def llm_eval(summary, article):
    response = client.messages.parse(
        model=JUDGE,
        max_tokens=4096,   # la réflexion adaptative du juge compte dans max_tokens
        messages=[{"role": "user", "content":
                   f"{RUBRIC}\n\n<original_article>{article}</original_article>\n\n<candidate_summary>{summary}</candidate_summary>"}],
        output_format=SummaryGrades,
    )
    grades = response.parsed_output
    avg = (grades.conciseness + grades.accuracy + grades.tone) / 3
    return avg, grades.explanation

def get_assert(output: str, context, threshold=4.5):
    article = context["vars"]["article"]
    score, explanation = llm_eval(output, article)
    return {"pass": score >= threshold, "score": score, "reason": explanation}

get_assert est la même fonction qu’à la leçon 7 ; la différence est qu’elle appelle un modèle au lieu de compter des mots. Le reason renvoyé est l’explication du juge, lisible dans le tableau de bord. Le carnet joignait à sa grille deux résumés d’exemple notés, l’un 5 partout, l’autre 1 en ton, 5 en exactitude et 3 en concision ; gardez-les dans votre version, ils calibrent le juge.

Exécuter

npx promptfoo@latest eval
npx promptfoo@latest view

L’exécution est plus longue : une requête pour produire chaque résumé, puis une pour le noter. Dans le carnet, best_summarize l’emportait, et la loupe montrait par exemple un résumé recalé pour un ton trop technique malgré une bonne exactitude. Les graphiques en tête du tableau de bord comparent la distribution des scores des trois prompts.

Choisir le modèle au coût par tâche réussie

Le carnet s’arrêtait là. Le cours de Claude Academy reprend la question posée à la leçon 1 avec un exemple chiffré, sur son extraction de trois champs dans cinquante rapports de projet, et un seuil d’exactitude de 90 %. Les cinq étapes :

  1. Fixer le seuil à partir du coût d’une erreur (ici, cinq minutes de vérification manuelle ; cinq erreurs par mois absorbables ; 90 %).
  2. Noter chaque modèle sur les mêmes cas avec le même prompt, pour que le modèle soit la seule variable.
  3. Lire le coût de chaque appel dans le bloc usage de la réponse (jetons d’entrée et de sortie), jamais en l’estimant : les tokéniseurs diffèrent d’un modèle à l’autre.
  4. Comparer au coût par tâche réussie : coût total du passage divisé par le nombre de tâches réussies, pas par le nombre d’appels, parce que les appels ratés sont facturés aussi.
  5. Prendre, parmi les modèles qui passent le seuil, le moins cher.
Modèle (démo d’août 2026)RéussitesCoût du passageCoût par tâche réussie
Haiku 4.542 sur 50 (84 %)0,0380 $0,0009 $
Sonnet 550 sur 500,0984 $0,0020 $
Opus 550 sur 500,2323 $0,0046 $

Haiku est le moins cher du tableau et n’est pas candidat : il ne passe pas le seuil. Sonnet l’emporte. Sur une tâche plus dure, le modèle le plus capable peut être le seul à passer, et c’est alors le bon choix. Les chiffres sont ceux de la démo, enregistrée en août 2026 sur les modèles d’alors ; la page des tarifs donne les prix du jour, et chaque résultat est lié au prompt et aux cas : on relance l’évaluation à chaque changement de l’un ou de l’autre.

Corriger le prompt avant de changer de modèle

Un score sous le seuil dit qu’un modèle rate trop de cas, pas si la faute est au modèle ou au prompt. On regarde le prompt d’abord, parce qu’un modèle plus gros renchérit chaque requête à venir, alors qu’une ligne de prompt se teste en quelques minutes. La méthode du cours :

  • Lire les échecs groupés par type de cas et par champ. Dans l’exemple, cinq des huit échecs de Haiku portaient sur les rapports sans section « risques » : le modèle inventait un risque chaque fois, parce que le prompt autorisait null sans dire quand l’employer.
  • Choisir les corrections parmi les techniques contre les réponses non étayées : permettre « je ne sais pas » (dire quoi renvoyer quand le document ne contient pas la réponse), citer avant de répondre (extraire la phrase qui étaye, puis répondre), renvoyer chaque affirmation au document, ajouter une passe de vérification, interdire les connaissances extérieures.
  • Relancer les mêmes cas, deux fois ou plus, et regarder toutes les colonnes : une correction peut en casser une autre, et un modèle peut répondre différemment d’une exécution à l’autre. Dans l’exemple, Haiku est passé de 84 % à 100 % sans perdre un seul cas, et devenait le choix le moins cher.
  • Changer de modèle seulement quand les modifications de prompt ne font plus bouger le score : le modèle manque alors d’une capacité.
Corriger le prompt avant de changer de modèle Cinq étapes : lire les échecs groupés par type de cas et par champ, choisir des corrections contre les réponses non étayées, les écrire dans le prompt, relancer tous les cas sur tous les modèles, vérifier chaque colonne ; on ne change de modèle que quand le score ne bouge plus. SOUS LE SEUIL Corriger le prompt avant de changer de modèle 1 Échecs les lire par type de rapport et par champ : 5 sur 8 sur les rapports sans risque 2 Choisir permettre « je ne sais pas » (null), citer avant de répondre 3 Écrire deux lignes dans le prompt ; le code ne parse que ce qui suit la citation 4 Relancer mêmes 50 cas, mêmes modèles, deux fois ou plus 5 Vérifier toutes les colonnes : rien de juste devenu faux ? tant que le score bouge ; quand il stagne sous le seuil, le modèle manque d'une capacité : changer de modèle Dans la démo d'août 2026, le plus petit modèle passe de 84 % à 100 % sans perdre un cas, et devient le choix le moins cher par tâche réussie.
D'après la leçon « Iterate the prompt before you upgrade the model » (Claude Academy, « Introduction to evals », 2026).

Après le lancement : CI, lot, dérive

L’évaluation continue de travailler après la mise en production, parce que deux choses changent : l’équipe modifie le prompt, et les entrées réelles finissent par ne plus ressembler aux cas testés. Quatre pratiques, toujours d’après le cours de Claude Academy.

Une évaluation rapide sur chaque pull request. Un workflow d’intégration continue exécute un sous-ensemble du jeu, assez petit pour finir pendant qu’on attend (dix cas sur cinquante, couvrant chaque type de rapport, une minute environ dans l’exemple), puis compare le score à un seuil (88 %, donc neuf cas sur dix), commente la pull request avec les résultats et fait échouer la vérification sous le seuil. Dans l’exemple, une pull request qui « simplifie » le prompt en retirant la règle du null et la citation tombe à 80 % : deux rapports sans risque reçoivent un risque inventé, et le commentaire nomme les cas cassés.

# .github/workflows/smoke-eval.yml (squelette)
on: pull_request
permissions:
  contents: read
  pull-requests: write
jobs:
  smoke-eval:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v7
      - uses: actions/setup-python@v7
        with:
          python-version: "3.12"
      - run: pip install anthropic
      - run: python eval.py --cases smoke.json --out results.json
        env:
          ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
      - run: python score.py results.json --threshold 0.88
        env:
          GH_TOKEN: ${{ github.token }}

Les deux scripts sont les vôtres : le premier exécute les cas de l’évaluation rapide et enregistre les résultats en JSON ; le second calcule le score, publie le commentaire sur la pull request (d’où la permission pull-requests: write et le jeton) et se termine en erreur sous le seuil, ce qui fait échouer la vérification. Les versions des actions sont celles du 5 novembre 2026 (checkout 7.0.1, setup-python 7.1.0).

Un contrôle requis. Une vérification rouge n’est qu’un avertissement tant qu’une règle de protection de branche n’en fait pas un contrôle requis avant fusion. La règle appartient à la branche, pas au workflow.

Le jeu complet en lot. À chaque changement de prompt ou de modèle, on relance tout le jeu, et comme personne n’attend ces réponses, on passe par l’API de lots (Message Batches) : la moitié du prix sur les jetons d’entrée et de sortie, des résultats en général dans l’heure, au plus tard sous vingt-quatre heures, après quoi les requêtes non traitées expirent sans être facturées. Deux changements dans le script : un custom_id unique par requête, parce que les résultats arrivent dans n’importe quel ordre (le cours joint l’identifiant du cas et le modèle), et la lecture de request_counts avant de noter, pour repérer un lot revenu incomplet.

batch = client.messages.batches.create(
    requests=[
        {
            "custom_id": f"{case['id']}_{model}",
            "params": {
                "model": model,
                "max_tokens": 1024,
                "messages": [{"role": "user", "content": case["prompt"]}],
            },
        }
        for model in MODELS
        for case in CASES
    ]
)

batch = client.messages.batches.retrieve(batch.id)
print(batch.request_counts)          # processing, succeeded, errored, canceled, expired
answers = {}
for result in client.messages.batches.results(batch.id):
    if result.result.type == "succeeded":
        answers[result.custom_id] = result.result.message

Seule l’évaluation rapide, qu’une fusion attend, reste sur client.messages.create(...). Et quand on compare des modèles, on ne mélange pas tarifs de lot et tarifs standard dans un même tableau.

Un échantillon du trafic réel chaque nuit. Le jeu complet ne connaît que les rapports qu’on avait en le construisant ; si un nouveau client envoie un format que rien ne couvre, la CI reste verte pendant que les réponses se dégradent. C’est la dérive. On prélève 1 à 5 % du trafic, on le fait noter par un juge LLM à partir du document, de la réponse et de la grille (le trafic réel n’a pas de réponses attendues), on lit soi-même quelques verdicts de temps en temps, et on n’alerte que sur une vraie chute : un score de nuit à plus de deux écarts-types sous la moyenne des nuits récentes. Chaque réponse jugée incorrecte devient un cas du jeu complet, avec sa réponse attendue écrite à la main : les cas venus d’échecs réels gardent le jeu proche de l’usage.

L'évaluation après le lancement Frise à quatre jalons : une évaluation rapide sur chaque pull request, un contrôle requis par protection de branche, le jeu complet en lot à moitié prix, un échantillon nocturne du trafic réel noté par un juge. PRODUCTION L'évaluation après le lancement PR Évaluation rapide 10 cas sur 50, une minute ; seuil 88 % ; commentaire sur la pull request, échec sous le seuil FUSION Contrôle requis règle de protection de branche : sans elle, une croix rouge n'est qu'un avertissement CHANGEMENT Jeu complet en lot API de lots : moitié prix, résultats sous 24 h, custom_id unique, request_counts avant de noter NUIT Échantillon réel 1 à 5 % du trafic, juge avec grille, alerte à plus de deux écarts-types, chaque échec devient un cas Deux choses changent après le lancement : le prompt, que l'équipe modifie, et les entrées réelles, qui finissent par ne plus ressembler aux cas testés (la dérive).
D'après la leçon « Evals in production: CI, batch, and drift » (Claude Academy, « Introduction to evals », 2026) et la page Message Batches API (documentation Claude).

À vous de jouer

Finir votre évaluation. Reprenez les vingt cas construits à la leçon 2, les notateurs choisis par champ, et faites tourner le tout dans promptfoo sur deux modèles. Relevez le score, le coût de chaque passage depuis les blocs usage (ou la colonne de coût de promptfoo), et le coût par tâche réussie. Décidez du modèle, par écrit, avec le seuil que vous aviez fixé.

Préparer la production. Choisissez cinq cas pour une évaluation rapide de pull request et fixez son seuil ; écrivez la grille du juge qui notera l’échantillon nocturne sans réponse attendue ; et notez où iront les échecs réels. Vous avez le plan complet d’une évaluation qui vit.

Quiz de fin de formation

1. Qu'est-ce qui distingue une évaluation de prompt d'un benchmark publié ?

Un benchmark compare les modèles entre eux sur des tâches génériques ; une évaluation mesure un couple prompt + modèle sur vos propres cas, avec des réponses attendues écrites à l’avance. Aucun benchmark n’a vu votre prompt.

2. Comment fixe-t-on le seuil d'exactitude, et quand regarde-t-on le prix ?

À partir de ce que coûte une mauvaise réponse et du nombre d’erreurs que l’équipe absorbe ; le prix ne compte qu’ensuite, entre les modèles qui passent le seuil, comparés au coût par tâche réussie.

3. Pourquoi comparer des ensembles plutôt que des chaînes pour la classification des réclamations ?

Parce qu’une réclamation peut relever de deux catégories et que leur ordre ne compte pas ; une égalité de chaînes échouerait sur une réponse juste écrite dans l’autre ordre. Une sortie structurée à valeurs autorisées supprime en plus les erreurs de forme.

4. Que renvoie une fonction `get_assert` à promptfoo ?

Un booléen (réussi ou non), un nombre (un score), ou un dictionnaire GradingResult avec pass, score et reason ; elle reçoit la sortie du modèle et un context qui contient les variables du test.

5. Qu'est-ce qui rend un juge LLM fiable ?

Une grille précise, les entrées dans des balises séparées, un verdict structuré avec une option « je ne sais pas », un modèle différent de celui qui a produit la sortie, et un contrôle : accord avec le code sur les cas qu’il sait noter, puis accord avec un humain sur un échantillon.

6. Quelles sont les quatre pratiques d'une évaluation en production ?

Une évaluation rapide sur chaque pull request avec un seuil ; un contrôle requis par protection de branche ; le jeu complet relancé en lot à moitié prix à chaque changement de prompt ou de modèle ; un échantillon nocturne du trafic réel noté par un juge, avec alerte sur une chute de plus de deux écarts-types et transformation de chaque échec en nouveau cas.

Bilan du cours

Des critères écrits, un jeu de cas qui ressemble à la production, un notateur par champ, du code partout où il suffit, un juge là où le texte est libre, une personne pour vérifier les deux ; puis un seuil, un coût par tâche réussie, et une évaluation qui tourne encore après le lancement. Le cours de 2024 apprenait à mesurer ; celui de 2026 apprend à décider avec la mesure. Pour continuer : Prompting en situations réelles pour les prompts que vous évaluerez, L’utilisation d’outils (tool use) pour les applications qui les entourent.

Sources officielles : 09_custom_model_graded_prompt_foo/lesson.ipynb (anthropics/courses, archivé) · Introduction to evals (Claude Academy, 2026 : leçons « When to use what model », « Iterate the prompt before you upgrade the model », « Evals in production ») · Message Batches API, Structured outputs et Pricing (documentation Claude) · About protected branches (GitHub Docs)

Points clés à retenir

  • Un juge personnalisé est une assertion python qui appelle Claude avec une grille et renvoie une moyenne, un seuil et l’explication ; en 2026, le verdict passe par un schéma, sans préremplissage ni temperature.
  • On choisit un modèle parmi ceux qui passent le seuil, au coût par tâche réussie, lu dans les blocs usage.
  • Avant de changer de modèle, on lit les échecs groupés, on corrige le prompt (dire quand répondre null, citer avant de répondre), on relance plusieurs fois.
  • En production : évaluation rapide sur chaque pull request, contrôle requis, jeu complet en lot à moitié prix, échantillon nocturne noté par un juge.
  • Chaque échec réel devient un cas du jeu : c’est ce qui garde l’évaluation proche de l’usage.

Crédits et licence — Adaptation française du cours « Prompt evaluations » du dépôt anthropics/courses (Anthropic), archivé en lecture seule le 15 septembre 2026 et publié sous licence CC BY-NC 4.0 ; cette adaptation, qui le traduit, le remanie, met son code à jour pour les modèles de novembre 2026 et y intègre les repères du cours « Introduction to evals » de Claude Academy, est diffusée sous la même licence.