Aller au contenu principal

É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 :

  1. Retrieval : les bons documents sont-ils trouvés ? (métriques de la leçon 10)
  2. 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étriqueCibleCe qu’elle mesure
Recall@5> 0.90Les bons docs sont-ils dans le top 5 ?
Precision@5> 0.70Les docs retournés sont-ils pertinents ?
Faithfulness> 0.85La réponse est-elle fidèle aux sources ?
Answer Relevance> 0.80La réponse répond-elle à la question ?
Context Precision> 0.60Les 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