[!NOTE] Tradução automática Este artigo foi traduzido automaticamente a partir da versão original em inglês.

Avaliação de RAG: Métricas para Cada Etapa de um Sistema RAG em Produção

Parte 1 da série de produção RAG

Um sistema RAG com filtros danificados pode permanecer em funcionamento durante meses sem disparar nenhum alerta operacional. Ele continua a fornecer respostas e a cumprir o seu objetivo de latência, mas essas respostas baseiam‑se em evidências incompletas. A análise de recall@k em relação ao conjunto de referência original revela essa perda; os painéis de latência e disponibilidade, no entanto, não a detetam.

A avaliação só consegue detetar uma falha quando cada fase pipeline dispõe da sua própria métrica. Este artigo estabelece uma correspondência entre os modos de falha mais comuns e essas métricas, abrangendo desde a análise de documentos até à monitorização em produção.

[!TIP] Quer pular para a frente e executar o código?

O código executável slavadubrov/rag-evals-demo o repositório aplica as métricas ao SciFact. make eval executa o conjunto de testes, e make benchmark Compara as configurações de chunking, embedding, e LLM. Os notebooks de 00 a 09 isolam cada uma dessas métricas. A demonstração utiliza o Qdrant integrado, pelo que não requer a utilização do Docker.

TL;DR

As secções seguem a ordem definida pelo pipeline. Comece com a tabela de decisão e, em seguida, utilize as secções seguintes como referência para cada fase.


Tabela de decisão de avaliação RAG

Utilize esta tabela como ponto de partida antes de selecionar um framework. A métrica adequada depende do modo de falha que se pretende detetar, e não do nome da ferramenta.

PerguntaFamília de métricasUtilize isto quandoCuidado com
A análise de sintaxe preservou o conteúdo original?Completude da extração, cobertura de tabelas/figurasPDFs, diapositivos, imagens escaneadas e páginas HTML são inseridos no corpus.Um texto com aparência limpa ainda pode apresentar falhas em legendas, notas de rodapé ou na estrutura das tabelas.
A fase de recuperação encontrou as evidências corretas?Recall@k, nDCG@k, MRR, precisão/recall de contextoPode rotular os trechos ou documentos relevantes.Um filtro rigoroso de metadados pode excluir o documento correto antes mesmo do início da classificação.
Será que o reranking melhorou a lista de candidatos?Reranker aumento, Precisão@1, delta nDCGOs cross-encoders ou classificadores LLM são utilizados após a fase de recuperação.Meça a latência e o custo com o ganho de qualidade
A resposta utilizou as evidências?Fidelidade, fundamentação e suporte à citaçãoA resposta cita documentos ou retira factos do contexto.A fidelidade não consegue diagnosticar uma análise de dados incorreta ou uma recuperação inadequada.
O sistema é estável em produção?Deriva, regeneração, plano de fallback, latência p95, custo por respostaAlterações no tráfego após o lançamentoA telemetria de produção requer revisão humana amostrada para manter a calibração adequada

Para uma comparação mais concisa de ferramentas, consulte Os melhores ferramentas e métricas de avaliação RAG em 2026.

Parte 1: Defina o sucesso antes da arquitetura

Elabore o conjunto de avaliação antes do diagrama de arquitetura. Isso permite definir um objetivo mensurável para cada escolha de componente posterior.

Não é possível escolher entre BM25 e recuperação densa, chunking recursivo e semântico, ou Cohere Rerank e BGE até se saber o que se está a otimizar. “Respostas melhores” não é uma métrica adequada. Um contrato ilustrativo seria “fidelidade ≥ 0,85 num conjunto de referência com 200 consultas que abranja as nossas três principais intenções, com latência p95 < 1,5 s e taxa de exclusão falsa por filtro < 2%”. Os valores numéricos são apenas placeholders; o importante é que a qualidade, a cobertura, a latência e o filtragem tenham critérios explícitos.

Defina o harness antes de escrever o código de recuperação. O primeiro harness estará incorreto, e você terá de o rever. Revisar uma métrica é muito mais barato do que reestruturar um sistema que já foi lançado.

Três camadas pipeline e dois modos de execução

O RAG moderno é um pipeline, pelo que a avaliação tem de ser realizada de forma pipeline. Nenhum valor numérico isolado consegue detetar todos os modos de falha.

A avaliação em produção possui três camadas pipeline. A avaliação de ingestão verifica se o corpus e o índice preservam as características originais dos dados. A avaliação em tempo de consulta analisa se a reescrita, filtragem, recuperação, reranking e montagem do contexto permitiram identificar as evidências corretas. Já a avaliação da resposta e da operação em produção avalia se a resposta utilizou efetivamente essas evidências e se a sua qualidade se mantém sob carga real de tráfego. Ao combinar essas camadas num único valor de pontuação, erros de normalização podem passar despercebidos dentro de um resultado aceitável.

