Aller au contenu principal

Notation par modèle

Mis à jour le 30 juillet 2026

Les trois types de notation

Le choix du notateur détermine ce que votre évaluation est capable de mesurer, et il vaut mieux le faire dans l’ordre du tableau ci-dessous. Chaque fois qu’un critère peut se vérifier par du code — le JSON est-il valide, la réponse contient-elle le champ attendu, fait-elle moins de 200 mots — écrivez le code : c’est gratuit, instantané et sans ambiguïté. Le notateur par modèle ne se justifie que pour ce qu’aucun test ne sait trancher.

TypeDescriptionQuand l’utiliser
CodeVérification automatique par programmeRéponses factuelles, formats stricts
ModèleUn autre appel AI évalue la réponseRéponses ouvertes, qualité subjective
HumainUn évaluateur humain noteDernière validation, cas critiques

La notation par code est la plus fiable quand elle s’applique (ex: “la réponse est-elle du JSON valide ?”). Mais pour des critères comme “le code est-il bien commenté ?” ou “l’explication est-elle claire ?”, on a besoin d’un notateur par modèle.

Le principe

L’idée surprend au premier abord — demander à un modèle de juger un modèle — mais la tâche n’est pas la même des deux côtés. Produire une bonne réponse est difficile ; reconnaître si une réponse remplit trois critères explicites l’est beaucoup moins. C’est cette asymétrie qui rend la notation automatique fiable, à condition que les critères soient réellement explicites.

  • La requête originale
  • La réponse générée
  • Les critères d’évaluation
  • L’instruction de produire un score et une justification

Définir les critères d’évaluation

Pour notre assistant AWS, on définit trois critères :

CRITERES_EVALUATION = """Évalue la réponse selon ces critères :

1. FORMAT (0-3 points) : La sortie est-elle dans le format demandé (Python/JSON/Regex) ?
   Est-elle bien formatée et lisible ?

2. VALIDITÉ (0-4 points) : Le code/config généré est-il syntaxiquement correct ?
   Fonctionnerait-il tel quel sans modification ?

3. ADÉQUATION (0-3 points) : La reponse correspond-elle précisément à la demande ?
   Tous les elements demandes sont-ils presents ?

Score total sur 10."""

Implémenter grade_by_model()

import anthropic
import json

client = anthropic.Anthropic()

def grade_by_model(test_input, test_type, response):
    """
    Utilise Claude pour noter une réponse.
    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}

RÉPONSE À ÉVALUER :
{response}

{CRITERES_EVALUATION}

Analyse la réponse et fournis :
1. Les points forts (forces)
2. Les points faibles (faiblesses)
3. Ton raisonnement detaille
4. Un score final sur 10

Réponds en JSON."""

    grading_response = client.messages.create(
        model="claude-sonnet-5",
        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'évaluation",
            "faiblesses": "Le notateur n'a pas produit de JSON valide",
            "raisonnement": grading_response.content[0].text[:200],
            "score": 5  # Score neutre par défaut
        }

Intégrer dans le pipeline

On remplace le score placeholder de run_test_case() :

def run_test_case(test_case, prompt_template):
    """
    Exécute un cas de test avec notation par modele.
    """
    # Obtenir la réponse 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 spécifié"),
        "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 intègre tout :

import anthropic
import json
from collections import defaultdict

client = anthropic.Anthropic()

CRITERES_EVALUATION = """Évalue la réponse selon ces critères :

1. FORMAT (0-3 points) : La sortie est-elle dans le format demandé ?
2. VALIDITÉ (0-4 points) : Le code est-il syntaxiquement correct ?
3. ADÉQUATION (0-3 points) : La reponse correspond-elle a la demande ?

Score total sur 10."""

PROMPT_TEMPLATE = """Tu es un expert AWS. Génère du code propre et fonctionnel.

Contexte : {context}
Type de sortie attendu : {type}

Requête : {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-5",
        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}

RÉPONSE À ÉVALUER :
{response}

{CRITERES_EVALUATION}

Réponds en JSON avec : forces, faiblesses, raisonnement, score (entier 1-10)."""

    grading_response = client.messages.create(
        model="claude-sonnet-5",
        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'évaluation
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 résultats

# 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

Coût : chaque cas de test nécessite maintenant deux appels API (un pour la réponse, un pour la notation). Sur 100 cas, ça fait 200 appels. Utilisez Haiku pour la notation si le budget est serré :

# Variante économique : Haiku pour la notation
def grade_by_model_haiku(test_input, test_type, response):
    # Meme code mais avec model="claude-haiku-4-5"
    ...

Biais du notateur : un modèle qui évalue ses propres réponses est structurellement indulgent — il a produit ce texte parce qu’il le jugeait bon, et on lui demande ensuite de le juger. Deux parades fonctionnent : des critères assez précis pour ne laisser aucune marge d’appréciation, et l’exigence d’une justification avant le score, qui oblige à motiver plutôt qu’à valider par réflexe. Pour atténuer ce biais, soyez très précis dans les critères et demandez explicitement de chercher les faiblesses.

Reproductibilité : utilisez temperature=0 pour les deux appels (réponse et notation) afin d’obtenir des résultats stables.

Résumé

Le pipeline d’évaluation est maintenant complet :

Dataset → run_prompt() → Réponse Claude → grade_by_model() → Score + Analyse
                              ↓                                      ↓
                        Prompt à évaluer                    Critères d'évaluation

Vous pouvez désormais :

  1. Mesurer objectivement la qualité d’un prompt
  2. Comparer deux versions sur les mêmes données
  3. Identifier les faiblesses spécifiques (par type, par difficulté)
  4. Itérer avec confiance vers un prompt optimal