Aller au contenu principal

Évaluations notées par code

Mis à jour le 28 juillet 2026

Le principe

La notation par code consiste à écrire des fonctions qui comparent automatiquement la sortie du modèle à la réponse attendue. C’est l’approche la plus rapide et la plus déterministe des trois : elle ne coûte rien en appels API supplémentaires, elle s’exécute en quelques millisecondes et elle rend toujours le même verdict sur la même entrée. Cette dernière propriété est décisive quand vous cherchez à savoir si votre prompt s’est amélioré : le seul élément qui varie entre deux exécutions doit être le prompt, jamais le juge.

Le prix de cette rigueur, c’est l’étroitesse. Un notateur par code ne sait vérifier que ce que vous avez su exprimer en instructions. Il ne dira jamais si un résumé est agréable à lire — mais il dira sans faillir s’il fait moins de trois cents caractères et s’il contient le nom du client.

Choisir sa comparaison

Le premier réflexe est la correspondance exacte, qui vérifie que la sortie est identique à la référence après normalisation de la casse et des espaces.

def grade_exact(model_output, expected):
    return model_output.strip().lower() == expected.strip().lower()

Elle est utile pour les classifications, les réponses oui/non et les valeurs numériques — partout où il n’existe qu’une seule bonne réponse et où sa forme est imposée. Le strip() et le lower() ne sont pas de la coquetterie : sans eux, un simple retour à la ligne ferait échouer une réponse parfaitement correcte.

Quand la réponse est libre mais doit mentionner quelque chose de précis, on passe à la correspondance partielle.

def grade_contains(model_output, expected_substring):
    return expected_substring.lower() in model_output.lower()

C’est le bon outil pour vérifier la présence de mots-clés : que la réponse d’un assistant juridique cite bien l’article invoqué, par exemple, quelle que soit la phrase qui l’entoure. Attention toutefois au revers : une réponse qui contient le mot attendu au milieu d’un contresens passera le test.

L’expression régulière monte d’un cran en précision.

import re

def grade_regex(model_output, pattern):
    return bool(re.search(pattern, model_output))

Elle vérifie des formats — une adresse e-mail, une date, la présence d’un bloc JSON — plutôt qu’un contenu figé. Un notateur de ce type valide que le modèle a produit une référence client de la forme attendue sans avoir à connaître à l’avance laquelle.

La vérification structurelle, enfin, ne se contente pas de regarder le texte : elle le parse.

import json

def grade_json(model_output):
    try:
        data = json.loads(model_output)
        return "nom" in data and "email" in data
    except json.JSONDecodeError:
        return False

C’est l’approche indispensable pour les sorties structurées, JSON, CSV ou XML. Remarquez le try/except : une sortie qui n’est pas du JSON valide échoue proprement au lieu de faire tomber toute votre campagne d’évaluation. Un notateur qui plante est un notateur qui ne note rien.

Assembler un pipeline complet

Ces briques prennent leur sens une fois réunies dans une boucle qui interroge le modèle, note chaque réponse et calcule un score global. L’exemple ci-dessous traite une classification de sentiment sur trois cas.

import anthropic

client = anthropic.Anthropic()

test_cases = [
    {"input": "Le service était excellent", "expected": "POSITIF"},
    {"input": "Tres decu, produit defectueux", "expected": "NEGATIF"},
    {"input": "Commande recue, rien a signaler", "expected": "NEUTRE"},
]

prompt_template = """Analyse le sentiment de ce texte.
Réponds uniquement par POSITIF, NEGATIF ou NEUTRE.
<texte>{input}</texte>"""

correct = 0
for test in test_cases:
    response = client.messages.create(
        model="claude-sonnet-5",
        max_tokens=50,
        messages=[{"role": "user", "content": prompt_template.format(**test)}]
    )
    output = response.content[0].text.strip()
    if output == test["expected"]:
        correct += 1
    else:
        print(f"ECHEC : '{test['input']}' -> '{output}' (attendu : '{test['expected']}')")

print(f"Score : {correct}/{len(test_cases)} ({100*correct/len(test_cases):.0f}%)")

Deux détails font toute la différence à l’usage. Le print de la branche d’échec affiche l’entrée, la sortie obtenue et la sortie attendue : c’est ce triplet qui vous permet de diagnostiquer sans rouvrir vos fichiers. Et le score final, exprimé en fraction et en pourcentage, est la valeur que vous noterez avant de toucher au prompt, pour pouvoir comparer ensuite.

Faire vivre l’évaluation

Commencez avec un petit jeu de test, une dizaine de cas suffit pour démarrer, et élargissez progressivement à mesure que vous découvrez les faiblesses du prompt. Incluez délibérément des cas limites et des cas adversariaux : l’avis sarcastique qui dit du bien en pensant du mal, la facture illisible, le message vide. Automatisez ensuite l’exécution dans un script ou un pipeline CI/CD, pour que l’évaluation tourne sans qu’on ait à y penser. Conservez enfin un historique des scores : une courbe qui redescend après six semaines d’améliorations vous apprend plus que n’importe quelle relecture manuelle.

Points clés à retenir

  • La notation par code est rapide, gratuite et déterministe : le juge ne bouge pas d’une exécution à l’autre.
  • Correspondance exacte, correspondance partielle, expression régulière et parsing structurel couvrent la majorité des besoins objectifs.
  • Normalisez toujours casse et espaces, et protégez le parsing par un try/except.
  • Affichez systématiquement entrée, sortie obtenue et sortie attendue pour chaque échec.
  • Mesurez un score de base, élargissez le jeu de test avec des cas limites et suivez l’évolution dans le temps.