Os três locais onde um sistema RAG pode perder evidências

Essas camadas descrevem onde ocorre uma falha. Os modos offline e online indicam quando e com base em quais dados a verificação é realizada. A avaliação offline utiliza um dataset fixo e com valores de referência conhecidos; sendo reprodutível, é adequada para a seleção de componentes, comparações A/B e verificações em pipelines de integração contínua. Já a avaliação online analisa tráfego em tempo real, capturando fatores como regeneração, tempo de permanência, feedback explícito e deriva nas consultas reais. Esse método gera mais ruído nos resultados e é mais difícil de instrumentar.

Cada camada pipeline pode fornecer verificações tanto offline como online. Um conjunto de dados de ingestão fixo permite detetar regressões no parser antes do lançamento, enquanto monitores de atualização em tempo real e de falhas de análise cobrem as alterações em curso. Um conjunto de consultas fixo avalia o desempenho de recuperação de informações antes do lançamento, ao passo que rastreios em tempo real amostrados revelam desvios na produção. As verificações exclusivamente offline não permitem detetar mudanças em tempo real; por sua vez, as verificações exclusivamente online dificultam a reprodução de regressões.

A nível de componente vs. de ponta a ponta

Existem dois erros comuns. A avaliação apenas de ponta a ponta indica que o sistema está avariado, mas não onde exatamente. Por outro lado, a avaliação apenas de componentes pode mostrar que todas as partes funcionam corretamente, mesmo quando o sistema como um todo continua a falhar. A solução passa por utilizar algumas métricas principais de ponta a ponta para tomar decisões de aceitação ou rejeição, juntamente com métricas de componente para fins de diagnóstico. As métricas de recuperação detetam regressões no módulo responsável pela busca de informação, enquanto as métricas de geração identificam regressões no módulo responsável pela criação de respostas. A correção das respostas em nível de ponta a ponta permite detetar falhas na integração entre os diferentes componentes.

A referência frameworks (tour opinativo)

FrameworkMelhor emOnde ocorrem os problemas
RAGASMétricas RAG sem referência (fidelidade, relevância da resposta, precisão/recall do contexto); o vocabulário de factoLLM – avaliar o custo; componentes de pontuação opacos durante a depuração; predefinições centradas no inglês
ARESO classificador treinado avalia os resultados com base em pipeline; existem menos anotações em comparação com abordagens do estilo RAGAS; alta precisão para sistemas semelhantesConfiguração mais pesada; é necessário efetivamente treinar modelos.
TruLensFunções de feedback compostas com elevada capacidade de explicabilidade; rastreios OpenTelemetry; adequadas para ambiente de produçãoMenos baterias incluídas nas métricas específicas de RAG em comparação com as do RAGAS.
DeepEvalTestes unitários no estilo Pytest para os resultados gerados por LLM; G-Eval, métricas personalizadas, integrados nativamente em pipelines CI/CDUso intensivo de LLM-judge = aumentos significativos nos custos
Arize PhoenixRastreio detalhado e visualização de embedding; deteta visualmente a deriva de embedding; nativo para OTELVocê deve trazer as suas próprias definições de métricas.
Track TREC 2024 RAGPublicação de benchmark para avaliação de nuggets (AutoNuggetizer), suporte à avaliação e medição da fluência no MS MARCO Segment v2.1Não é uma ferramenta runtime, mas sim um benchmark para calibração.

A minha pilha padrão inclui o RAGAS para o vocabulário de métricas, o DeepEval para os gateways de CI, o Phoenix para rastreio em produção, além de código personalizado para métricas específicas da ontologia. Qualquer solução com que comece acabará por se revelar insuficiente. Escolha o framework que facilite a criação de métricas personalizadas.

Para benchmarks, utilize BEIR (Thakur et al., NeurIPS 2021) para a generalização da recuperação zero-shot, MTEB para uma qualidade geral embedding, MIRACL para recuperação multilíngue, e o TREC 2024 RAG Categorização para avaliação end-to-end RAG.


Parte 2: Mapear os pontos de avaliação para o pipeline

Um sistema RAG em produção é muito mais complexo do que apenas “incorporar documentos, recuperar trechos e chamar um LLM”. Cada etapa, desde a aquisição do documento até a entrega da resposta, pode falhar.

O conjunto completo de RAG pipeline, incluindo os indicadores métricos em cada fase do processo

