[!NOTE] Traduction automatique Cet article a été traduit automatiquement depuis la version originale en anglais.

Évaluation de RAG : Métriques pour chaque étape d’un système RAG en production

Première partie de la série de production RAG

Un système RAG présentant des filtres défectueux peut fonctionner pendant des mois sans déclencher d’alerte opérationnelle. Il continue de fournir des réponses et de respecter ses objectifs de latence, mais ces réponses s’appuient sur des preuves incomplètes. Une analyse par rappel@k à l’aide du jeu de données d’or initial permet de mettre en évidence cette perte de qualité, ce que les tableaux de bord relatifs à la latence et à la disponibilité ne font pas.

L’évaluation ne peut détecter une défaillance que si chaque étape pipeline dispose de sa propre métrique. Cet article établit un lien entre les modes de défaillance courants et ces métriques, allant de la parsing des documents jusqu’à la surveillance en production.

!!! astuce « Voulez-vous sauter directement à l’exécution du code ? »

L’exécutable [`slavadubrov/rag-evals-demo`](https://github.com/slavadubrov/rag-evals-demo) Le repository applique ces métriques à SciFact. `make eval` exécute le jeu d’outils, et `make benchmark` Il compare les méthodes de segmentation en chunks, embedding, ainsi que les configurations LLM. Les notebooks 00 à 09 présentent séparément chacune de ces métriques. La démonstration fait appel à Qdrant intégré, ce qui évite l’utilisation de Docker.

Résumé

Les sections suivent l’ordre défini par pipeline. Commencez par le tableau de décision, puis utilisez les sections ultérieures comme référence pour chaque étape.


Table de décision d’évaluation RAG

Utilisez ce tableau comme point de départ avant de choisir un framework. La métrique appropriée dépend du mode de défaillance que vous cherchez à détecter, et non du nom de l’outil.

QuestionFamille de métriquesUtilisez ceci lorsqueFaites attention à
Complétude de l’extraction, couverture des tableaux et figuresLes PDF, diapositives, scans et pages HTML sont intégrés au corpus.Un texte au rendu soigné peut tout de même présenter des sous-titres manquants, des notes de bas de page ou une structure de tableau incorrecte.
La phase de récupération a-t-elle trouvé les preuves appropriées ?Recall@k, nDCG@k, MRR, précision/recall dans le contexteVous pouvez étiqueter les fragments ou documents pertinents.Un filtre métadonnées strict peut éliminer le document pertinent avant même que le classement ne commence.
reranking a-t-il amélioré la liste restreinte ?Reranker amélioration, Précision@1, delta nDCGLes encodeurs croisés ou les classeurs LLM se situent après l’étape de récupération.Mesurer la latence et le coût grâce à l’amélioration de la qualité
Fidélité, ancrage, prise en charge des citationsLa réponse cite des documents ou se base sur des faits tirés du contexte.La fidélité ne permet pas de diagnostiquer une analyse incorrecte ou un chargement insuffisant des données.
Le système est-il stable en production ?Dérive, régénération, mécanisme de secours, latence p95, coût par réponseChangements de trafic après le lancementLa télémétrie de production nécessite une revue humaine échantillonnée afin de rester calibrée

Pour une comparaison plus concise des outils, consultez Meilleurs outils et métriques d’évaluation pour RAG en 2026.

Partie 1 : Définir le critère de succès avant l’architecture

Rédigez l’ensemble d’évaluation avant le diagramme d’architecture. Il permet à chaque choix de composant ultérieur d’avoir un objectif mesurable.

Vous ne pouvez pas choisir entre BM25 et la récupération dense, le chunking récursif et sémantique, ou Cohere Rerank et BGE avant de savoir ce que vous optimisez. « Meilleures réponses » n’est pas une métrique. Un cahier des charges illustratif pourrait être : « fidélité ≥ 0,85 sur un ensemble d’entraînement de 200 requêtes couvrant nos trois principales intentions, avec une latence p95 < 1,5 s et un taux d’exclusion fausse par filtrage < 2 % ». Ces chiffres sont des placeholders ; l’essentiel est que la qualité, la couverture, la latence et le filtrage disposent de seuils définis de manière explicite.

Définissez d’abord le harness avant d’écrire le code de récupération. Le premier harness sera incorrect, et vous devrez le corriger. Modifier une métrique coûte bien moins cher que de devoir revoir un système qui a déjà été déployé.

Trois couches pipeline et deux modes de fonctionnement

Le RAG moderne est un pipeline, ce qui implique que son évaluation doit s’effectuer selon des pipeline. Aucune valeur numérique unique ne permet de couvrir tous les modes de défaillance.

L’évaluation en environnement de production comporte trois niveaux pipeline. L’évaluation de l’ingestion vise à vérifier si le corpus ainsi que l’index conservent fidèlement la structure du texte source. L’évaluation en temps de requête s’assure que la réécriture, le filtrage, la récupération des données, reranking, ainsi que l’assemblage du contexte permettent de trouver les preuves appropriées. Enfin, l’évaluation de la réponse et de la production évalue si la réponse fait usage de ces preuves et si sa qualité reste satisfaisante face à un trafic réel. En regroupant ces niveaux en un seul score, les anomalies de normalisation peuvent être masquées au sein d’une note de réponse jugée acceptable.

Les trois situations dans lesquelles un système RAG peut perdre des preuves

Ces couches décrivent l’endroit où se produit une défaillance. Les modes offline et online indiquent quand et à partir de quels données s’effectue la vérification. L’évaluation offline utilise une dataset fixe disposant de valeurs de référence connues ; elle est reproductible et convient pour la sélection des composants, les comparaisons A/B ainsi que les contrôles intégrés. L’évaluation online évalue le trafic en temps réel et prend en compte la régénération, le temps d’attente, les retours explicites et l’évolution réelle des requêtes. Elle est plus bruyante et plus difficile à instrumenter.

Chaque couche pipeline peut fournir des vérifications en mode hors ligne ainsi qu’en mode en ligne. Un corpus d’ingestion fixe permet de détecter les régressions du analyseur avant la mise en production, tandis que des moniteurs de fraîcheur et d’échecs de parsing surveillent les mises à jour en temps réel. Un ensemble de requêtes fixe évalue les performances de récupération avant la publication, alors que des traces en temps réel échantillonnées révèlent tout écart par rapport aux conditions de production. Les vérifications exclusivement en mode hors ligne manquent les modifications en temps réel ; celles exclusivement en mode en ligne rendent difficile la reproduction des régressions.

Niveau de composant versus bout en bout

Il existe deux erreurs fréquentes. Une évaluation uniquement en boucle bout en bout indique que le système est défaillant, mais sans préciser l’endroit exact du problème. Une évaluation portant uniquement sur les composants peut montrer que chacune de ses parties fonctionne correctement, alors que le système dans son ensemble échoue malgré tout. La solution consiste à utiliser quelques métriques clés en boucle bout en bout pour prendre des décisions d’acceptation ou de rejet, complétées par des métriques par composant afin de diagnostiquer la cause du dysfonctionnement. Les métriques de récupération permettent de détecter les dégradations du module de récupération des données, tandis que les métriques de génération permettent d’identifier les dégradations du module de génération. Enfin, la correction des réponses en boucle bout en bout permet de repérer les échecs d’intégration.

La référence frameworks (itinéraire déterministe)

FrameworkIdéal pourLà où cela échoue
RAGASDes métriques RAG sans référence (fidélité, pertinence de la réponse, précision/recouvrement du contexte) ; le vocabulaire de factoLLM – évaluation du coût ; composantes de score opaques lors du débogage ; paramètres par défaut axés sur l’anglais
ARESLe classifieur entraîné évalue les résultats en fonction de pipeline ; il nécessite moins d’annotations que les approches de type RAGAS ; il offre une précision élevée pour les systèmes à proximité.Configuration plus lourde : il est nécessaire d’entraîner réellement des modèles.
TruLensFonctions de retour d’information composables offrant une forte capacité d’explicabilité ; traçage OpenTelemetry ; adaptées à l’environnement de productionLes métriques spécifiques à RAG indiquent moins de batteries incluses que celles de RAGAS.
DeepEvalTests unitaires de style Pytest pour les sorties générées par LLM ; G-Eval, métriques personnalisées, intégration native dans les pipelines CI/CDUne utilisation intensive de LLM-judge entraîne des pics de coûts.
Arize PhoenixTraçage approfondi et visualisation embedding ; détection visuelle de la dérive embedding ; natif d’OTELVous apportez vos propres définitions de métriques
Track TREC 2024 RAGPublic benchmark destiné à l’évaluation des « nuggets » (AutoNuggetizer), au soutien à l’évaluation ainsi qu’à la fluidité sur MS MARCO Segment v2.1Ce n’est pas un outil runtime, mais plutôt un benchmark servant à effectuer une calibration par rapport à

Ma stack par défaut comprend RAGAS pour le vocabulaire des métriques, DeepEval pour les contrôles d’intégration continue, Phoenix pour le traçage en production, ainsi que du code personnalisé destiné aux métriques spécifiques à l’ontologie. Vous finirez inévitablement par avoir besoin de plus que ce avec quoi vous commencez. Choisissez le framework qui facilite la création de métriques personnalisées.

Pour benchmarks, utilisez BEIR (Thakur et al., NeurIPS 2021) pour la généralisation de la récupération sans exemple. MTEB pour une qualité générale embedding, MIRACL pour la récupération multilingue, et le TREC 2024 RAG Piste à des fins d’évaluation bout en bout RAG.


Partie 2 : Associer les points d’évaluation aux pipeline

Un système RAG en environnement de production dépasse de loin le simple cadre consistant à « intégrer des documents, récupérer des fragments et appeler un LLM ». Chaque étape, allant de l’acquisition du document à la livraison de la réponse, peut échouer.

L’ensemble complet de RAG pipeline, accompagné d’indicateurs métriques à chaque étape

Chaque étape du diagramme dispose d’au moins une métrique. Une étape ne possédant aucune métrique peut échouer sans que personne ne s’en aperçoive.

Les trois voies coïncident aux points où les preuves peuvent être perdues. La voie d’ingestion prend en charge le parsing, le nettoyage, le chunking, embedding, ainsi que l’indexation. La voie en temps de requête gère la réécriture, le filtrage, la récupération, reranking, et l’assemblage du contexte. Enfin, la voie relative à la réponse et à la mise en production s’occupe de la fidélité des résultats, de la vérification des citations, des signaux émis par les utilisateurs, de la dérive des performances, de la latence et des coûts associés.

Les erreurs s’accumulent le long de la chaîne de traitement. Une analyse incorrecte limite la segmentation en chunks. Une segmentation défectueuse entrave la récupération des données. Une récupération insuffisante affecte directement reranking. De même, des reranking médiocres restreignent la capacité de génération. La fidélité ne mesure que la réponse finale, et jamais les causes situées en amont du processus.


Partie 3 : Évaluation de l’ingestion

De nombreuses pannes en environnement de production liées à RAG commencent lors de l’ingestion des données. Le système fonctionne correctement avec des documents de test propres, mais échoue dès qu’il est confronté à des PDF réels, des scans, des tableaux ou des pages de corpus peu structurées.

Acquisition et analyse du document

Qu’il convient de mesurer :

Comparez différentes familles de parseurs, tels qu’une version de base de Tesseract, un modèle basé sur VLM et OCR, ainsi que la solution proposée par votre fournisseur. Utilisez un échantillon stratifié composé de classes de documents réelles à une résolution DPI fixe, incluant des scans nets, des photos, des tableaux, du texte multilingue, du contenu mathématique et de l’écriture manuscrite. Indiquez le CER ou le WER pour chaque classe, ainsi que le TEDS pour les pages contenant des tableaux.

Nettoyage et normalisation

Le découpage en chunks influence la qualité de la récupération

Le découpage en chunks peut engendrer un écart de rappel à plusieurs points, même lorsque le modèle embedding reste inchangé. Dans Le fournisseur NVIDIA pour 2024 benchmark, Le découpage au niveau de page a permis d’obtenir la plus haute précision ainsi que la plus faible variance pour les documents paginés. Il convient de considérer ce résultat comme une preuve spécifique au corpus testé, et non comme une solution valable pour tous les cas.

Le chunking sémantique regroupe des phrases adjacentes en fonction de leur similarité embedding et effectue des coupes aux frontières où cette similarité diminue. LangChain’s SemanticChunker et celui de LlamaIndex SemanticSplitterNodeParser Mettez en œuvre cette stratégie. Elle permet d’améliorer le taux de rappel par rapport aux fenêtres fixes lorsque les frontières thématiques jouent un rôle important.

La séparation récursive des caractères teste d’abord les sauts de paragraphe, puis les sauts de phrase, et enfin les sauts de mot, jusqu’à ce que chaque morceau corresponde à la taille cible. LangChain’s RecursiveCharacterTextSplitter Il met en œuvre cette séquence : sélectionnez des valeurs de fenêtre et d’overlap adaptées à la structure de votre document, puis laissez l’ensemble doré déterminer les valeurs finales.

Métriques à suivre :

Mon avis : le découpage structurel (division selon les titres, les tableaux et les sections — mis en œuvre par des analyseurs tels que unstructured.io ou en parcourant l’AST déjà généré par votre analyseur) est sous-utilisé. Si vos documents présentent une structure, utilisez‑la avant d’ajouter des heuristiques de similarité. La segmentation récursive des caractères constitue la référence de base ; le chunking sémantique justifie les surcoûts principalement pour du texte non structuré.

Extraction et enrichissement des métadonnées

Génération Embedding

Construction de l’index


Partie 4 : Évaluation en temps de requête

La voie de temps de requête contient les métriques permettant de diagnostiquer le parcours de récupération des données. Le taux de rappel @k seul ne permet pas de déterminer si c’est la réécriture, le filtrage, reranking, ou l’assemblage du contexte qui est à l’origine de l’échec.

Compréhension et réécriture des requêtes

Métriques de récupération

Il s’agit des métriques de référence. Si vous ne les suivez pas, il vous est impossible de déterminer si la récupération s’améliore.

MétriqueCe qu’il mesureQuand l’utiliser
Recall@kfraction des documents pertinents d’une requête restitués parmi les k premiersÀ utiliser lorsque la présence de l’un quelconque des éléments du ensemble concerné est critique
Précision@kpourcentage de top-k pertinents
MRRmoyenne de 1/rang du premier document pertinentlorsque les utilisateurs ne consultent que les résultats du top-1 ou du top-3
nDCG@kgain pondéré par la perte de position et les notes de pertinencemétrique de récupération standard pour l’adéquation graduée
MAPmoyenne de la précision moyenne sur les requêteslorsque l’on se soucie de toute la liste classée
Taux de réussite@ksi au moins un document pertinent apparaît parmi les k premiersCalculer la moyenne du résultat binaire sur l’ensemble des requêtes afin d’obtenir rapidement une métrique de vérification.
Couverturepourcentage de documents « golden » jamais récupérés au cours de toutes les requêtesdétecte les lacunes systématiques dans l’index

Les formules, à titre de référence (pertinence binaire avec l’ensemble pertinent RqR_q pour la requête qq, et reli=1\text{rel}_i = 1 si le ii-ème document récupéré appartient à RqR_q) :

\text{Recall@k} = \frac{|R_q \cap \{d_1, \dots, d_k\}|}{|R_q|}, \quad \text{Precision@k} = \frac{|R_q \cap \{d_1, \dots, d_k\}|{k} RRq=1rang du premier document pertinent,MRR=1QqQRRq\text{RR}_q = \frac{1}{\text{rang du premier document pertinent}}, \quad \text{MRR} = \frac{1}{|Q|} \sum_{q \in Q} \text{RR}_q DCG@k=i=1k2reli1log2(i+1),nDCG@k=DCG@kIDCG@k\text{DCG@k} = \sum_{i=1}^{k} \frac{2^{\text{rel}_i} - 1}{\log_2(i + 1)}, \quad \text{nDCG@k} = \frac{\text{DCG@k}}{\text{IDCG@k}}

Pour la pertinence graduée, reli{0,1,2,}\text{rel}_i \in \{0, 1, 2, \dots\} ; le nDCG binaire constitue le cas particulier utilisé dans le code ci‑dessous. MAP représente la moyenne, parmi toutes les requêtes, de APq=1Rqi:reli=1Precision@i\text{AP}_q = \frac{1}{|R_q|}\sum_{i: \text{rel}_i = 1} \text{Precision@}i. Voir Manning, Raghavan, Schütze, Introduction à la récupération d’informations, Chapitre 8 : Dérivations.

Pour le code en production, utilisez ranx, pytrec_eval, ou ir_measures — Ils prennent en charge l’ensemble de la famille de métriques TREC et gèrent correctement la pertinence graduée. Définissez des objectifs de déploiement en vous basant sur un ensemble de référence réaliste, sur la qualité des réponses produites et sur le coût d’une erreur de classification. Ne copiez pas les seuils proposés dans des tutoriels.

Le test harness pour ceux-ci est court. Vous pouvez le lancer depuis un notebook avant même d’avoir choisi une base de données vectorielle.

from math import log2
from statistics import mean

# synthetic gold set: query_id -> set of relevant doc ids
gold = {
    "q1": {"d3"},
    "q2": {"d7", "d2"},
    "q3": {"d11"},
    "q4": {"d5"},
}

# ranked retrieval results: query_id -> ranked list of doc ids (top-10)
runs = {
    "q1": ["d8", "d3", "d1", "d4", "d2", "d9", "d6", "d10", "d12", "d13"],
    "q2": ["d2", "d6", "d4", "d7", "d1", "d3", "d8", "d11", "d5", "d9"],
    "q3": ["d11", "d2", "d3", "d4", "d1", "d6", "d7", "d8", "d10", "d12"],
    "q4": ["d1", "d2", "d3", "d6", "d8", "d9", "d10", "d12", "d13", "d14"],
}

def recall_at_k(ranked, gold_set, k):
    if not gold_set:
        return 0.0
    hit = sum(1 for d in ranked[:k] if d in gold_set)
    return hit / len(gold_set)

def reciprocal_rank(ranked, gold_set):
    # MRR contribution per query: 1/rank of the first relevant doc.
    for rank, d in enumerate(ranked, start=1):
        if d in gold_set:
            return 1.0 / rank
    return 0.0

def ndcg_at_k(ranked, gold_set, k):
    # binary relevance: rel ∈ {0, 1}
    gains = [1.0 if d in gold_set else 0.0 for d in ranked[:k]]
    dcg = sum(g / log2(i + 2) for i, g in enumerate(gains))
    # ideal DCG: all gold docs ranked first, capped by k
    n_gold_in_topk = min(k, len(gold_set))
    idcg = sum(1.0 / log2(i + 2) for i in range(n_gold_in_topk))
    return dcg / idcg if idcg else 0.0

K = 5
print(f"Recall@{K}: {mean(recall_at_k(runs[q], gold[q], K) for q in gold):.3f}")
print(f"MRR:       {mean(reciprocal_rank(runs[q], gold[q]) for q in gold):.3f}")
print(f"nDCG@{K}:  {mean(ndcg_at_k(runs[q], gold[q], K) for q in gold):.3f}")
# Recall@5: 0.750
# MRR:       0.625
# nDCG@5:    0.627

Il s’agit de votre porte de contrôle CI pour la récupération des données. Connectez-la à un sous-ensemble rapide basé sur le taux de couverture pour chaque PR, et exécutez l’ensemble complet de référence lors de la porte de déploiement plus lente. Bloquez la fusion lorsque une métrique préenregistrée dépasse son budget de régression.

Le dépôt associé fixe les valeurs numériques précises mentionnées ci-dessus.Recall@5 = 0.750, MRR = 0.625, nDCG@5 = 0.627) en tant que test unitaire dans tests/test_retrieval_metrics.py; Notebook 01 effectue des analyses de Recall@k / MRR / nDCG sur un index réel SciFact, et la version destinée à la production harness est utilisée en evaluation/retrieval.py.

Fusion hybride de la récupération d’informations et du rang réciproque

BM25 il s’agit d’un évaluateur lexical dispersif qui combine la correspondance terme à terme, le pondération des termes et la normalisation de la longueur. Il est disponible dans rank_bm25Elasticsearch, OpenSearch, ainsi que la plupart des moteurs de recherche.

Fusion de rangs réciproques (Cormack, Clarke, et Buettcher, SIGIR 2009) intègre le BM25 aux classements densifs basés sur la position. L’original k=60 Cette configuration constitue une référence utile. Le RRF étant indépendant de la valeur de score, il élimine la normalisation inter-lignes nécessaire en cas d’interpolation linéaire. Lorsque l’ensemble étiqueté est suffisamment volumineux pour permettre d’estimer une différence stable, il convient également d’évaluer une combinaison convexe et d’ajuster la valeur de α.

L’utilisation d’une récupération hybride associée à un encodage croisé reranker améliore fréquemment la qualité des corpus techniques, de type journal de logs ou de code. Les gains peuvent être limités pour les corpus fortement sémantiques. Il est recommandé de comparer les résultats avec ceux obtenus en utilisant uniquement des données denses ou uniquement des données sparses, car une configuration de fusion inefficace peut entraîner de performances inférieures par rapport à l’un ou l’autre type d’entrée.

La mise en œuvre tient sur quelques lignes seulement.

from collections import defaultdict

# two retrieval lanes: dense embeddings and BM25.
dense  = ["d3", "d7", "d1", "d4", "d2", "d9", "d10"]
sparse = ["d2", "d3", "d8", "d1", "d11", "d4", "d6"]

def rrf(rankings: list[list[str]], k: int = 60) -> list[tuple[str, float]]:
    """Reciprocal Rank Fusion (Cormack et al., SIGIR 2009).

    score(d) = sum over rankings of 1 / (k + rank(d))
    Score-agnostic: only rank position matters. k=60 is the canonical default.
    """
    scores: dict[str, float] = defaultdict(float)
    for ranking in rankings:
        for rank, doc in enumerate(ranking, start=1):
            scores[doc] += 1.0 / (k + rank)
    return sorted(scores.items(), key=lambda kv: kv[1], reverse=True)

fused = rrf([dense, sparse], k=60)
for doc, score in fused[:5]:
    print(f"{doc}  score={score:.5f}")
# d3  score=0.03252   <- rank 1 dense, rank 2 sparse
# d2  score=0.03178   <- rank 5 dense, rank 1 sparse
# d1  score=0.03150

Remarquez ce que RRF ne fait pas : il ne prend jamais en compte les scores de similarité bruts. Un retriever dense qui affiche une valeur de cosinus de 0,98 et un algorithme BM25 qui donne un score de 17,4 ne sont pas directement comparables. Si vous les normalisez à l’aide de scores z ou d’une échelle min-max, vous risquez de privilégier le résultat présentant la plus grande variance au sein de ce lot.

RRF ne prend en compte que le rang. Si un récupérateur place un document à la position 2, ce vote vaut 1 / (60 + 2), quel que soit le score brut qui l’a généré.

Hybride + RRF sur SciFact : Notebook 02 compare les algorithmes dense, BM25 et RRF en utilisant des deltas par requête. Le fusionneur adapté au environnement de production est présent dans retrieval/hybrid_rrf.py; tests/test_rrf.py fixe la version canonique d3 / d2 / d1 Commander en k=60.

Reranking

from sentence_transformers import CrossEncoder

reranker = CrossEncoder("BAAI/bge-reranker-v2-m3")

query = "How do I rotate database credentials in production?"
candidates = [
    "Production database credentials are rotated via Vault every 30 days.",
    "The new logo was unveiled at the all-hands meeting.",
    "To rotate prod DB creds, run the `rotate-secrets` GitHub Action.",
]

scores = reranker.predict([(query, c) for c in candidates])
ranked = sorted(zip(candidates, scores), key=lambda x: -x[1])
for doc, score in ranked:
    print(f"{score:+.3f}  {doc}")

Un reranker constitue un candidat à fort potentiel pour une RAG pipeline de base, mais ce n’est pas une solution garantie. Mesurez sa ΔPrecision@1 ainsi que sa ΔnDCG sur votre ensemble de référence, puis conservez-le uniquement si l’amélioration obtenue est supérieure à ses limites en termes de latence et de coût. Comparez ensuite cette amélioration mesurée avec celles obtenues grâce à des modifications plus modestes du système de récupération avant de choisir la prochaine étape d’optimisation.

ΔnDCG et ΔPrecision@1 obtenus à l’aide d’un cross-encoder sur SciFact : Notebook 03; module : retrieval/reranker.py.

Construction du contexte et problème du « perdu au milieu »

C’est là que surviennent de nombreux échecs de type « bonne récupération des données, mauvaise réponse ».


Partie 5 : Le taux de faux rejet du filtre

Ce métrique bénéficie d’une section dédiée, car les scores de récupération agrégés ne permettent pas d’attribuer une erreur de recherche au filtre.

Un filtre métadonnées strict comme tenant_id = X AND product = Y AND locale = en-US Il peut réduire le taux de rappel efficace à zéro. Un Recall@k correctement implémenté permet de détecter cette perte, car son dénominateur reste l’ensemble initial des documents pertinents. Il ne permet cependant pas de déterminer si c’est le filtre, le récupérateur ou le classeur qui est à l’origine de cette omission. La fiabilité peut encore sembler bonne, puisque l’évaluation se fait sur le contexte récupéré incomplet ; le modèle a donc fidèlement indiqué « Je ne sais pas ».

La branche rouge dans l’arbre représente l’échec le plus fréquent : le document souhaité existe bien, mais le filtre le supprime avant sa récupération.

Taxonomie des échecs silencieux grâce à la métrique qui détecte chaque mode

La métrique

filter_false_exclusion_rate =
    (# queries where all gold docs were excluded by metadata filter) /
    (# queries with at least one gold doc)

Cette définition au niveau de la requête prend en compte les exclusions catastrophiques : aucun document pertinent ne survit. Pour les requêtes multi-or, le Recall@k standard entraîne toujours une perte partielle ; il convient d’ajouter un taux d’exclusion par document si ce critère est important. Pour calculer l’un ou l’autre de ces taux, il faut (a) les identifiants des documents réels pour chaque requête d’évaluation et (b) un système d’instrumentation qui enregistre les prédicats de filtrage appliqués, et non seulement les résultats finaux. Déterminez l’objectif en fonction du coût lié à l’exclusion d’une réponse valide ainsi que de l’intervalle de confiance de votre échantillon de production.

Voici une implémentation fonctionnelle. Elle compare le taux de rappel standard correct à un évaluateur invalide qui redéfinit la pertinence après filtrage.

# A small worked example where hard filters remove relevant documents.
docs = [
    {"id": "d1", "tenant": "acme",   "locale": "en-US"},
    {"id": "d2", "tenant": "acme",   "locale": "en-GB"},
    {"id": "d3", "tenant": "globex", "locale": "en-US"},
    {"id": "d4", "tenant": "acme",   "locale": "en-US"},
    {"id": "d5", "tenant": "acme",   "locale": "de-DE"},
]

queries = [
    # the gold doc lives in en-GB but the dynamic filter forced en-US
    {"qid": "q1", "gold": {"d2"}, "filter": lambda d: d["locale"] == "en-US"},
    # the gold doc is correctly within the tenant filter
    {"qid": "q2", "gold": {"d4"}, "filter": lambda d: d["tenant"] == "acme"},
    # the gold doc is in a different tenant and gets dropped
    {"qid": "q3", "gold": {"d3"}, "filter": lambda d: d["tenant"] == "acme"},
    # the gold doc passes the filter (de-DE locale match)
    {"qid": "q4", "gold": {"d5"}, "filter": lambda d: d["locale"] == "de-DE"},
]

def filter_false_exclusion_rate(queries, docs):
    n_with_gold, n_excluded = 0, 0
    for q in queries:
        if not q["gold"]:
            continue
        n_with_gold += 1
        survivors = {d["id"] for d in docs if q["filter"](d)}
        if not (q["gold"] & survivors):
            n_excluded += 1
    return n_excluded / n_with_gold if n_with_gold else 0.0

rate = filter_false_exclusion_rate(queries, docs)
print(f"filter_false_exclusion_rate = {rate:.2%}")
# filter_false_exclusion_rate = 50.00%

# Correct Recall@k keeps the original gold set as its denominator.
def standard_recall_at_k(queries, docs, k=10):
    recalls = []
    for q in queries:
        survivors = [d for d in docs if q["filter"](d)][:k]
        survivor_ids = {d["id"] for d in survivors}
        recalls.append(len(q["gold"] & survivor_ids) / len(q["gold"]))
    return sum(recalls) / len(recalls) if recalls else 0.0

print(f"standard recall@10 = {standard_recall_at_k(queries, docs):.2%}")
# standard recall@10 = 50.00%

# INVALID: rebuilding the gold set after filtering changes the question.
# It drops queries whose relevant documents did not survive, then scores 100%.
def invalid_recall_over_filtered_gold(queries, docs, k=10):
    recalls = []
    all_doc_ids = {d["id"] for d in docs}
    for q in queries:
        all_survivors = {d["id"] for d in docs if q["filter"](d)}
        filtered_gold = q["gold"] & all_doc_ids & all_survivors
        if not filtered_gold:
            continue
        top_k_ids = set(list(all_survivors)[:k])
        recalls.append(len(filtered_gold & top_k_ids) / len(filtered_gold))
    return sum(recalls) / len(recalls) if recalls else 0.0

invalid = invalid_recall_over_filtered_gold(queries, docs)
print(f"INVALID recall (filtered gold) = {invalid:.2%}")
# INVALID recall (filtered gold) = 100.00%

assert rate == 0.5
assert standard_recall_at_k(queries, docs) == 0.5
assert invalid == 1.0

La moitié des requêtes perdent leur document de référence en raison du filtre, ce qui fait chuter le taux de rappel correct @10 à 50 %. Ce score permet de détecter le symptôme, mais il n’est pas en mesure d’en attribuer la cause. Le taux d’exclusion fausse indique que le prédicat a supprimé deux réponses avant même que le récupérateur ne fonctionne. L’évaluateur délibérément invalide affiche 100 % uniquement parce qu’il élimine ces échecs de son ensemble de référence. Aucun modèle ne peut récupérer un document qui a été filtré.

Le taux de 50 % mentionné ci-dessus est reproduit sous forme de test unitaire dans le repo associé : tests/test_filter_exclusion.py::test_50_percent_exclusion_rate. Notebook 04 on l’exécute sur SciFact à l’aide de métadonnées synthétiques afin de pouvoir observer un filtre réel éliminer complètement le taux de rappel ; la métrique runtime (accompagnée de ses indicateurs associés de précision/pourcentage de rappel) se trouve dans evaluation/filter_exclusion.py.

Métrique associée : précision et rappel du prédicat

Lorsque le filtrage est dynamique (par exemple, un LLM extrait les prédicats de filtrage à partir de la requête), considérez l’extracteur de prédicats comme un modèle de classification et évaluez‑le en conséquence. Mesurez la précision et le rappel des prédicats à l’aide d’un ensemble étiqueté de (query, correct predicate) paires. Un taux d’erreur prédicatif ne correspond pas directement à une perte en points équivalente dans les métriques de rappel ; il convient plutôt d’évaluer la fréquence à laquelle ces erreurs excluent un document de référence. Une fois que ce filtre strict a éliminé le document de référence, aucune quantité de reranking ne peut y remédier.

Amélioration douce versus filtre strict

Cette métrique impose une décision de conception. Il convient d’utiliser des filtres stricts lorsque la correction est binaire : juridiction légale, limites des ACL, état publié contre version草案. À l’inverse, des surpondérations douces doivent être appliquées lorsque la pertinence est évaluée sur une échelle : préférences locales, date de publication, version. En l’absence d’une mesure du taux d’exclusion, il devient difficile de détecter le mauvais choix.

La règle de décision, mesurable :

For each filter predicate F:
  hard_recall_F  = retrieval_recall@k with F as a hard filter
  soft_recall_F  = retrieval_recall@k with F as a +0.X rerank boost
  hard_precision = relevant_in_top_k / k under hard filter
  soft_precision = relevant_in_top_k / k under soft boost
  exclusion_rate = % of queries where the gold doc was filtered out (hard)

Use hard filter only if exclusion_rate < ε AND hard_precision >> soft_precision.
Otherwise prefer soft boost.

Choisissez ε en fonction du risque lié à une exclusion erronée, des avantages d’une plus grande précision, ainsi que de la taille de l’échantillon d’évaluation. Un article dédié de cette série aborde plus en détail ce compromis.


Partie 6 : Évaluation de la génération

Les métriques de récupération vous indiquent si le système peut répondre correctement. Elles ne vous disent pas s’il l’a fait réellement. Les métriques de génération comblent cette lacune.

Fidélité et ancrage

Fidélité RAGAS décompose la réponse en affirmations atomiques (énoncés factuels courts et autonomes), puis vérifie chacune d’elles à l’aide d’un juge LLM en s’appuyant sur le contexte récupéré :

fideˊliteˊ=affirmations eˊtayeˊes par le contextetotal des demandes\text{fidélité} = \frac{|\text{affirmations étayées par le contexte}|}{|\text{total des demandes}|}

Le pourcentage de prétentions pris en charge correspond à ce score. Cette structure est plus utile que n’importe quel chiffre isolé, car elle indique quelles prétentions ne sont pas soutenues. Le code de production se trouve dans le ragas package — son utilisation se présente comme suit :

from datasets import Dataset
from ragas import evaluate
from ragas.metrics import faithfulness, answer_relevancy, context_precision

samples = Dataset.from_dict({
    "question": ["How many moons does Mars have?"],
    "answer":   ["Mars has two moons, Phobos and Deimos."],
    "contexts": [["Mars has two moons named Phobos and Deimos."]],
    "ground_truth": ["Mars has two moons."],
})

result = evaluate(samples, metrics=[faithfulness, answer_relevancy, context_precision])
print(result)

Ci-dessous se trouve la même boucle déroulée, avec un juge de remplacement déterministe, afin que vous puissiez observer sa structure du début à la fin.

def extract_claims(answer: str) -> list[str]:
    # Production: an LLM call that decomposes the answer.
    # Demo: split on sentence-final punctuation.
    return [c.strip() for c in answer.replace("?", ".").replace("!", ".").split(".") if c.strip()]

def verify_claim(claim: str, context: str) -> bool:
    # Production: an NLI (natural-language inference) model or LLM judge.
    # Demo: a deterministic stand-in so the example runs offline.
    entailed_pairs = {
        "Mars has two moons": True,
        "Phobos and Deimos orbit Mars": True,
        "Mars has a thick atmosphere": False,  # unsupported by context
        "Curiosity landed in 2012": True,
    }
    for k, v in entailed_pairs.items():
        if k.lower() in claim.lower() or claim.lower() in k.lower():
            return v
    words = [w.lower() for w in claim.split() if len(w) > 3]
    return all(w in context.lower() for w in words) if words else False

context = (
    "Mars has two moons, Phobos and Deimos. NASA's Curiosity rover "
    "landed on Mars in 2012."
)
answer = (
    "Mars has two moons. Phobos and Deimos orbit Mars. "
    "Mars has a thick atmosphere. Curiosity landed in 2012."
)

claims = extract_claims(answer)
verdicts = [(c, verify_claim(c, context)) for c in claims]
faithfulness = sum(1 for _, ok in verdicts if ok) / len(verdicts)
for c, ok in verdicts:
    print(f"  [{'✓' if ok else '✗'}] {c}")
print(f"faithfulness = {faithfulness:.2f}")
# faithfulness = 0.75   (one unsupported claim about the atmosphere)

La structure est cruciale. En environnement de production, verify_claim cela devient un modèle NLI ou une invocation de LLM. Le reste des étapes de harness reste inchangé : extraction, vérification, agrégation.

Extraction et vérification bout en bout des affirmations concernant les réponses SciFact générées : Notebook 05; module : evaluation/faithfulness.py. Le dépôt exécute également un vérificateur inter-familles de type HHEM au sein du même boucle, afin que vous puissiez déterminer quelle famille de juges est en accord avec laquelle.

Une alternative spécifiquement conçue pour remplacer LLM en tant que juge HHEM-2.1-Open (Hughes Hallucination Evaluation Model, Vectara), un classifieur affiné pour la détection des hallucinations. Sa fiche de modèle décrit le checkpoint, la frontière de décision par défaut, ainsi que les résultats obtenus sur AggreFact et RAGTruth. Considérez ces éléments comme des preuves fournies par la fiche de modèle, et non comme une garantie valable pour votre corpus : calibrez le seuil en utilisant des étiquettes locales et comparez-le avec le critère de jugement que vous avez retenu avant déploiement.

Évaluation des faits atomiques

FActScore (Min et al., EMNLP 2023) décompose les générations de longue forme en faits atomiques, récupère les preuves correspondant à chaque fait, et assigne une étiquette à chacun d’eux supported / not-supported, et indique la fraction prise en charge :

FActScore=faits atomiques pris en chargefacts atomiques totaux\text{FActScore} = \frac{|\text{faits atomiques pris en charge}|}{|\text{facts atomiques totaux}|}

Implémentation de référence : shmsw25/FActScore. Il fonctionne très bien pour les biographies, les résumés ainsi que d’autres types de contenus de long format. Attention cependant : des faits triviaux répétitifs peuvent faire augmenter le score, et les attaques de type « MontageLie » (faits véridiques présentés dans un ordre trompeur) peuvent le rendre inefficace. VeriScore traite les revendications en y appliquant les modificateurs nécessaires ; le Cœur Le filtre contribue à empêcher le remplissage artificiel des données factuelles.

Précision de la citation

Suivez la précision des citations (les segments cités prennent réellement en charge l’affirmation) ainsi que le rappel des citations (les affirmations qui devraient être citées le sont bien) :

cite_precision=les segments citeˊs qui eˊtayent une affirmationsegments citeˊs,cite_recall=alleˊgations contenant au moins une portion citeˊaˋ l’appuialleˊgations qui doivent eˆtre citeˊes\text{cite\_precision} = \frac{|\text{les segments cités qui étayent une affirmation}|}{|\text{segments cités}|}, \quad \text{cite\_recall} = \frac{|\text{allégations contenant au moins une portion citée à l’appui}|}{|\text{allégations qui doivent être citées}|}

La piste TREC 2024 RAG définit un protocole d’évaluation sur support reproductible. Upadhyay et al. (SIGIR 2025) Le rapport indique que GPT-4o est en accord avec les juges humains dans 56 % des cas lors d’une évaluation manuelle effectuée à partir de zéro, ce taux montant à 72 % après édition des prédictions générées par LLM. Cela constitue un atout pour amplifier la productivité dans leurs conditions spécifiques, mais ne remplace pas l’évaluation humaine dans des contextes à haut risque. Il s’agit donc d’une approximation automatisée. ALCE (Gao et al., EMNLP 2023) met en œuvre une précision/recall de citation grâce à une vérification basée sur la NLI.

Exactitude de la réponse, exhaustivité, refus

Vérification post-génération

Les gains de fiabilité les plus économiques proviennent le plus souvent de vérifications déterministes post-validation, et non de modèles plus volumineux.


Partie 7 : Évaluation basée sur une ontologie RAG

Les métriques standards mentionnées ci-dessus couvrent les corpus ouverts RAG. Les systèmes basés sur des ontologies nécessitent davantage d’indicateurs. Lorsque votre RAG effectue des recherches à partir d’une ontologie structurée, d’une taxonomie ou d’un graphe de connaissances (produits dans un catalogue, conditions dans SNOMED, composants dans une liste de matériel, techniques de sécurité dans MITRE ATT&CK), les métriques standards RAG sont indispensables, mais insuffisantes. Il est également nécessaire d’évaluer la couche ontologique elle-même.

Précision du lien d’entité

La première tâche consiste à associer une mention issue d’une requête à une entité de l’ontologie (« Aspirin » → wikidata:Q18216, « le 737 » → aircraft:Boeing_737).

Évaluation tenant compte de la hiérarchie

L’exactitude brute traite le cas où la prédiction est « Sedan » alors que la réalité est « Hatchback » de la même manière que le cas où la prédiction est « Sedan » alors que la réalité est « Submarine ». Or, ces erreurs ne sont pas équivalentes.

Taux de faux rejet des filtres (reprise, désormais critique)

Dans les systèmes fondés sur des ontologies, les filtres rigides proviennent souvent de l’ontologie elle-même (« ne récupérer que les documents étiquetés avec la catégorie X »). La métrique du taux d’exclusion (définie dans Partie 5) Il devient ainsi un signal principal de correction. Une prédiction de catégorie erronée peut annuler complètement le taux de rappel ; le taux d’exclusion attribue cette perte au filtre.

Conformité de génération contrainte

Lorsque votre sortie doit respecter une ontologie (chaque nom d’entité dans la réponse doit être un membre valide de l’ontologie ; chaque prédicat doit provenir d’un vocabulaire fermé), mesurez :

Constrained decoding frameworks (Aperçus, XGrammar, Directives, OpenAI Structured Outputs) Ils sont conçus pour garantir la validité du schéma. JSONSchemaBench Il compare l’efficacité, le taux de couverture et la qualité entre les différentes implémentations. Il réexécute ses cas correspondant à vos schémas ainsi qu’à serving backend, car le taux de couverture et la latence dépendent des deux facteurs.

Auditabilité

Pour les systèmes fondés sur des ontologies où les réponses font l’objet d’une revue :


Partie 8 : Évaluation au niveau du système

Qualité globale de la réponse

LLM-en tant que juge présente de réels biais :

Recette pratique : sélectionnez un juge à partir de données de calibration étiquetées par des humains, aléatoisez l’ordre des réponses, masquez les identités des modèles, et précisez la politique de longueur dans le rubriche. Ne reproduisez les cas que lorsque les échantillons ajoutés réduisent de manière significative l’incertitude. Pour les évaluations à haut enjeu, comparez les juges issus de familles de modèles différentes et analysez les désaccords par rapport aux étiquettes humaines.

Raisonnement guidé par des schémas pour les juges

La sortie de forme libre constitue l’une des sources de variation entre les exécutions effectuées par les juges. Deux exécutions portant sur la même réponse peuvent organiser le critère d’évaluation différemment, ce qui entraîne des notes distinctes. Raisonnement guidé par schéma (SGR) Il convient de formaliser cette grille d’évaluation en définissant les étapes de notation au sein d’un schéma Pydantic, puis d’obtenir une sortie contrainte à l’aide d’Outlines, XGrammar, vLLM structured outputs, ou OpenAI. response_format Ainsi, chaque exécution renvoie les mêmes champs dans le même ordre.

Pour RAG eval, le schéma décompose l’évaluation en champs explicites et auditable, plutôt que de permettre au modèle d’obtenir directement une valeur numérique :

from pydantic import BaseModel, Field
from typing import Literal

class FaithfulnessJudgment(BaseModel):
    extracted_claims: list[str] = Field(
        description="Atomic factual claims in the answer, one per item."
    )
    supported_claims: list[str] = Field(
        description="Subset of extracted_claims that are entailed by the context."
    )
    unsupported_claims: list[str] = Field(
        description="Subset that is NOT entailed by the context."
    )
    failure_mode: Literal[
        "none", "fabrication", "overgeneralization", "wrong_entity", "stale_fact"
    ]
    score: float = Field(ge=0.0, le=1.0)
    rationale: str

Les champs structurés permettent de récupérer la note. len(supported) / len(extracted) Et indiquer précisément quels arguments ont fait l’objet de désaccord entre les deux juges. Le modèle Pydantic permet également de rendre visibles les modifications apportées à la grille d’évaluation sous forme de diff de code. Comme la sortie est contrainte, seules la structure des résultats est garantie et non un jugement objectif ; par conséquent, la randomisation des positions, l’utilisation de juges issus de familles différentes et la calibration humaine restent nécessaires.

Cette approche fonctionne pour tout juge basé sur des critères prédéfinis, et pas uniquement en ce qui concerne la fidélité. Les préférences par paires, le suivi des citations ainsi que la correction des refus bénéficient tous du même traitement.

Un juge G-Eval / par paires / à biais de position / inter-familial harness est implémenté dans Notebook 07; module : evaluation/llm_judge.py. La balayée benchmarkmake benchmark Dans le dépôt), ces composants interconnectent trois modèles de premier plan — gpt-5-mini, claude-haiku-4-5, gemini-2.5-flash — en un test A/B à juges rotatifs par paires, de sorte que chaque modèle évalue les deux autres, ce qui permet d’obtenir une mesure numérique des préférences intrinsèques.

Latence et coût

La valeur p50/p95/p99 par étape, accompagnée d’un détail par étape, est intégrée directement Notebook 08 et le thread d’exécution à evaluation/latency.py; Le rapport benchmark combine la latence et la fidélité au sein d’une même matrice que vous pouvez relancer à volonté make benchmark.

Test A/B


Partie 9 : Construction de l’ensemble de test

Une métrique n’a de valeur que selon l’ensemble de test sur lequel elle est évaluée. Si votre ensemble d’entraînement couvre trois intentions tandis que le trafic en production en comprend douze, la mesure Recall@10 ne prendra en compte que ces trois intentions. Pire encore, un ensemble de test qui surajuste aux questions faciles (« Quelle est la politique de remboursement de l’entreprise ? ») peut valider un système qui échoue face aux cas complexes (« Quelles sont les conditions de remboursement pour une annulation partielle conformément à la loi européenne sur les services numériques de 2023, facturée en EUR et émise depuis l’Irlande ? »). Le score global augmente alors que le système continue de ne pas gérer une partie importante du trafic réel.

Le même problème se pose pour les données de vérité absolue. Si les experts métier annotent les documents évidents mais manquent ceux de la « longue queue » pertinents, le taux de rappel @k sous-estimera la performance d’un système de récupération qui les a réellement trouvés. On optimise donc en fonction des étiquettes, et non en fonction de la vérité elle-même.

Construisez d’abord l’ensemble de test en fonction de la distribution réelle des requêtes ainsi que de leur niveau de difficulté. Ensuite, sélectionnez des métriques adaptées aux modes d’échec ciblés et ajustez le système en conséquence.

Génération de requêtes synthétiques

Utilisez un LLM pour générer des questions à partir de votre corpus :

RAGAS Il dispose d’une répartition intégrée des types de questions (raisonnement, conditionnel, multi-contexte). DataMorgana Il génère des données synthétiques benchmarks configurables, adaptées aux différentes catégories d’utilisateurs et de questions. Les données synthétiques sont utiles pour les démarrages en l’absence de données historiques ainsi que pour les tests de couverture. Elles ne peuvent pas remplacer les requêtes réelles des utilisateurs.

Construction « Golden dataset »

Les données curées par des humains servent de référence pour l’ensemble d’entraînement de référence.

  1. Extraire des requêtes réelles d’utilisateurs (ou simulées en phase pré-lancement) stratifiées selon l’intention.
  2. Faire répondre à chaque question des experts métier afin d’identifier le ou les documents contenant la réponse.
  3. Déterminer la taille de l’ensemble à partir de la matrice de couverture et de l’intervalle de confiance requis pour prendre des décisions de mise en production ; la couverture est plus importante qu’un simple nombre de requêtes collectées.
  4. Réaliser une nouvelle sélection lorsque le rythme de mise en production, les signaux de dérive, les risques liés au domaine et la capacité d’annotation l’exigent.

Ensembles de test adversariaux

Couverture et évaluation continue


Partie 10 : Surveillance en production

L’ensemble d’évaluation que vous déployez décrit le système au moment du lancement. Le trafic en production évolue par la suite.

Retour implicite et explicite

Détection du dérive

Évaluation en mode ombre et human-in-the-loop

Exécutez le système candidat en parallèle avec la version en production, comparez les résultats hors ligne, et n’affichez pas ces derniers aux utilisateurs. Cette approche permet de détecter d’éventuelles régressions avant le lancement. Elle entraîne des coûts supplémentaires en termes de calcul, mais n’a aucun impact sur les clients.

Pour l’examen de human-in-the-loop (HITL) :

L’ensemble minimal de garde-fous

Alerte pour ceux-ci, par ordre de priorité :

  1. Le score de fidélité/HHEM est en deçà du seuil sur un échantillon de production itératif.
  2. La latence p95 dépasse les objectifs de service définis (SLO).
  3. Le taux de faux exclusions filtrées est supérieur au seuil (basé sur des échantillons).
  4. Le taux de régénération se situe en dehors d’une bande de contrôle calibrée localement, tenant compte de la taille de la fenêtre, du trafic, de la saisonnalité et du budget alloué aux alertes fausses.
  5. Le coût par requête dépasse le budget prévu.

Lorsqu’un alerte est déclenchée sans modification correspondante du code ou du modèle, il s’agit très probablement d’une dérive. Si elle est déclenchée après une modification, c’est généralement le signe d’une régression. Dans les deux cas, vous recevez un signal avant même l’arrivée des demandes de support.


Précautions à prendre


Ce qui va suivre dans cette série

Voici l’index. Les actions complémentaires que je prévois :


Références

Frameworks et benchmarks

Récupération et classement

Génération, fidélité, évaluateurs

Dérive et environnement de production

Code d’accompagnement