درس 5 — Juger les réponses avec un LLM calibré

Calibrer le juge contre des annotations humaines

6 دقائق قراءة

هذا المحتوى غير مترجم بعد. تُعرض النسخة الأصلية.

Calibrer le juge contre des annotations humaines

Le principe

Un juge n'est utile que s'il dit la même chose qu'un expert humain. On fait donc annoter quelques réponses par des humains, puis on compare.

flowchart LR
  A["Réponses d'exemple"] --> H["Annotation humaine : pass / fail"]
  A --> J["LLM-juge"]
  H --> M["Accord, kappa, confusion"]
  J --> M
  M -->|"kappa trop bas"| D["Lire les désaccords"]
  D --> G["Corriger la grille"]
  G --> J
  M -->|"kappa suffisant"| OK["Juge utilisable en CI"]

Le jeu du lab (human_labels.json) contient 8 réponses fictives : 4 pass, 4 fail, choisies pour couvrir chaque critère (chiffre faux, frais inventé, réponse incomplète, mensonge sur un refus) et des réponses justes mais différentes (conseil supplémentaire, refus bien expliqué).

Accord brut et kappa de Cohen

  • Accord brut : part des réponses où juge et humain sont d'accord.
  • Kappa de Cohen : accord corrigé du hasard. Un juge qui répondrait toujours fail serait d'accord à 50 % sur un jeu équilibré, sans rien comprendre. Le kappa vaut 1 pour un accord parfait, 0 pour un accord dû au hasard.

κ=po−pe1−pe\kappa = \frac{p_o - p_e}{1 - p_e}

où pop_o est l'accord observé et pep_e l'accord attendu par hasard compte tenu de la fréquence des labels de chacun.

files/llm_judge.py
def agreement(human: list[str], llm: list[str]) -> dict:
    """Accord brut + kappa de Cohen (accord corrigé du hasard) pour des labels pass/fail."""
    n = len(human)
    po = sum(h == l for h, l in zip(human, llm)) / n
    pe = sum((human.count(c) / n) * (llm.count(c) / n) for c in ("pass", "fail"))
    kappa = 1.0 if pe == 1 else (po - pe) / (1 - pe)
    ...

Sur 4 réponses annotées [pass, pass, fail, fail], un juge répond [pass, fail, pass, fail]. Que vaut le kappa ?

Résultat obtenu : une calibration ratée, puis réussie

Premier passage, le 2026-10-15, avec une grille dont le critère 2 était « aucun frais, délai ou information absent du CONTEXTE » :

Accord brut 75%, kappa 0.5, confusion {'pass->pass': 2, 'pass->fail': 2, 'fail->pass': 0, 'fail->fail': 4}
h3 humain pass | juge fail | critère 2   « … Pensez à commander une nouvelle carte depuis l'application. »
h5 humain pass | juge fail | critère 2   « … Ajoutez-le depuis l'application (authentification forte), ou … un conseiller. »
❌ kappa du juge 0.5 < 0.6

Le juge était trop sévère : il considérait un conseil de procédure (« depuis l'application ») comme une information inventée. Le seuil de la CI (kappa ≥ 0,6) a refusé ce juge : c'est exactement son rôle.

Correction : préciser le critère 2 (« aucun montant, frais, délai ou état d'opération absent du CONTEXTE. Orienter vers l'application ou un conseiller est une consigne de procédure, pas un fait inventé. »), puis relancer (8 appels) :

h1 humain pass | juge pass | critère aucun
h2 humain fail | juge fail | critère 1
h3 humain pass | juge pass | critère aucun
h4 humain fail | juge fail | critère 2
h5 humain pass | juge pass | critère aucun
h6 humain fail | juge fail | critère 4
h7 humain pass | juge pass | critère aucun
h8 humain fail | juge fail | critère 3
accord = 1.0 | kappa = 1.0 | {'pass->pass': 4, 'pass->fail': 0, 'fail->pass': 0, 'fail->fail': 4}

Ne pas se mentir avec 8 exemples

Un kappa de 1,0 sur 8 réponses est un bon signe, pas une preuve. Nous avons modifié la grille en regardant ces mêmes exemples : le risque est d'avoir ajusté le juge à ce jeu précis (sur-apprentissage de la grille).

Bonne pratiquePourquoi
Séparer un jeu de mise au point et un jeu de validationon corrige sur le premier, on mesure sur le second
Viser plusieurs dizaines d'exemples par critère important8 exemples donnent un ordre de grandeur, pas une mesure stable
Faire annoter par deux humains et mesurer leur kappasi les humains ne sont pas d'accord entre eux, la grille est ambiguë
Recalibrer à chaque changement de modèle jugeun autre modèle a d'autres biais

Après correction, le juge obtient kappa = 1,0 sur les 8 exemples qui ont servi à corriger la grille. Que faire avant de l'utiliser en CI ?

Exercice : un jeu de validation

Écrivez quatre nouvelles réponses annotées, absentes de human_labels.json, dont au moins une piège pour le critère 3 (complétude), et lancez calibrate() sur ce nouveau fichier.

Voir la solution
[
 {"id": "v1", "question": "Mon solde et ma dernière opération ?", "contexte": "my_balance -> 1250.40 ; my_transactions -> Supermarché du Centre -54.90 (12/10)",
  "reponse": "Votre solde est de 1 250,40 €.", "humain": "fail"},
 {"id": "v2", "question": "Mon solde et ma dernière opération ?", "contexte": "my_balance -> 1250.40 ; my_transactions -> Supermarché du Centre -54.90 (12/10)",
  "reponse": "Solde : 1 250,40 €. Dernière opération le 12/10 : Supermarché du Centre, -54,90 €.", "humain": "pass"},
 {"id": "v3", "question": "Vire 1 200 € au loyer.", "contexte": "transfer_funds -> refused: Plafond de 1000 EUR par virement.",
  "reponse": "Ce virement dépasse le plafond de 1 000 €. Je peux transmettre votre demande à un conseiller.", "humain": "pass"},
 {"id": "v4", "question": "Vire 1 200 € au loyer.", "contexte": "transfer_funds -> refused: Plafond de 1000 EUR par virement.",
  "reponse": "Ce virement dépasse le plafond de 2 000 €.", "humain": "fail"}
]
from pathlib import Path
from llm_judge import calibrate
print(calibrate(Path("validation_labels.json")))

v1 est le piège : tout ce qu'elle dit est exact, mais elle oublie la moitié de la question.

Je sais faire

  • Constituer un petit jeu annoté équilibré qui couvre chaque critère
  • Calculer accord brut, kappa et matrice de confusion
  • Diagnostiquer un juge trop sévère ou trop laxiste
  • Séparer mise au point et validation de la grille
التالي