Cada fase no diagrama possui, pelo menos, uma métrica. Uma fase que não tenha nenhuma métrica pode falhar sem que ninguém perceba.

A via de processamento em três etapas corresponde aos locais onde as evidências podem ser perdidas. A via de ingestão abrange a análise estrutural, a limpeza dos dados, o particionamento, embedding, e a indexação. A via de consulta abrange a reescrita das consultas, a filtragem, a recuperação de resultados, reranking, e a montagem do contexto. A via de geração de respostas e produção final abrange a fidelidade às informações, a verificação de citações, os sinais fornecidos pelos utilizadores, a deriva nos parâmetros, a latência e os custos operacionais.

Os erros vão se acumulando ao longo da cadeia de processamento. Uma análise de dados deficiente limita a divisão dos dados em blocos. Uma divisão inadequada dos blocos restringe a recuperação das informações necessárias. Uma recuperação insuficiente limita o reranking. Já um reranking deficiente limita a geração do resultado final. A fidelidade ao conteúdo original avalia apenas a resposta obtida no final, e nunca as causas que ocorreram anteriormente na cadeia de processamento.


Parte 3: Avaliação da ingestão

Muitas falhas em produção relacionadas com RAG começam na fase de ingestão de dados. O sistema funciona corretamente com documentos de teste limpos, mas falha ao lidar com PDFs reais, imagens escaneadas, tabelas e páginas de corpus desorganizadas.

Aquisição e análise de documentos

O que medir:

Compare famílias de analisadores distintas, como, por exemplo, uma linha de base do Tesseract, um modelo OCR baseado em VLM, e a solução proposta pelo seu fornecedor. Utilize uma amostra estratificada de categorias reais de documentos com uma resolução DPI fixa, incluindo digitalizações nítidas, fotografias, tabelas, texto multilíngue, equações matemáticas e escrita à mão. Relate o CER ou o WER para cada categoria, bem como o TEDS para as páginas com tabelas.

Limpeza e normalização

O processamento em blocos controla a qualidade da recuperação de informações

O chunking pode gerar uma lacuna de recuperação em vários pontos, mesmo quando o modelo embedding permanece inalterado. Em O fornecedor de 2024 da NVIDIA benchmark, O particionamento a nível de página gerou a maior precisão e a menor variância para documentos paginados. Trate esse resultado como evidência específica para o corpus testado, e não como uma solução válida para todos os casos.

O agrupamento semântico organiza frases adjacentes com base na semelhança embedding e realiza a separação em pontos de transição onde a similaridade é baixa. O LangChain’s SemanticChunker e o LlamaIndex’s SemanticSplitterNodeParser Implemente esta estratégia. Ela consegue melhorar o recall em janelas fixas quando as fronteiras temáticas são relevantes.

A divisão recursiva de caracteres tenta primeiro quebras de parágrafo, depois quebras de frase e, por fim, quebras de palavra, até que cada fragmento atinja o tamanho alvo. O LangChain’s RecursiveCharacterTextSplitter Implementa a sequência. Escolha os valores de janela e sobreposição adequados à estrutura do seu documento e, em seguida, deixe o conjunto dourado determinar os valores finais.

Métricas a monitorizar:

A minha opinião: chunking estrutural (divisão com base em títulos, tabelas e secções — implementado por analisadores como unstructured.io ou percorrendo a AST já gerada pelo seu analisador) é subutilizado. Se os seus documentos possuem estrutura, utilize-a antes de adicionar heurísticas de similaridade. A divisão recursiva de caracteres constitui o ponto de partida; o agrupamento semântico só justifica o esforço adicional em textos não estruturados.

Extração e enriquecimento de metadados

Geração Embedding

Construção do índice


Parte 4: Avaliação em tempo de consulta

A faixa de tempo de consulta contém as métricas que permitem diagnosticar o percurso de recuperação de dados. O Recall@k, por si só, não consegue indicar se a reescrita, a filtragem, reranking, ou a montagem de contexto foram as causas do falhanço.

Compreensão e reescrita de consultas

Métricas de recuperação

Estas são as métricas de referência. Se não as monitorizar, não será possível determinar se a recuperação de informação está a melhorar.

