Méthode · IA
Évaluer un agent IA : des tests pour des réponses non déterministes
Cas de référence, critères vérifiables par code, LLM juge et suivi des régressions : comment savoir si un changement de prompt ou de modèle améliore vraiment le résultat.
Pourquoi les tests classiques ne suffisent pas
Un test unitaire compare une sortie à une valeur attendue. Une fonctionnalité fondée sur un LLM ne répond jamais deux fois exactement la même chose, et plusieurs réponses différentes peuvent être également bonnes. Pourtant, il faut bien savoir si une nouvelle consigne, un nouveau modèle ou un nouvel outil améliore le résultat ou le dégrade.
C’est le rôle d’une évaluation (« eval ») : un jeu de cas représentatifs, des critères mesurables, et un score qu’on compare d’une version à l’autre.
Constituer un jeu de cas
Commencer petit : vingt à cinquante cas bien choisis valent mieux que mille cas générés au hasard. Les puiser dans :
- les usages réels les plus fréquents (anonymisés) ;
- les cas limites : entrée vide, très longue, dans une autre langue, hors périmètre ;
- chaque bug constaté : un défaut corrigé devient un cas, pour ne pas revenir.
Exemple pour un résumé de demandes de support (organisation fictive) :
[
{
"id": "resume-sinistre-auto",
"entree": "Sujet : Bris de glace\n\nLe client signale un pare-brise fissuré après un gravillon sur autoroute...",
"attendu": {
"max_phrases": 3,
"doit_contenir": ["pare-brise"],
"ne_doit_pas_contenir": ["montant", "€"]
}
},
{
"id": "demande-hors-sujet",
"entree": "Sujet : Recette\n\nPouvez-vous me donner une recette de crêpes ?",
"attendu": { "categorie": "hors_perimetre" }
}
]Des critères vérifiables par code
Tout ce qui peut se vérifier par code doit l’être : c’est rapide, gratuit et reproductible. Longueur, format JSON valide, présence d’un mot clé, absence d’une donnée sensible, catégorie choisie dans une liste.
import json, re
def verifier(cas, sortie):
attendu, echecs = cas["attendu"], []
phrases = [p for p in re.split(r"[.!?]+\s", sortie.strip()) if p]
if "max_phrases" in attendu and len(phrases) > attendu["max_phrases"]:
echecs.append(f"{len(phrases)} phrases au lieu de {attendu['max_phrases']} max")
for mot in attendu.get("doit_contenir", []):
if mot.lower() not in sortie.lower():
echecs.append(f"mot attendu absent : {mot}")
for mot in attendu.get("ne_doit_pas_contenir", []):
if mot.lower() in sortie.lower():
echecs.append(f"mot interdit présent : {mot}")
return echecs
cas_liste = json.load(open("evals/cas.json"))
resultats = {cas["id"]: verifier(cas, generer(cas["entree"])) for cas in cas_liste}
reussis = sum(1 for e in resultats.values() if not e)
print(f"{reussis}/{len(cas_liste)} cas réussis") La fonction generer() appelle la fonctionnalité testée, avec exactement la configuration de production (consigne, modèle, paramètres).
Le LLM juge, pour le reste
Certains critères ne se vérifient pas par code : fidélité au texte source, pertinence, ton. On peut alors demander à un modèle d’évaluer la sortie, avec une grille précise :
Tu évalues le résumé d'une demande de support.
<demande>{entree}</demande>
<resume>{sortie}</resume>
Réponds à chaque question par OUI ou NON, puis justifie en une phrase :
1. Le résumé contient-il une information absente de la demande ?
2. Le résumé omet-il l'action attendue du client ?
3. Le ton est-il neutre et professionnel ? - Des questions fermées (oui / non) plutôt qu’une note de 1 à 10, plus stable d’une exécution à l’autre.
- Une justification courte, pour pouvoir contrôler le juge.
- Un étalonnage : sur une vingtaine de cas, comparer les verdicts du juge à ceux d’un humain avant de lui faire confiance.
Évaluer un agent, pas seulement une réponse
Pour un agent qui enchaîne des outils, la réponse finale ne dit pas tout. On évalue aussi le chemin :
- Résultat : la tâche est-elle accomplie ? Pour un agent de code, les tests du dépôt passent-ils après sa modification ?
- Outils : a-t-il appelé le bon outil, avec des arguments valides ? A-t-il évité une action interdite ?
- Coût : nombre de tours, jetons consommés, durée. Une version qui réussit autant mais en deux fois moins de jetons est meilleure.
Chaque cas s’exécute dans un environnement remis à zéro (copie du dépôt, organisation de test), pour que les essais ne s’influencent pas.
Intégrer l’évaluation au cycle de développement
- Mesurer la version actuelle : c’est la référence.
- Modifier une seule chose (consigne, modèle, outil, skill).
- Relancer l’évaluation, plusieurs fois si les résultats varient, et comparer cas par cas, pas seulement le score global.
- Garder le changement s’il améliore sans régression sur les cas critiques.
Lancée dans la CI à chaque modification de consigne, l’évaluation joue le rôle des tests de non-régression d’un code classique.