Évaluer un pipeline RAG
Objectifs
- Évaluer séparément le retrieval et la génération
- Implémenter des métriques automatiques de qualité RAG
- Construire un framework d’évaluation complet
Les deux axes d’évaluation
Un pipeline RAG a deux composants à évaluer indépendamment :
- Retrieval : les bons documents sont-ils trouvés ? (métriques de la leçon 10)
- Génération : la réponse est-elle fidèle aux documents et utile ?
Métriques de génération
Faithfulness (fidélité)
La réponse est-elle fidèle aux documents fournis ? Ne contient-elle pas d’hallucinations ?
from openai import OpenAI
client = OpenAI()
def evaluer_fidelite(
question: str,
reponse: str,
contextes: list[str]
) -> dict:
"""Évalue si la réponse est fidèle aux contextes."""
contexte_complet = "\n\n---\n\n".join(contextes)
response = client.chat.completions.create(
model="gpt-5.3",
messages=[{
"role": "user",
"content": (
f"Évalue la fidélité de cette réponse par rapport aux "
f"documents sources.\n\n"
f"Documents sources :\n{contexte_complet}\n\n"
f"Question : {question}\n\n"
f"Réponse : {reponse}\n\n"
f"Pour chaque affirmation dans la réponse, indique :\n"
f"- SUPPORTÉE : si elle est confirmée par les documents\n"
f"- NON SUPPORTÉE : si elle n'apparaît pas dans les documents\n"
f"- CONTREDITE : si elle contredit les documents\n\n"
f"Termine par un score global de 0 à 1."
)
}],
temperature=0
)
return {"evaluation": response.choices[0].message.content}
Answer Relevance (pertinence de la réponse)
La réponse répond-elle vraiment à la question posée ?
def evaluer_pertinence(question: str, reponse: str) -> float:
"""Score de pertinence de la réponse par rapport à la question."""
response = client.chat.completions.create(
model="gpt-5.3",
messages=[{
"role": "user",
"content": (
f"Sur une échelle de 0 à 1, à quel point cette réponse "
f"répond-elle à la question posée ?\n\n"
f"Question : {question}\n"
f"Réponse : {reponse}\n\n"
f"Critères :\n"
f"- 1.0 : réponse complète et précise\n"
f"- 0.7 : réponse partielle mais utile\n"
f"- 0.3 : tangentiellement liée\n"
f"- 0.0 : hors sujet\n\n"
f"Retourne uniquement le score numérique."
)
}],
temperature=0,
max_tokens=10
)
try:
return float(response.choices[0].message.content.strip())
except ValueError:
return 0.0
Context Precision
Les documents utilisés par le LLM sont-ils les bons ?
def evaluer_precision_contexte(
question: str,
contextes: list[str],
reponse: str
) -> dict:
"""Évalue quels contextes ont été utiles pour la réponse."""
resultats = []
for i, ctx in enumerate(contextes):
response = client.chat.completions.create(
model="gpt-5.3",
messages=[{
"role": "user",
"content": (
f"Ce document a-t-il été utile pour répondre "
f"à la question ?\n\n"
f"Question : {question}\n"
f"Document : {ctx[:500]}\n"
f"Réponse générée : {reponse}\n\n"
f"Réponds par OUI ou NON."
)
}],
temperature=0,
max_tokens=5
)
utile = "oui" in response.choices[0].message.content.lower()
resultats.append({"index": i, "utile": utile})
nb_utiles = sum(1 for r in resultats if r["utile"])
return {
"details": resultats,
"precision": nb_utiles / len(contextes) if contextes else 0
}
Framework d’évaluation complet
class EvaluateurRAG:
def __init__(self, pipeline_rag):
self.pipeline = pipeline_rag
self.client = OpenAI()
def evaluer_dataset(self, dataset: list[dict]) -> dict:
"""Évalue le pipeline sur un dataset de questions-réponses.
Chaque item : {question, reponse_attendue, docs_pertinents}
"""
resultats = {
"fidelite": [],
"pertinence": [],
"precision_contexte": [],
}
for item in dataset:
# Exécuter le pipeline
output = self.pipeline.repondre(item["question"])
reponse = output["reponse"]
contextes = [s["extrait"] for s in output["sources"]]
# Évaluer
pertinence = evaluer_pertinence(item["question"], reponse)
resultats["pertinence"].append(pertinence)
ctx_eval = evaluer_precision_contexte(
item["question"], contextes, reponse
)
resultats["precision_contexte"].append(ctx_eval["precision"])
# Moyennes
import numpy as np
return {
metrique: round(float(np.mean(scores)), 4)
for metrique, scores in resultats.items()
}
# Utilisation
dataset = [
{
"question": "Combien de jours de congés ai-je ?",
"reponse_attendue": "25 jours de congés payés par an",
"docs_pertinents": ["politique-conges"]
},
{
"question": "Puis-je télétravailler le lundi ?",
"reponse_attendue": "Oui, le télétravail est possible sauf mardi et jeudi",
"docs_pertinents": ["politique-teletravail"]
},
]
evaluateur = EvaluateurRAG(pipeline_rag)
metriques = evaluateur.evaluer_dataset(dataset)
print("Résultats :")
for k, v in metriques.items():
print(f" {k}: {v}")
Tableau de bord des métriques
| Métrique | Cible | Ce qu’elle mesure |
|---|---|---|
| Recall@5 | > 0.90 | Les bons docs sont-ils dans le top 5 ? |
| Precision@5 | > 0.70 | Les docs retournés sont-ils pertinents ? |
| Faithfulness | > 0.85 | La réponse est-elle fidèle aux sources ? |
| Answer Relevance | > 0.80 | La réponse répond-elle à la question ? |
| Context Precision | > 0.60 | Les contextes fournis sont-ils utiles ? |
Résumé
- Évaluez séparément le retrieval (recall, precision) et la génération (fidélité, pertinence)
- La fidélité détecte les hallucinations du LLM
- La pertinence vérifie que la réponse est utile
- Construisez un dataset de test et mesurez avant chaque changement