MétricaO que é medidoQuando utilizar
Recall@kfração de documentos relevantes de uma consulta retornados nos k principaisUtilizar quando a ausência de qualquer componente do conjunto relevante tem impacto significativo
Precisão@kpercentagem dos top-k que são relevantesútil quando a janela de contexto é o gargalo
média de 1/rank do primeiro documento relevantequando os utilizadores analisam apenas os resultados de topo-1 ou topo-3
nDCG@kganho descontado em função da posição, ponderado por graus de relevânciamétrica padrão de recuperação para relevância graduada
MAPmédia de precisão média ao longo das consultasquando se dá importância a toda a lista classificada
Taxa de Acerto@kse pelo menos um documento relevante está presente nos k principaisCalcular a média do resultado binário entre as consultas, como métrica rápida de verificação.
Coberturapercentagem de documentos “dourados” já recuperados em todas as consultasdeteta lacunas sistemáticas no índice

As fórmulas, para referência (relevância binária com o conjunto relevante RqR_q para a consulta qq, e reli=1\text{rel}_i = 1 se o ii-ésimo documento recuperado estiver em RqR_q):

\text{Recall@k} = \frac|R_q ∩ {d_1, …, d_k}|}{|R_q|}, \quad \text{Precisão@k} = \frac|R_q ∩ {d_1, …, d_k}|{k} RRq=1rank da primeira documentac¸a˜o relevante,MRR=1QqQRRq\text{RR}_q = \frac{1}{\text{rank da primeira documentação relevante}}, \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}}

Para a relevância graduada, reli{0,1,2,}\text{rel}_i \in \{0, 1, 2, \dots\}; o nDCG binário é o caso específico utilizado no código abaixo. O MAP corresponde à média, calculada sobre todas as consultas, de APq=1Rqi:reli=1Precision@i\text{AP}_q = \frac{1}{|R_q|}\sum_{i: \text{rel}_i = 1} \text{Precision@}i. Ver Manning, Raghavan, Schütze, Introdução à Recuperação de Informação, capítulo 8 para as derivações.

Para código de produção, utilize ranx, pytrec_eval, ou ir_measures — Eles implementam toda a família de métricas TREC e lidam corretamente com a relevância graduada. Defina objetivos de lançamento com base num conjunto de referência realista, na qualidade das respostas finais e no custo associado a erros. Não herde limiares de tutoriais.

O teste harness para estes casos é breve. Pode executá‑lo a partir de um notebook mesmo antes de escolher uma base de dados vetorial.

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

Esse é o seu ponto de controlo CI para recuperação de dados. Ligue-o a um subconjunto rápido baseado em cobertura em cada PR e execute o conjunto completo “golden” no ponto de controlo de lançamento, que é mais lento. Bloqueie a fusão quando uma métrica pré-registrada ultrapassar o seu orçamento de regressão.

O repositório complementar fixa os valores exatos acima.Recall@5 = 0.750, MRR = 0.625, nDCG@5 = 0.627) como um teste de unidade em tests/test_retrieval_metrics.py; Notebook 01 realiza análises de Recall@k / MRR / nDCG num índice real do SciFact, e o modelo harness adaptado para produção encontra-se em evaluation/retrieval.py.

Fusão híbrida de recuperação e de rank recíproco

BM25 é um pontuador léxico esparsa que combina correspondência de termos exatos, ponderação de termos e normalização de comprimento. Está disponível em rank_bm25, Elasticsearch, OpenSearch e a maioria dos motores de busca.

Fusão de Rank Recíproco (Cormack, Clarke e Buettcher, SIGIR 2009) combina o BM25 com classificações densas por posição. A versão original k=60 A definição de parâmetros constitui uma base útil. O RRF é independente da pontuação, o que evita a normalização entre faixas necessária na interpolação linear. Com um conjunto rotulado suficientemente grande para estimar um valor de delta estável, teste também uma combinação convexa e ajuste o valor de α.

A recuperação híbrida combinada com um codificador cruzado reranker melhora frequentemente corpora técnicos, de logs e de código. O ganho pode ser reduzido em corpora fortemente semânticos. É necessário comparar os resultados com as abordagens que utilizam apenas dados densos ou apenas dados esparsos, uma vez que uma configuração deficiente de fusão pode resultar em desempenho inferior face a qualquer um desses tipos de entrada.

A implementação cabe em poucas linhas.

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

Observe o que o RRF não faz: ele nunca analisa as pontuações de similaridade brutas. Um recuperador denso que retorna um valor de cosseno de 0,98 e um algoritmo BM25 que devolve uma pontuação de 17,4 não são diretamente comparáveis. Se os normalizar com valores z ou através de escala min-máx, pode acabar por favorecer o método com a maior variância nesse lote.

O RRF utiliza apenas a classificação. Se um recuperador colocar um documento na posição 2, esse voto tem valor 1 / (60 + 2)independentemente da pontuação bruta que o gerou.

