Leçon 4 — Méthodes d'évaluation

Évaluer un RAG et un agent

6 min de lecture

Un RAG, deux systèmes à mesurer

Quand un RAG répond mal, la question utile est : la bonne information était-elle dans le contexte ? Si non, c'est la recherche. Si oui, c'est la génération. Mesurer une seule note globale empêche de savoir quoi corriger.

flowchart LR
  Q["Question"] --> R["Recherche (top-k)"]
  R --> C["Contexte : k extraits"]
  C --> G["Génération (LLM)"]
  G --> A["Réponse"]
  R -. "Rappel du contexte, précision du contexte" .-> M1["Métriques de recherche"]
  C -. "Fidélité" .-> M2["Métriques de génération"]
  A -. "Pertinence de la réponse, justesse" .-> M2

Les métriques de RAGAS

L'article RAGAS (Es et al., 2023, arXiv 2309.15217) a popularisé des métriques de RAG évaluées par LLM, dont trois centrales :

MétriqueQuestion poséeComment c'est calculé (principe)
Fidélité (faithfulness)La réponse est-elle soutenue par le contexte ?On extrait les affirmations de la réponse, puis on vérifie chacune contre le contexte. Score = affirmations soutenues / total
Pertinence de la réponse (answer relevance)La réponse traite-t-elle la question posée ?On génère des questions à partir de la réponse et on mesure leur similarité avec la question d'origine
Pertinence du contexte (context relevance)Le contexte contient-il surtout de l'utile ?Proportion des phrases du contexte nécessaires pour répondre

Deux métriques de recherche, plus simples, se calculent sans LLM dès que vos cas indiquent les sources attendues :

  • Rappel@k : parmi les documents attendus, combien sont dans le top-k ?
  • Précision@k : parmi les k documents retrouvés, combien sont attendus ?
metriques_recherche.py
def rappel_at_k(retrouves, attendus, k=5):
    if not attendus:
        return None  # cas « sans réponse » : métrique non applicable
    return len(set(retrouves[:k]) & set(attendus)) / len(attendus)

def precision_at_k(retrouves, attendus, k=5):
    top = retrouves[:k]
    return len(set(top) & set(attendus)) / len(top) if top else 0.0

assert rappel_at_k(["conges-2026", "teletravail", "mutuelle"], ["conges-2026"]) == 1.0
assert precision_at_k(["conges-2026", "teletravail"], ["conges-2026"], k=2) == 0.5

Fidélité : une version minimale

fidelite.py
import json

PROMPT = """Contexte :
<contexte>{contexte}</contexte>
Réponse :
<reponse>{reponse}</reponse>

1. Découpe la réponse en affirmations factuelles atomiques.
2. Pour chacune, indique si elle est soutenue par le contexte.
Réponds uniquement en JSON : [{{"affirmation": "...", "soutenue": true}}]"""

def fidelite(appeler_llm, reponse, contexte):
    items = json.loads(appeler_llm(PROMPT.format(contexte=contexte, reponse=reponse)))
    if not items:
        return 1.0  # aucune affirmation factuelle (ex. un refus)
    return sum(i["soutenue"] for i in items) / len(items)

Le rappel@5 vaut 1,0 sur un cas, mais la fidélité vaut 0,4. Où chercher en priorité ?

Évaluer un agent : résultat et chemin

Un agent enchaîne des appels d'outils. Anthropic, dans « Building effective agents », insiste sur l'importance de tester largement en environnement contrôlé et de garder les agents simples et observables. Deux angles complémentaires :

AngleCe qu'on vérifieExemple (agent de support fictif)
Succès de tâcheL'état final est-il correct ?Le ticket est créé avec la bonne catégorie et le bon client
TrajectoireLe chemin est-il acceptable ?A vérifié l'identité avant de modifier l'abonnement, pas d'outil interdit, moins de 8 étapes
EfficacitéCombien d'étapes, de tokens, de secondes ?Coût moyen par tâche réussie
sequenceDiagram
  participant U as "Client"
  participant A as "Agent"
  participant I as "verifier_identite"
  participant T as "creer_ticket"
  U->>A: "Je veux changer d'offre"
  A->>I: "client_id=C-42"
  I-->>A: "ok"
  A->>T: "categorie=changement_offre"
  T-->>A: "ticket T-981"
  A-->>U: "Ticket T-981 créé"
eval_agent.py
def evaluer_trajectoire(etapes, cas):
    """etapes : liste de noms d'outils appelés, dans l'ordre."""
    verdicts = {}
    verdicts["outils_interdits"] = not (set(etapes) & set(cas.get("interdits", [])))
    verdicts["budget_etapes"] = len(etapes) <= cas.get("max_etapes", 10)
    # ordre imposé : chaque paire (avant, apres) doit être respectée
    for avant, apres in cas.get("ordre", []):
        ok = avant in etapes and apres in etapes and etapes.index(avant) < etapes.index(apres)
        verdicts[f"{avant}<{apres}"] = ok
    return verdicts

cas = {"interdits": ["supprimer_compte"], "max_etapes": 6,
       "ordre": [("verifier_identite", "modifier_offre")]}
print(evaluer_trajectoire(["verifier_identite", "modifier_offre", "creer_ticket"], cas))
# {'outils_interdits': True, 'budget_etapes': True, 'verifier_identite<modifier_offre': True}

Quels contrôles sont des invariants raisonnables pour un agent qui modifie un abonnement ?

Exercice : diagnostiquer trois cas RAG

Pour chaque cas, calculez le rappel@3 et dites où agir.

CasSources attenduesTop-3 retrouvéObservation sur la réponse
Ateletravail-2026teletravail-2024, mutuelle, conges-2026Cite 2 jours de télétravail (règle 2024)
Bconges-2026conges-2026, rtt, teletravail-2026Ajoute « et 5 jours de RTT » absent du contexte
Caucunemutuelle, rtt, conges-2026Invente une politique de vélo de fonction
Voir la solution
  • A : rappel@3 = 0 (le document 2026 n'est pas retrouvé, la version 2024 l'est). Action : recherche/index : retirer ou filtrer les documents périmés (métadonnée de validité), vérifier le découpage du document 2026.
  • B : rappel@3 = 1,0. La réponse ajoute une affirmation non soutenue : fidélité faible. Action : génération : consigne « n'affirme rien qui ne figure pas dans le contexte », et cas ajouté au jeu doré avec interdit: ["RTT"].
  • C : rappel non applicable (aucune source attendue). L'assistant aurait dû répondre qu'il ne sait pas. Action : génération + garde-fou : consigne explicite de refus quand le contexte ne contient pas la réponse ; éventuellement seuil de score de similarité sous lequel on répond « je ne trouve pas cette information ».

Je sais faire

  • Séparer les métriques de recherche et de génération d'un RAG.
  • Calculer rappel@k et précision@k à partir des sources attendues.
  • Expliquer le principe de la fidélité selon RAGAS et l'implémenter simplement.
  • Évaluer un agent par son état final et par des invariants de trajectoire.
Suivant