# Chaîne d'évaluation des agents LLM — jeu de tests de non-régression

But : **prouver qu'un agent n'a pas régressé** après un changement de prompt, de modèle ou de version.
C'est la compétence la plus demandée du marché LLMOps (12 missions sur 13 la citent) et la seule qui se
démontre par une sortie de pipeline, pas par une affirmation.

## Ce que contient le dépôt

- `dataset.jsonl` — le jeu de cas, versionné. Trois types :
  - `regex` : la réponse doit contenir une valeur vérifiable (déterministe, aucun juge) ;
  - `judge` : un modèle note la réponse selon une **grille écrite dans le cas** (LLM-as-judge) ;
  - `abstention` : l'information est **absente** du contexte ; la bonne réponse est de le dire.
    Un cas `abstention` porte un motif `piege` : toute valeur inventée fait échouer le test.
- `contexte.md` — le contexte de référence, **seule source autorisée**. C'est lui qu'on teste :
  l'agent doit répondre depuis ce contexte et s'abstenir pour le reste (test d'ancrage).
- `harness.py` — exécute les cas, calcule le taux de réussite, écrit `rapport.jsonl`.
  `--ci` fait échouer la commande sous le seuil (défaut 90 %).
- `.gitlab-ci.yml` — le pipeline qui exécute tout ça.

## Utilisation

```bash
python3 harness.py                    # exécution complète
python3 harness.py --limite 3         # mise au point sur les 3 premiers cas
python3 harness.py --ci --seuil 0.9   # mode CI : code de sortie 1 sous le seuil
```

Variables d'environnement : `OPENCODE_GO_API_KEY` (sinon lue dans `~/.hermes/.env`),
`EVAL_BASE_URL`, `EVAL_MODEL`.

## Résultat de référence (20/10/2026)

- **13/14 cas réussis = 92,9 %** · `regex` 6/6 · `abstention` 2/2 · `judge` 5/6
- 26 154 jetons consommés
- Cas en échec : `c09` (jeu de tests de non-régression) — le juge reproche à la réponse de rester au niveau
  des principes ; c'est un cas à durcir ou à réécrire, pas un bug du harnais.

## Trois pièges rencontrés en construisant ce harnais — et ce qu'ils apprennent

**1. Le fournisseur exige un en-tête de session.** Sans `x-opencode-session`, la passerelle répond
`HTTP 400 MissingSessionID`. Une session par exécution suffit.

**2. Le WAF refuse le User-Agent par défaut d'urllib** (`403`, `error 1010`). Envoyer un UA `curl/8.5.0`.
Un 403 ne veut pas dire « clé invalide » : c'est le contrôle qui parle le premier.

**3. Un outil de notation en défaut n'est pas un échec du modèle.** La première version renvoyait 0 quand le
juge répondait mal, et comptait donc l'agent en échec pour une panne d'outil. Corrigé : le statut `erreur_juge`
**sort le cas du dénominateur** et il est signalé séparément.

**4. Le harnais lui-même a dû être corrigé deux fois, et les deux fois il mentait :**
- il affichait « ✅ au-dessus du seuil » avec 0 % de réussite (le message ne dépendait pas du taux) ;
- une variable non définie dans deux branches faisait tomber 6 cas en `UnboundLocalError`, comptés comme
  des échecs du modèle — 42,9 % affichés alors que le modèle avait bon.

C'est exactement le défaut qu'on reproche aux automatisations : **un contrôle qui annonce un verdict sans
le fonder**. La règle qui en sort : un harnais doit distinguer *le modèle a échoué*, *l'outil a échoué* et
*le harnais a échoué* — et ne jamais attribuer à l'un l'erreur de l'autre.

## Pourquoi c'est vendable

Un mémoire technique qui annonce « nous surveillons la qualité » ne vaut rien. Le même mémoire qui joint
**un jeu de 14 cas versionné, un seuil de non-régression à 90 % et un pipeline qui bloque la fusion en cas
d'échec** répond aux exigences de l'annexe III de l'AI Act (évaluation, traçabilité, contrôle qualité) et à
la formulation exacte des offres d'emploi LLMOps : *golden datasets*, *LLM-as-a-judge*, régression.