Hybrido + RRF em SciFact: Notebook 02 compara o método dense com o BM25 e o RRF, utilizando deltas por consulta. O fusor adaptado para produção encontra-se em retrieval/hybrid_rrf.py; tests/test_rrf.py fixa o canónico d3 / d2 / d1 realização de pedidos em 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}")

Um reranker é um candidato com alto potencial para ser utilizado como base num RAG pipeline, mas não representa uma solução garantida de sucesso. Avalie os valores de ΔPrecision@1 e ΔnDCG neste conjunto de referência, mantendo-o apenas se o ganho obtido for suficientemente significativo para compensar o aumento da latência e dos custos envolvidos. Compare esse ganho medido com as alterações menores na recuperação de dados antes de optar pela próxima otimização.

ΔnDCG e ΔPrecision@1 obtidos a partir de um cross-encoder no SciFact: Notebook 03; módulo: retrieval/reranker.py.

Construção de contexto e problema do “perdido no meio”

É aqui que surgem muitos dos falhas do tipo “recuperação adequada, resposta inadequada”.


Parte 5: A taxa de falsa exclusão do filtro

Esta métrica possui uma secção própria porque as pontuações agregadas de recuperação não permitem atribuir um erro ao filtro.

Um filtro de metadados rígido como tenant_id = X AND product = Y AND locale = en-US Pode reduzir o recall efetivo a zero. O Recall@k, quando implementado corretamente, deteta essa perda, uma vez que o seu denominador permanece o conjunto original de documentos relevantes. No entanto, ele não indica se o filtro, o recuperador ou o classificador foram responsáveis pelo erro de recuperação. A fidelidade pode ainda parecer adequada, pois avalia a resposta com base no contexto recuperado, que é incompleto; nesse caso, o modelo respondeu fielmente “Não sei”.

O ramo vermelho na árvore representa a falha mais comum: o documento correto existe, mas o filtro elimina-o antes da sua recuperação.

Taxonomia de falhas silenciosas com a métrica que deteta cada modo

A métrica

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

Esta definição a nível de consulta conta as exclusões catastróficas: não sobra nenhum documento relevante. No caso de consultas multi-gold, o Recall@k padrão ainda revela uma perda parcial; adicione uma taxa de exclusão por documento se esse critério for importante. Para calcular qualquer uma destas taxas, é necessário (a) os IDs dos documentos verdadeiros para cada consulta de avaliação e (b) instrumentação que registe os predicados de filtro aplicados, e não apenas os resultados finais. Defina o objetivo com base no custo de excluir uma resposta válida e no intervalo de confiança da sua amostra de produção.

Aqui está uma implementação funcional. Ela compara o recall padrão correto com um avaliador inválido que redefine a relevância após a filtragem.

# 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

Metade das consultas perde o seu documento de referência devido ao filtro, fazendo com que a taxa correta de Recall@10 caia para 50%. Esse valor identifica o sintoma, mas não consegue atribuí-lo a uma causa específica. A taxa de exclusão falsa indica que o predicado removeu duas respostas antes mesmo de o mecanismo de recuperação ser acionado. O avaliador intencionalmente inválido reporta 100% apenas porque descarta esses falhanços do seu conjunto de referência. Nenhum modelo consegue recuperar um documento que foi filtrado.

A taxa de 50% mencionada acima é reproduzida como um teste unitário no repositório complementar: tests/test_filter_exclusion.py::test_50_percent_exclusion_rate. Notebook 04 roda-o no SciFact com metadados sintéticos, de modo a ser possível observar um filtro real a eliminar completamente a taxa de recall; a métrica runtime (juntamente com as métricas complementares de precisão/predicado e recall) encontra-se em evaluation/filter_exclusion.py.

Métrica complementar: precisão e recuperação do predicado

Quando a filtragem é dinâmica (por exemplo, um LLM extrai os predicados de filtro da consulta), trate o extractor de predicados como um modelo de classificação e avalie-o como tal. Meça a precisão e a recuperação dos predicados com base num conjunto rotulado de (query, correct predicate) pares. Uma taxa de erro preditiva não se traduz diretamente na mesma perda de ponto em termos de recall de recuperação; é necessário medir com que frequência esses erros excluem um documento de referência. Uma vez que um filtro rigoroso elimina o documento de referência, nenhuma quantidade de reranking consegue ajudar.

Reforço suave vs. filtro rígido

Esta métrica obriga a uma decisão de projeto. Devem ser utilizados filtros rígidos quando a correção é binária — como em questões de jurisdição legal, limites de ACL ou entre versões publicadas e em rascunho. Por outro lado, devem ser aplicados reforços suaves quando a relevância é avaliada de forma gradual, tendo em conta preferências de localização, data de atualização ou versão do conteúdo. Sem a medição da taxa de exclusão, é difícil identificar a escolha incorreta.

