Juge personnalisé, choix du modèle et évaluations en production
Sur cette page
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 :
- Fixer le seuil à partir du coût d’une erreur (ici, cinq minutes de vérification manuelle ; cinq erreurs par mois absorbables ; 90 %).
- Noter chaque modèle sur les mêmes cas avec le même prompt, pour que le modèle soit la seule variable.
- Lire le coût de chaque appel dans le bloc
usagede 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. - 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.
- Prendre, parmi les modèles qui passent le seuil, le moins cher.
| Modèle (démo d’août 2026) | Réussites | Coût du passage | Coût par tâche réussie |
|---|---|---|---|
| Haiku 4.5 | 42 sur 50 (84 %) | 0,0380 $ | 0,0009 $ |
| Sonnet 5 | 50 sur 50 | 0,0984 $ | 0,0020 $ |
| Opus 5 | 50 sur 50 | 0,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
nullsans 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é.
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.
À 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
pythonqui 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 nitemperature. - 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.