Évaluations pour les GenAI et les agents : tests en une seule étape vs tests en plusieurs étapes dans le CI
Comparez les évaluations en une seule itération basées sur des traces avec les évaluations en plusieurs itérations pilotées par un simulateur, en abordant les référents d’or, les juges basés sur des LLM, les métriques d’agent, les portes de contrôle continue et sept pratiques de travail.
« Ça fonctionne avec mon prompt » n’est pas une norme d’ingénierie. Un test unitaire conventionnel réussit ou échoue de manière déterministe, mais les systèmes génératifs fournissent des réponses ouvertes et probabilistes. Un pipeline RAG peut être globalement correct, et un agent peut accomplir une tâche de manière à sembler réussie sur le papier tout en étant dangereux dans la pratique. C’est pourquoi les évaluations, contrairement au jugement subjectif ou à des canaux de discussion remplis d’avis du type « ça me semble bien », servent désormais de base à une ingénierie ML sérieuse pour l’IA générative et les systèmes agents. Ce qui suit aborde ce qu’il convient de mesurer, en quoi les évaluations à un tour et à plusieurs tours diffèrent, les compromis associés à chacune, ainsi que des modèles en Python adaptés aux pipelines CI.
Pourquoi les évaluations ne sont pas optionnelles
Trois caractéristiques des applications de LLM en font la nécessité :
- Non-déterminisme. Des prompts identiques peuvent produire des résultats différents selon les exécutions, les versions du modèle ou les valeurs de température.
- Faillite cumulative chez les agents. Alors qu’un chatbot classique ne peut se tromper qu’une seule fois, un agent enchaîne de 10 à 50 appels au modèle, appels d’outils et décisions. Un taux d’erreur modeste à chaque étape peut entraîner des échecs fréquents de bout en bout.
- Régressions silencieuses. Un léger ajustement du prompt, une mise à jour du modèle ou un réindexage d’un stock de vecteurs peut dégrader la qualité sans qu’aucune exception ne soit levée.
L’évaluation transforme l’expression « ça semble mieux » en un chiffre qui peut être suivi dans le temps, utilisé pour décider des déploiements et comparé entre les versions.
Deux formes d’évaluation
Presque tous les frameworks, parmi lesquels DeepEval, Ragas, LangSmith, Braintrust et OpenAI Evals, suivent l’un de ces deux schémas.
Un seul tour, basé sur les traces
Le processus est simple : prendre chaque trace, appeler l’application une fois, puis évaluer cette trace.
Open a test run with trace-level metrics
for each golden:
yield golden
call app with golden.input
capture trace
build a test case from the trace
score test case with metrics
upload test run (traces + scores)
Un golden est un échantillon étiqueté : l’entrée, la réponse qu’il doit produire, ainsi que souvent le contexte ou les appels d’outils qu’il doit impliquer. Le trace enregistre l’ensemble du déroulement d’une exécution, y compris quels fragments ont été récupérés, quels outils ont été utilisés, le raisonnement intermédiaire, la latence et le nombre de tokens, et pas seulement le texte final. Évaluer ce enregistrement plutôt que uniquement la réponse permet d’identifier l’étape où a eu lieu l’échec : la récupération des données, la génération ou un appel d’outil en cours de route ?
Multi-turn, basé sur un simulateur
C’est le modèle plus difficile et plus réaliste pour les agents et les assistants de conversation.
def simulate(conversational goldens)
for each golden:
loop until expected outcome OR max turns:
simulator model generates a user turn
ask app for next reply
capture assistant turn
build a ConversationalTestCase from the full transcript
evaluate(test cases, metrics) # metrics run concurrently
-> pass/fail + reason per metric
Ici, il n’existe pas de paire d’entrée et de sortie fixe. Un modèle de simulateur simule un utilisateur ayant un objectif, comme annuler une abonnement ou obtenir un remboursement pour une commande spécifique, et adapte chaque message aux réponses de l’assistant, tout comme le ferait une personne. Le système est évalué sur l’ensemble du parcours : reste-t-il concentré sur sa tâche ? Utilise-t-il correctement les outils au fil des échanges ? Résout-il finalement son objectif ? Invente-t-il des détails de politique dès le troisième échange ?
Les agents, les assistants à plusieurs étapes et tout système disposant de mémoire ou d’état ont besoin de ce type d’approche. Une évaluation sur un seul échange ne permet pas de détecter si un agent se contredit au quatrième échange ou utilise un outil cinq fois de suite en boucle.
Choix des métriques
Les métriques se divisent en quatre catégories, et il est judicieux de les combiner, car aucune ne donne une image complète.
Un modèle mental utile : les métriques à un seul tour donnent une image instantanée, tandis que les métriques agentes évaluent une trajectoire.
L’LLM en tant que juge
La plupart des métriques actuelles sans référence fonctionnent selon le principe de « LLM en tant que juge ». Un modèle puissant, souvent différent et plus performant que le modèle testé, évalue une sortie selon des critères prédéfinis et explique son score.
from dataclasses import dataclass
JUDGE_PROMPT = """
You are evaluating an AI assistant's response for FAITHFULNESS.
A response is faithful if every factual claim it makes is supported
by the provided context. It is unfaithful if it adds unsupported
claims, contradicts the context, or fabricates details.
Context:
{context}
Response:
{response}
Return JSON: {{"score": <0.0-1.0>, "reason": "<short explanation>"}}
"""
@dataclass
class TestCase:
input: str
actual_output: str
retrieval_context: list[str]
def faithfulness_metric(test_case: TestCase, judge_llm) -> dict:
prompt = JUDGE_PROMPT.format(
context="\n".join(test_case.retrieval_context),
response=test_case.actual_output,
)
result = judge_llm.generate(prompt, response_format="json")
return {"score": result["score"], "reason": result["reason"], "passed": result["score"] >= 0.7}
Les juges présentent des défis :
- Biais de préférence personnelle. Les modèles évaluent plus favorablement leurs propres résultats ou ceux produits dans leur propre style. Utilisez autant que possible un juge provenant d’une autre famille de modèles.
- Sensibilité aux instructions. De légères modifications dans la formulation des critères influencent les scores. Mettez les instructions destinées aux juges sous contrôle de version, tout comme du code.
- Costs et latence. L’utilisation de juges coûte également de l’argent. Lorsque le budget est limité, testez d’abord avec du trafic réel et ne jugez pas tout.
- Biais de longueur et de précision. Les juges ont tendance à privilégier des réponses plus longues et plus assurées, qu’elles soient justes ou non. Calibrez régulièrement leurs évaluations à l’aide d’un petit ensemble de données étiquetées par des humains.
L’usage en une seule étape dans la pratique
Un ensemble d’exemples idéal peut être aussi simple qu’une liste de dictionnaires contenant une entrée et une sortie attendue.
goldens = [
{
"input": "What's our refund policy for digital products?",
"expected_output": "Digital products are refundable within 14 days if unused.",
"expected_tools": [],
},
{
"input": "Cancel order #48213",
"expected_output": None, # open-ended, judged not matched
"expected_tools": ["lookup_order", "cancel_order"],
},
]
Le lanceur suit le cycle décrit ci-dessus : lancer un essai, parcourir chaque cas de test, appeler l’application, enregistrer les traces, évaluer et télécharger le résultat.
from deepeval import evaluate
from deepeval.metrics import FaithfulnessMetric, AnswerRelevancyMetric
from deepeval.test_case import LLMTestCase
def run_eval_suite(goldens, app_callback, metrics):
test_cases = []
for golden in goldens:
trace = app_callback(golden["input"]) # your traced LLM/RAG app
test_case = LLMTestCase(
input=golden["input"],
actual_output=trace.final_output,
retrieval_context=trace.retrieved_chunks,
expected_output=golden.get("expected_output"),
)
test_cases.append(test_case)
results = evaluate(test_cases, metrics) # runs metrics concurrently
return results
metrics = [FaithfulnessMetric(threshold=0.7), AnswerRelevancyMetric(threshold=0.7)]
results = run_eval_suite(goldens, my_rag_app, metrics)
Une affirmation transforme les scores en un seuil.
def assert_eval_gate(results, min_pass_rate=0.9):
passed = sum(1 for r in results if r.success)
pass_rate = passed / len(results)
assert pass_rate >= min_pass_rate, f"Eval pass rate {pass_rate:.2%}
below gate {min_pass_rate:.0%}"
Intégrez-le dans le pipeline de PR de la même manière que pytest est intégré. Si un changement dans les prompts ou la récupération de données fait chuter le niveau de fidélité en dessous du seuil, la fusion doit être bloquée et rien ne doit être déployé discrètement.
En pratique, les conversations multiples
Plutôt que d’une entrée fixe, le simulateur reçoit un profil d’utilisateur et un objectif, et il guide la conversation.
class ConversationSimulator:
def __init__(self, simulator_model, max_turns=8):
self.simulator_model = simulator_model
self.max_turns = max_turns
def simulate(self, golden, chatbot_callback):
transcript = []
user_turn = self.simulator_model.generate_opening(golden["scenario"])
for _ in range(self.max_turns):
transcript.append({"role": "user", "content": user_turn})
assistant_turn = chatbot_callback(transcript)
transcript.append({"role": "assistant", "content": assistant_turn})
if self.simulator_model.is_goal_met(golden["expected_outcome"], transcript):
break
user_turn = self.simulator_model.generate_next_turn(transcript, golden)
return transcript
La conversation résultante est ensuite évaluée à l’aide de métriques d’agentivité.
from deepeval.test_case import ConversationalTestCase, Turn
from deepeval.metrics import (
TaskCompletionMetric,
ToolCorrectnessMetric,
ConversationCompletenessMetric,
)
def build_conversational_test_case(transcript, tools_called):
turns = [Turn(role=t["role"], content=t["content"]) for t in transcript]
return ConversationalTestCase(turns=turns, tools_called=tools_called)
metrics = [
TaskCompletionMetric(threshold=0.8), # did it accomplish the user's goal?
ToolCorrectnessMetric(threshold=1.0), # right tools, right args, right order
ConversationCompletenessMetric(threshold=0.7), # did it address everything raised?
]
test_case = build_conversational_test_case(transcript, tools_called=agent_trace.tool_calls)
result = evaluate([test_case], metrics)
Les vérifications typiques au niveau de l’agent comprennent :
- Correctitude de l’appel à l’outil : l’outil approprié, avec les arguments justes, dans le bon ordre, ce qui va au-delà du simple fait que « quelque outil a été appelé ».
- Efficacité des étapes et détection des boucles : appels répétés à un même outil sans aucun progrès.
- Respect du rôle et des politiques définies : maintien du personnage et des contraintes définis tout au long de la conversation, et non seulement lors du premier échange.
- Récupération en cas d’erreur : tentative de réessai judicieuse après un appel à l’outil infructueux, plutôt que la production d’un résultat inventé.
- Transmission du contexte : mémorisation de ce que l’utilisateur a dit trois échanges auparavant.
Évaluation des compromis
| Un seul échange | Plusieurs échanges | |
|---|---|---|
| Coût | Un appel par message, peu coûteux et rapide |
Puisque les résultats obtenus après plusieurs tours sont bruités, interprétez-les de manière statistique. Exécutez l’ensemble d’épreuves N fois et examinez les distributions plutôt qu’un seul score.
Méthodes fiables
- Commencez par un petit ensemble d’entraînement propre de 30 à 100 exemples. Un ensemble bruité de 1 000 exemples est pire qu’un ensemble bien organisé de 50, car des étiquettes incorrectes altèrent le signal.
- Évaluez séparément la récupération et la génération dans RAG. La fidélité et le rappel contextuel répondent à des questions différentes, et les combiner masque la véritable cause du problème.
- Jugez à l’aide d’un modèle plus robuste et indépendant, et vérifiez de temps en temps ses scores par rapport aux évaluateurs humains. Même 20 à 30 exemples par mois permettent de détecter d’éventuelles déviations dans le jugement.
En résumé
Les évaluations pour l’IA générative ont commencé comme une note en bas de page, une cellule de notebook exécutée avant une démonstration. Les systèmes agents ont dépassé ce stade. Lorsque le logiciel peut agir à plusieurs reprises, appeler des outils et conserver un état d’une manche à l’autre, l’évaluation n’est plus une simple vérification de qualité. Celle-ci devient le principal outil pour comprendre le comportement réel du système.
Les évaluations en une seule manche basées sur le suivi offrent des contrôles de régression rapides et peu coûteux sur la qualité des résultats. Les évaluations en plusieurs manches utilisant des simulateurs fournissent l’analyse fiable pour déterminer si un agent se comporte comme prévu tout au long d’une interaction, et pour la plupart des agents en production, c’est cette analyse qui compte le plus. Concevez les deux types d’évaluations et automatisez-les tous deux. Ne faites confiance à aucun d’eux aveuglément, et laissez toujours un humain vérifier les résultats.