A regra de decisão, mensurável:

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.

Escolha o valor de ε com base no dano causado por uma exclusão errónea, no benefício da maior precisão obtida e no tamanho da amostra de avaliação. Um artigo específico desta série aborda mais a fundo este equilíbrio.


Parte 6: Avaliação da geração

As métricas de recuperação indicam se o sistema poderia responder corretamente. Elas não indicam se ele de fato o fez. As métricas de geração preenchem essa lacuna.

Fidelidade e ancoragem

Fidelidade RAGAS decompõe a resposta em afirmações atómicas (declarações factuais curtas e autónomas), verificando posteriormente cada uma delas em relação ao contexto recuperado através de um juiz LLM:

fidelidade=afirmac¸o˜es suportadas pelo contextoreclamac¸o˜es totais\text{fidelidade} = \frac{|\text{afirmações suportadas pelo contexto}|}{|\text{reclamações totais}|}

A percentagem de reivindicações suportadas corresponde à pontuação obtida. Esta estrutura é mais útil do que qualquer número isolado, uma vez que indica quais reivindicações não são suportadas. O código de produção encontra-se em ragas package — a utilização é semelhante a:

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)

Abaixo encontra-se a mesma estrutura de loop desdobrada, com um juiz substituto determinístico, para que possa visualizar o fluxo de ponta a ponta.

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)

A estrutura é fundamental. Em produção, verify_claim torna‑se um modelo NLI ou uma chamada ao LLM. O resto do processo de harness permanece inalterado: extrair, verificar, agregar.

Extração e verificação end-to-end de reivindicações em respostas SciFact geradas: caderno de notas 05; módulo: evaluation/faithfulness.py. O repositório também executa um verificador de tipo HHEM entre diferentes famílias de algoritmos no mesmo ciclo, para que possa verificar qual família de juízes concorda com qual.

Uma alternativa desenvolvida especificamente para substituir o LLM-como-jurado HHEM-2.1-Aberto (O Hughes Hallucination Evaluation Model, Vectara), um classificador otimizado para a deteção de alucinações. O seu documento de descrição do modelo detalha o checkpoint, o limite de decisão padrão, bem como os resultados obtidos nos testes AggreFact e RAGTruth. Considere esses dados como evidências contidas no documento do modelo, e não como uma garantia relativa ao seu próprio conjunto de dados: ajuste o limiar com base em etiquetas locais e compare-o com o critério de avaliação escolhido por si antes da implementação.

Avaliação de factos atómicos

FActScore (Min et al., EMNLP 2023) decompõe as gerações de formato longo em factos atómicos, recupera evidências para cada facto e atribui uma etiqueta a cada um deles supported / not-supportede informa a fração suportada:

\text{FActScore} = \frac|\text{factos atómicos suportados}|}{|\text{fatos atómicos totais}|}

Implementação de referência: shmsw25/FActScore. Funciona bem para biografias, resumos e outros tipos de saídas de formato extenso. Cuidado: factos triviais e repetitivos podem elevar a pontuação, e ataques do tipo “MontageLie” (factos verdadeiros apresentados em ordem enganosa) podem anular os seus resultados. VeriScore lida com reivindicações utilizando os modificadores necessários; o Core O filtro ajuda a evitar o preenchimento artificial de dados.

Precisão das citações

Acompanhe a precisão das citações (os trechos citados realmente suportam a afirmação) e o recall das citações (as afirmações que deveriam ser citadas, de fato o são):

\text{cite\_precision} = \frac{|\text{trechos citados que suportam uma afirmação}|}{|\text{intervalos citados}|}, \quad \text{cite\_recall} = \frac|\text{afirmações que contêm pelo menos um trecho citado de suporte}|}{|\text{afirmações que devem ser citadas}|}

A faixa TREC 2024 RAG define um protocolo de avaliação de suporte reprodutível. Upadhyay e colaboradores (SIGIR 2025) Relata-se que o GPT-4o concorda com os juízes humanos em 56% das vezes nas avaliações manuais realizadas a partir do zero, taxa que sobe para 72% após a edição pós-processamento das previsões de LLM. Isso é útil como um fator de amplificação nas condições em que é utilizado, mas não substitui a avaliação humana em contextos de alto risco. Trata-se, portanto, de uma aproximação automatizada. ALCE (Gao et al., EMNLP 2023) implementam precisão/recall de citações através de verificação baseada em NLI.

Correção, completude e recusa da resposta

Verificação pós-geração

