Notation par modele
Les trois types de notation
Pour evaluer les reponses de Claude, trois approches existent :
| Type | Description | Quand l’utiliser |
|---|---|---|
| Code | Verification automatique par programme | Reponses factuelles, formats stricts |
| Modele | Un autre appel AI evalue la reponse | Reponses ouvertes, qualite subjective |
| Humain | Un evaluateur humain note | Derniere validation, cas critiques |
La notation par code est la plus fiable quand elle s’applique (ex: “la reponse est-elle du JSON valide ?”). Mais pour des criteres comme “le code est-il bien commente ?” ou “l’explication est-elle claire ?”, on a besoin d’un notateur par modele.
Le principe
On utilise Claude lui-meme (ou un autre modele) comme evaluateur. On lui donne :
- La requete originale
- La reponse generee
- Les criteres d’evaluation
- L’instruction de produire un score et une justification
Definir les criteres d’evaluation
Pour notre assistant AWS, on definit trois criteres :
CRITERES_EVALUATION = """Evalue la reponse selon ces criteres :
1. FORMAT (0-3 points) : La sortie est-elle dans le format demande (Python/JSON/Regex) ?
Est-elle bien formatee et lisible ?
2. VALIDITE (0-4 points) : Le code/config genere est-il syntaxiquement correct ?
Fonctionnerait-il tel quel sans modification ?
3. ADEQUATION (0-3 points) : La reponse correspond-elle precisement a la demande ?
Tous les elements demandes sont-ils presents ?
Score total sur 10."""
Implementer grade_by_model()
import anthropic
import json
client = anthropic.Anthropic()
def grade_by_model(test_input, test_type, response):
"""
Utilise Claude pour noter une reponse.
Retourne un dict avec score, forces, faiblesses et raisonnement.
"""
grading_prompt = f"""Tu es un evaluateur strict de code AWS.
REQUETE ORIGINALE :
{test_input}
TYPE ATTENDU : {test_type}
REPONSE A EVALUER :
{response}
{CRITERES_EVALUATION}
Analyse la reponse et fournis :
1. Les points forts (forces)
2. Les points faibles (faiblesses)
3. Ton raisonnement detaille
4. Un score final sur 10
Reponds en JSON."""
grading_response = client.messages.create(
model="claude-sonnet-4-20250514",
max_tokens=1024,
temperature=0,
messages=[
{"role": "user", "content": grading_prompt},
{"role": "assistant", "content": "```json\n"}
],
stop_sequences=["```"]
)
try:
evaluation = json.loads(grading_response.content[0].text.strip())
return evaluation
except json.JSONDecodeError:
# Fallback si le JSON est invalide
return {
"forces": "Erreur d'evaluation",
"faiblesses": "Le notateur n'a pas produit de JSON valide",
"raisonnement": grading_response.content[0].text[:200],
"score": 5 # Score neutre par defaut
}
Integrer dans le pipeline
On remplace le score placeholder de run_test_case() :
def run_test_case(test_case, prompt_template):
"""
Execute un cas de test avec notation par modele.
"""
# Obtenir la reponse de Claude
response = run_prompt(test_case, prompt_template)
# Notation par modele (remplace le score=10 placeholder)
evaluation = grade_by_model(
test_input=test_case["input"],
test_type=test_case["type"],
response=response
)
return {
"input": test_case["input"],
"type": test_case["type"],
"context": test_case["context"],
"difficulty": test_case.get("difficulty", "non specifie"),
"response": response,
"score": evaluation.get("score", 0),
"forces": evaluation.get("forces", ""),
"faiblesses": evaluation.get("faiblesses", ""),
"raisonnement": evaluation.get("raisonnement", "")
}
Pipeline complet
Voici le code complet qui integre tout :
import anthropic
import json
from collections import defaultdict
client = anthropic.Anthropic()
CRITERES_EVALUATION = """Evalue la reponse selon ces criteres :
1. FORMAT (0-3 points) : La sortie est-elle dans le format demande ?
2. VALIDITE (0-4 points) : Le code est-il syntaxiquement correct ?
3. ADEQUATION (0-3 points) : La reponse correspond-elle a la demande ?
Score total sur 10."""
PROMPT_TEMPLATE = """Tu es un expert AWS. Genere du code propre et fonctionnel.
Contexte : {context}
Type de sortie attendu : {type}
Requete : {input}"""
def run_prompt(test_case, prompt_template):
prompt = prompt_template.format(
context=test_case["context"],
type=test_case["type"],
input=test_case["input"]
)
response = client.messages.create(
model="claude-sonnet-4-20250514",
max_tokens=1024,
temperature=0,
messages=[{"role": "user", "content": prompt}]
)
return response.content[0].text
def grade_by_model(test_input, test_type, response):
grading_prompt = f"""Tu es un evaluateur strict de code AWS.
REQUETE : {test_input}
TYPE ATTENDU : {test_type}
REPONSE A EVALUER :
{response}
{CRITERES_EVALUATION}
Reponds en JSON avec : forces, faiblesses, raisonnement, score (entier 1-10)."""
grading_response = client.messages.create(
model="claude-sonnet-4-20250514",
max_tokens=1024,
temperature=0,
messages=[
{"role": "user", "content": grading_prompt},
{"role": "assistant", "content": "```json\n"}
],
stop_sequences=["```"]
)
try:
return json.loads(grading_response.content[0].text.strip())
except json.JSONDecodeError:
return {"forces": "", "faiblesses": "Erreur parsing", "raisonnement": "", "score": 5}
def run_test_case(test_case, prompt_template):
response = run_prompt(test_case, prompt_template)
evaluation = grade_by_model(test_case["input"], test_case["type"], response)
return {
"input": test_case["input"],
"type": test_case["type"],
"response": response,
"score": evaluation.get("score", 0),
"forces": evaluation.get("forces", ""),
"faiblesses": evaluation.get("faiblesses", "")
}
def run_eval(dataset, prompt_template):
results = []
for i, test_case in enumerate(dataset):
print(f"[{i+1}/{len(dataset)}] {test_case['input'][:50]}...")
result = run_test_case(test_case, prompt_template)
results.append(result)
print(f" -> Score : {result['score']}/10")
avg_score = sum(r["score"] for r in results) / len(results)
print(f"\n{'='*50}")
print(f"Score moyen : {avg_score:.2f}/10")
return results, avg_score
# Lancer l'evaluation
with open("dataset.json", "r", encoding="utf-8") as f:
dataset = json.load(f)
results, avg_score = run_eval(dataset, PROMPT_TEMPLATE)
# Sauvegarder
with open("eval_results.json", "w", encoding="utf-8") as f:
json.dump(results, f, indent=2, ensure_ascii=False)
Analyser les resultats
# Score moyen par type
scores_par_type = defaultdict(list)
for r in results:
scores_par_type[r["type"]].append(r["score"])
print("Scores par type :")
for t, scores in sorted(scores_par_type.items()):
avg = sum(scores) / len(scores)
print(f" {t:10s} : {avg:.1f}/10 (n={len(scores)})")
# Cas les plus faibles
print("\nCas les plus faibles :")
worst = sorted(results, key=lambda r: r["score"])[:3]
for r in worst:
print(f" Score {r['score']}/10 : {r['input'][:60]}...")
print(f" Faiblesses : {r['faiblesses']}")
Points d’attention
Cout : chaque cas de test necessite maintenant deux appels API (un pour la reponse, un pour la notation). Sur 100 cas, ca fait 200 appels. Utilisez Haiku pour la notation si le budget est serré :
# Variante economique : Haiku pour la notation
def grade_by_model_haiku(test_input, test_type, response):
# Meme code mais avec model="claude-haiku-4-20250514"
...
Biais du notateur : un modele qui evalue ses propres reponses peut etre trop indulgent. Pour attenuer ce biais, soyez tres precis dans les criteres et demandez explicitement de chercher les faiblesses.
Reproductibilite : utilisez temperature=0 pour les deux appels (reponse et notation) afin d’obtenir des resultats stables.
Resume
Le pipeline d’evaluation est maintenant complet :
Dataset → run_prompt() → Reponse Claude → grade_by_model() → Score + Analyse
↓ ↓
Prompt a evaluer Criteres d'evaluation
Vous pouvez desormais :
- Mesurer objectivement la qualite d’un prompt
- Comparer deux versions sur les memes donnees
- Identifier les faiblesses specifiques (par type, par difficulte)
- Iterer avec confiance vers un prompt optimal