Os maiores ganhos em termos de fiabilidade geralmente provêm de verificações pós-processamento determinísticas, e não de modelos mais complexos.


Parte 7: Avaliação baseada em ontologias RAG

As métricas padrão mencionadas acima abrangem o conjunto de dados aberto RAG. Os sistemas baseados em ontologias exigem mais indicadores. Se o seu RAG efetuar buscas com base numa ontologia estruturada, numa taxonomia ou num grafo de conhecimento (produtos num catálogo, condições no SNOMED, componentes numa lista de materiais, técnicas de segurança no MITRE ATT&CK), as métricas padrão RAG são necessárias, mas não suficientes. É igualmente necessário medir o nível da ontologia.

Precisão de ligação de entidades

A primeira tarefa consiste em mapear uma menção numa consulta para uma entidade da ontologia (“Aspirin” → wikidata:Q18216”o 737” aircraft:Boeing_737).

Avaliação com consciência hierárquica

A precisão simples trata o caso em que “previsto Sedan quando a realidade é Hatchback” da mesma forma que o caso em que “previsto Sedan quando a realidade é Submarine”. Esses erros não são equivalentes.

Taxa de filtragem de falsas exclusões (reprise, agora crítica)

Em sistemas baseados em ontologias, os filtros rígidos provêm frequentemente da própria ontologia (“apenas recuperar documentos marcados com a categoria X”). A métrica de taxa de exclusão (definida em Parte 5) Torna‑se um sinal principal de correção. Uma previsão de categoria incorreta pode anular o recall; a taxa de exclusão atribui essa perda ao filtro.

Conformidade de geração condicionada

Quando a sua saída deve estar em conformidade com uma ontologia (cada nome de entidade na resposta tem de ser um membro válido da ontologia; cada predicado tem de provir de um vocabulário fechado), meça:

Constrained decoding frameworks (Esboços, XGrammar, Orientações, OpenAI Structured Outputs) São projetados para garantir a validade do esquema. JSONSchemaBench Compara a eficiência, a cobertura e a qualidade entre as diferentes implementações. Execute novamente os seus casos que correspondem aos seus esquemas e serving backend, uma vez que tanto a cobertura como o tempo de resposta dependem de ambos.

Auditabilidade

Em sistemas baseados em ontologias onde as respostas são sujeitas a revisão:


Parte 8: Avaliação a nível de sistema

Qualidade global da resposta

LLM-como juiz, possui vieses reais:

Receita prática: selecione um avaliador com base em dados de calibração rotulados por humanos, randomize a ordem das respostas, oculte as identidades dos modelos e especifique a política de comprimento na rubrica. Repita os casos apenas quando as amostras adicionadas reduzirem significativamente a incerteza. Em avaliações de alto risco, compare avaliadores de diferentes famílias de modelos e analise as discrepâncias em relação aos rótulos fornecidos por humanos.

Raciocínio Orientado por Esquema para juízes

A saída em formato livre é uma das causas de variação nos resultados obtidos pelos avaliadores. Duas avaliações da mesma resposta podem organizar a rubrica de forma diferente, resultando em pontuações distintas. Raciocínio Guiado por Esquema (SGR) Torna essa rubrica explícita: defina as fases de avaliação como um esquema Pydantic e, em seguida, utilize uma saída restrita através de Outlines, XGrammar, vLLM structured outputs, ou OpenAI response_format Assim, cada execução devolve os mesmos campos na mesma ordem.

Para RAG eval, o esquema degrada o julgamento em campos explícitos e auditáveis, em vez de permitir que o modelo chegue diretamente a um número:

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

Os campos estruturados permitem que a pontuação seja recuperada. len(supported) / len(extracted) E indica exatamente quais afirmações geraram desacordo entre os dois juízes. O modelo Pydantic também torna visível uma alteração na rubrica na forma de um diff de código. A saída condicionada garante apenas a estrutura esperada, e não um veredito imparcial; portanto, a randomização de posições, a utilização de juízes de famílias diferentes e a calibração humana continuam a ser aplicadas.

Isto funciona para qualquer avaliador baseado em rubricas, não apenas no critério de fidelidade. A preferência entre pares, o suporte a citações e a correção de recusas beneficiam todos do mesmo tratamento.

Um avaliador G-Eval / emparelhado / com viés de posição / interfamiliar harness encontra-se em caderno de notas 07; módulo: evaluation/llm_judge.py. A varredura benchmarkmake benchmark no repositório) conecta três modelos de nível avançado — gpt-5-mini, claude-haiku-4-5, gemini-2.5-flash — transformando-o num teste A/B emparelhado com juízes rotativos, de modo que cada modelo avalie os outros dois, revelando numericamente as suas preferências próprias.

Latência e custo

A medição de p50/p95/p99 por fase, juntamente com a detalhamento da estrutura por fases, está integrada diretamente caderno de notas 08 e o executor em evaluation/latency.py; o relatório benchmark combina a latência com a fidelidade numa única matriz que pode ser executada novamente make benchmark.

Teste A/B


Parte 9: Construção do conjunto de teste

Uma métrica só é tão boa quanto o conjunto de teste em que é aplicada. Se o seu conjunto de referência abranger três intenções e o tráfego de produção incluir doze, a Recall@10 irá medir apenas essas três intenções. Pior ainda, um conjunto de teste que sofre sobreajuste com perguntas fáceis (“Qual é a política de reembolso da empresa?”) pode aprovar um sistema que falha nas questões mais difíceis (“Quais são os requisitos para reembolso em caso de cancelamento parcial, nos termos da Lei dos Serviços Digitais da UE de 2023, com faturação em euros e origem na Irlanda?”). A pontuação global aumenta, mas o sistema continua a falhar numa parte importante do tráfego real.

O mesmo problema afeta a verdade real. Se as PME rotularem os documentos óbvios, mas deixarem passar aqueles de cauda longa que são relevantes, o Recall@k atribuirá uma avaliação insuficiente a um recuperador que, na verdade, os encontrou. Otimiza-se em direção aos rótulos, e não em direção à verdade real.

Construa primeiro o conjunto de teste com base na distribuição real das consultas e no seu nível de dificuldade. Em seguida, selecione métricas que reflitam os modos de falha desejados e ajuste o sistema em função deles.

Geração de consultas sintéticas

Utilize um LLM para gerar perguntas a partir do seu corpus:

RAGAS Possui uma distribuição de tipos de questões integrada (raciocínio, condicional, multi-contexto). DataMorgana Gera benchmarks sintético configurável nas categorias de utilizador e de pergunta. Os dados sintéticos são úteis para situações de “cold start” e para testes de cobertura. Não podem substituir as consultas reais de utilizadores.

Construção Golden dataset

Os dados curados por humanos servem de referência fundamental para o conjunto ideal.

  1. Amostras de consultas reais de utilizadores (ou simuladas, se ainda antes do lançamento), estratificadas por intenção.
  2. Fazer com que especialistas respondam a cada pergunta e identifiquem qual(is) documento(s) contém a resposta.
  3. Definir o tamanho do conjunto com base na matriz de cobertura e no intervalo de confiança necessário para tomar decisões de lançamento; a cobertura é mais importante do que o número de consultas obtidas.
  4. Realizar uma nova curadoria sempre que o ritmo de lançamentos, sinais de desvio, riscos do domínio e a capacidade de anotação o justifiquem.

Conjuntos de teste adversariais

Cobertura e avaliação contínua


Parte 10: Monitorização em produção

O conjunto de testes de avaliação que é disponibilizado descreve o sistema no momento do lançamento. O tráfego em produção sofre alterações após esse período.

Feedback implícito e explícito

Deteção de deriva

Avaliação em sombra e human-in-the-loop

Execute o sistema candidato em paralelo com a versão em produção, compare os resultados off-line e não os exiba aos utilizadores. Esta abordagem permite detetar regressões antes do lançamento. Embora implique um custo adicional de inferência, não tem qualquer impacto nos clientes.

Para revisão do human-in-the-loop (HITL):

O conjunto mínimo de restrições de segurança

Aviso sobre estes, por ordem de prioridade:

  1. Pontuação de fidelidade/HHEM abaixo do limiar num conjunto de amostra em produção em tempo real.
  2. Latência p95 acima do SLO.
  3. Taxa de falsa exclusão pelo filtro acima do limiar (baseada em amostras).
  4. Taxa de regeneração fora de uma faixa de controlo calibrada localmente, que leva em conta o tamanho da janela, o tráfego, a sazonalidade e o orçamento de alertas falsos.
  5. Custo/consulta acima do orçamento estabelecido.

Se um alerta for acionado sem uma alteração correspondente no código ou no modelo, é provável que esteja a ocorrer deriva. Se for acionado após uma alteração, é provável que haja regressão. Em qualquer dos casos, recebe-se um sinal antes mesmo de chegarem os pedidos de suporte.


Considerações importantes


O que virá nesta série

Este era o índice. As próximas etapas que estou a planear:


Referências

Frameworks e benchmarks

Recuperação e classificação

Geração, fidelidade, avaliadores

Derivação e produção

Código complementar