Métricas de avaliação de RAG: retrieval, reranking e geração
Tradução automática Este artigo foi traduzido automaticamente a partir da versão original em inglês.
Atualização do artigo
Publicado originalmente a 10 de maio de 2026. Revisto e atualizado a 6 de setembro de 2026. A atualização acrescenta benchmarks de retrieval mais recentes e candidatos a rerankers, revê as recomendações sobre ferramentas de avaliação e corrige os parâmetros da API do exemplo do judge.
Um sistema RAG com filtros de relevância avariados pode funcionar durante meses sem gerar qualquer alerta operacional. Continua a devolver respostas e a cumprir o objetivo de latência, mas as respostas baseiam-se em evidência incompleta. O Recall@k face ao conjunto gold elegível original expõe essa perda. Os dashboards de latência e disponibilidade não.
Para engenheiros que operam ou avaliam sistemas RAG com várias etapas, esta referência relaciona as falhas no parsing de documentos, filtragem, retrieval, reranking e geração com a métrica que identifica cada uma. Mostra também que verificações executar antes do release e quais monitorizam o tráfego em produção.
Quer avançar e executar o código?
O repositório executável
slavadubrov/rag-evals-demoaplica métricas selecionadas ao SciFact.make evalexecuta a suite emake benchmarkcompara configurações de chunking, embedding e LLM. Os notebooks 00–09 abrangem retrieval, filtragem, geração e exemplos de sistemas; não implementam todas as verificações desta referência. A demonstração utiliza Qdrant embebido, pelo que não requer Docker.
O projeto complementar é um harness didático. A revisão analisada a 6 de setembro de 2026 ainda precisa de correções na contabilização de queries em falta, no parsing dos resultados do judge e nos dados gold de autorização. O seu pairwise judge também não inclui o contexto fornecido necessário para avaliar o suporte. Os contratos corrigidos e as verificações inline abaixo não corrigem esse repositório; utilize os notebooks para inspecionar o workflow e valide estes casos antes de adotar os seus scores como critérios de release.
Em resumo
- Uma stack de avaliação útil cobre ingestion, retrieval, grounding da geração, conformidade com a ontologia e sinais do sistema. RAGAS, TruLens, DeepEval, Arize Phoenix e o TREC 2024 RAG Track disponibilizam bibliotecas ou protocolos públicos de avaliação. Não escolhem as suas métricas por si.
- Em RAG baseado em metadata e ontologias, uma tag errada ou um predicado hard frágil pode fazer o recall cair para zero. O Recall@k standard deteta a perda quando mantém o conjunto gold elegível original. Uma métrica de false-exclusion do filtro identifica a causa. A Faithfulness pode continuar a avaliar claims face a contexto incompleto, mas não consegue diagnosticar a causa no filtro ou no retrieval. Uma recusa vazia pode não produzir statements e
NaN, dependendo da implementação.
Tabela de decisão para avaliação de RAG
Use esta tabela como ponto de partida antes de escolher um framework. A métrica certa depende do modo de falha que pretende detetar, não do nome da ferramenta.
| Pergunta | Família de métricas | Utilize quando | Atenção a |
|---|---|---|---|
| O parsing preservou a fonte? | Completude da extração, cobertura de tabelas/figuras | PDFs, slides, scans e páginas HTML entram no corpus | Texto com bom aspeto pode ainda omitir legendas, notas de rodapé ou estrutura de tabelas |
| O retrieval encontrou a evidência certa? | Recall@k, nDCG@k, MRR, precisão/recall de contexto | Pode etiquetar chunks ou documentos relevantes | Um filtro de metadata hard pode remover o documento correto antes de começar o ranking |
| O reranking melhorou a shortlist? | Uplift do reranker, Precision@1, delta de nDCG | Cross-encoders ou LLM rankers são executados após o retrieval | Meça a latência e o custo juntamente com o ganho de qualidade |
| A resposta utilizou a evidência? | Faithfulness, groundedness, suporte de citações | A resposta cita documentos ou afirma factos do contexto | A Faithfulness não consegue diagnosticar parsing ou retrieval incorretos |
| O sistema é estável em produção? | Drift, regeneração, fallback, latência p95, custo por resposta | O tráfego muda após o lançamento | A telemetria de produção precisa de revisão humana amostrada para se manter calibrada |
Para uma comparação mais curta de ferramentas, consulte Melhores ferramentas de avaliação de RAG: Ragas, DeepEval e TruLens.
Parte 1: Defina o sucesso antes da arquitetura
Prepare o conjunto de avaliação antes do diagrama da arquitetura. Assim, cada decisão posterior sobre componentes terá um objetivo mensurável.
Não pode escolher entre BM25 e dense retrieval, chunking recursivo e semântico, ou Cohere Rerank e BGE sem saber o que está a otimizar. “Respostas melhores” não é uma métrica. Um requisito de release ilustrativo seria “faithfulness ≥ 0,85 num golden set de 200 queries que cubra os nossos três principais intents, com latência p95 < 1,5 s e uma taxa de false-exclusion do filtro < 2%”. Os números são placeholders; o importante é que qualidade, cobertura, latência e filtragem tenham limiares explícitos.
Defina o harness antes de escrever o código de retrieval. O primeiro harness estará errado e irá revê-lo. Rever uma métrica é muito mais barato do que rever um sistema que já colocou em produção.
Três camadas do pipeline e dois modos de execução
A avaliação em produção tem três camadas de pipeline. A avaliação de ingestion pergunta se o corpus e o índice preservam a fonte. A avaliação em tempo de query pergunta se o rewriting, a filtragem, o retrieval, o reranking e a montagem do contexto encontraram a evidência certa. A avaliação da resposta e da produção pergunta se a resposta utilizou essa evidência e se a qualidade se mantém com tráfego real. Se colapsar as camadas num único score, um bug de normalização pode desaparecer dentro de um score de resposta aceitável.
Estas camadas descrevem onde ocorre uma falha. Offline e online descrevem quando e face a que dados a verificação é executada. A avaliação offline utiliza um dataset fixo com ground truth conhecido; é reprodutível e deve ser usada na seleção de componentes, em comparações A/B e em verificações de CI que possam bloquear uma alteração. A avaliação online atribui scores a tráfego real amostrado e capta regeneração, tempo de permanência, feedback explícito e drift real das queries. É mais ruidosa e mais difícil de instrumentar.
Utilize ambos os modos onde forem úteis: corpora e conjuntos de queries fixos tornam as regressões reprodutíveis; traces reais amostrados expõem falhas de freshness e drift.
Avaliação ao nível dos componentes vs. end-to-end
Há dois erros comuns. A avaliação apenas end-to-end diz-lhe que o sistema está avariado, mas não onde. A avaliação apenas por componentes pode mostrar que todas as partes passam enquanto o sistema completo continua a falhar. A solução consiste em algumas métricas end-to-end principais para decisões go/no-go, complementadas por métricas de componentes para diagnóstico. As métricas de retrieval detetam regressões no retriever. As métricas de geração detetam regressões no generator. A correção end-to-end da resposta deteta falhas de integração.
Os frameworks de referência (uma análise opinativa)
| Framework | Mais forte em | Limitações |
|---|---|---|
| RAGAS | Um vocabulário comum para faithfulness, answer relevancy e precisão/recall de contexto (métricas) | Custo do LLM judge; componentes do score pouco transparentes ao depurar; alterações de versão |
| ARES | Um classifier judge específico da tarefa, se o treino e a anotação justificarem o custo (artigo); a precisão reportada está limitada ao benchmark | Configuração mais pesada; tem de treinar efetivamente os modelos |
| TruLens | Feedback functions associadas a traces e integração com OpenTelemetry (projeto) | Menos completo em métricas específicas de RAG do que RAGAS |
| DeepEval | Integração com test runners e métricas personalizadas (projeto) | Uso intenso de LLM judges = picos de custo |
| Arize Phoenix | Tracing, experiências com datasets e avaliadores RAG/agent pré-construídos ou personalizados (documentação de avaliação) | As rubricas de domínio e os limiares do judge continuam a precisar de calibração local |
| TREC 2024 RAG Track | Benchmark público para avaliação de nuggets (AutoNuggetizer), avaliação de suporte e fluência em MS MARCO Segment v2.1 | Não é uma ferramenta de runtime; é um benchmark para calibrar |
A minha stack predefinida é RAGAS para o vocabulário de métricas, DeepEval para verificações de CI, Phoenix para tracing em produção e código personalizado para métricas específicas da ontologia. Escolha o framework que facilite a criação de métricas personalizadas.
Para escolher benchmarks, associe primeiro o benchmark à tarefa e só depois consulte a leaderboard. BEIR, MTEB e MIRACL continuam a ser baselines úteis para retrieval. Acrescente testes para capacidades que estes não estabelecem:
- RAG end-to-end atual: o TREC 2026 RAG Track utiliza queries narrativas e ClimbMix-400b, substituindo MS MARCO v2.1, e inclui uma ligação para o toolkit de avaliação RAGDoll. A 6 de setembro, a página do track ainda não anunciou uma data para a devolução dos resultados e dos julgamentos. Os tópicos publicados estão disponíveis para experiências; não constituem uma leaderboard 2026 concluída e avaliada. Mantenha os protocolos de 2024 e 2025 associados aos seus próprios corpora e julgamentos.
- Perguntas técnicas sobre código em evolução: o FreshStack combina perguntas do Stack Overflow colocadas por humanos, corpora de repositórios e julgamentos de nuggets. O snapshot publicado e o mecanismo para criar novos corpora são coisas diferentes; fixe a revisão do repositório e a data da pergunta.
- Imagens que transportam parte da pergunta ou da evidência: o MM-BRIGHT separa retrieval text-to-text, multimodal-to-text, multimodal-to-image e multimodal-to-multimodal. Avalie cada tarefa relevante separadamente. O texto de OCR, por si só, pode omitir o contributo de um gráfico ou screenshot.
Estes benchmarks alargam a cobertura; não substituem o conjunto de queries elegíveis, versionado, da sua aplicação.
Parte 2: Mapeie os pontos de avaliação
Utilize o diagrama para encaminhar um sintoma para a sua primeira métrica de diagnóstico. As perdas a montante limitam a qualidade a jusante: parsing incorreto limita o retrieval e retrieval incorreto limita o reranking e a geração. A Faithfulness mede a resposta, nunca a causa a montante.
Parte 3: Avaliação de ingestion
Muitas falhas de RAG em produção começam na ingestion. O sistema funciona com documentos de teste limpos e depois falha com PDFs, scans, tabelas e páginas de corpus desorganizadas reais.
Aquisição e parsing de documentos
O que medir:
-
Verificação de sanidade do comprimento da extração:
extracted_chars / expected_charspor classe de documento assinala alterações suspeitas no comprimento, mas texto duplicado ou incorreto pode continuar a obter 1,0. Compare o texto alinhado com uma referência limpa manualmente para detetar omissões e substituições; depois verifique separadamente notas de rodapé, legendas, conteúdo de tabelas e ordem de leitura. -
Precisão de OCR: CER (Character Error Rate) e WER (Word Error Rate), as métricas standard de speech/OCR:
onde , e são substituições, eliminações e inserções ao nível dos caracteres, e é a contagem de caracteres da referência (com o subscrito para a versão por palavras). Não aplique o mesmo limiar de CER a todo o corpus. Calibre-o por classe de documento e pela perda de qualidade das respostas a jusante. Texto impresso, manuscrito e material multilingue têm perfis de erro diferentes. Calcule com
jiwer(jiwer.cer(refs, hyps),jiwer.wer(refs, hyps)) ou comevaluateda Hugging Face. Para corpora de avaliação, FUNSD e SROIE são benchmarks públicos.from jiwer import cer, wer refs = ["Mars has two moons, Phobos and Deimos."] hyps = ["Mars has two m00ns, Phobos and Deirnos."] print(f"CER = {cer(refs, hyps):.3f}") # CER = 0.105 print(f"WER = {wer(refs, hyps):.3f}") # WER = 0.286 -
Fidelidade da extração de tabelas: TEDS (Tree-Edit-Distance-based Similarity) mede a proximidade entre a árvore HTML de uma tabela prevista e a referência, normalizada pelo tamanho da maior árvore. De Zhong et al., 2020 (PubTabNet):
O TEDS utiliza tanto a estrutura (linhas, colunas, spans) como o conteúdo das células. O TEDS-S remove o conteúdo e avalia apenas a estrutura. Implementação de referência:
teds.pydo PubTabNet (utilizaaptedinternamente). Para corpora de avaliação, consulte PubTabNet, FinTabNet e SciTSR. Parsers ingénuos falham frequentemente com tabelas. Faça benchmark antes de confiar neles. -
Preservação do layout/estrutura: ordem dos headings, integridade das listas e ordem de leitura em PDFs com várias colunas. Utilize DocLayNet para um benchmark anotado. Uma comparação pronta a usar pode abranger um parser de elementos como
unstructured, uma biblioteca de PDF comopymupdfe um pipeline Docling selecionado. O Docling disponibiliza percursos standard e baseados em VLM; registe qual testa.
Compare famílias de parsers distintas, por exemplo uma baseline com Tesseract, um modelo de OCR baseado em VLM e o candidato do seu fornecedor. Utilize uma amostra estratificada de classes reais de documentos, com DPI fixo, incluindo scans limpos, fotografias, tabelas, texto multilingue, matemática e manuscrito. Reporte CER ou WER para cada classe e TEDS para páginas com tabelas.
Limpeza e normalização
-
Precisão da remoção de boilerplate: precisão/recall face a spans de boilerplate etiquetados por humanos. A remoção agressiva elimina conteúdo relevante; a remoção insuficiente polui os embeddings. Ferramentas a comparar:
trafilatura,jusText,Resiliparse. Barbaresi (2021) compara Trafilatura com baselines que incluem jusText; Resiliparse é um candidato separado e não um sistema avaliado nesse artigo. -
Normalização Unicode: a percentagem de documentos que produzem resultados NFC e NFKC idênticos (calculada com
unicodedata.normalizeda stdlib) é um sinal útil de drift de compatibility form. Não deteta code points invisíveis/default-ignorable nem lookalikes entre scripts: faça uma análise explícita dos primeiros e aplique uma política ou detetor de confusables Unicode quando os segundos estiverem no âmbito. -
Precisão da deteção de idioma: F1 numa amostra multilingue etiquetada. É crítica para índices multilingues. Utilize
fasttext-langdetect(olid.176do Facebook),lingua-pyoucld3. O FLORES-200 disponibiliza texto de avaliação em 200 idiomas, mas é a mistura de idiomas em produção que deve determinar o slice de teste. -
Eficácia da deduplicação (MinHash / LSH): precisão/recall do seu detetor de near-duplicates face a um conjunto etiquetado manualmente. A ideia subjacente é estimar a similaridade de Jaccard entre conjuntos de shingles de documentos através de hashes de permutações aleatórias (Broder, 1997) e agrupar near-duplicates com LSH banding (Indyk & Motwani, 1998). Faça sweep do número de hashes e do limiar de Jaccard no seu corpus. Acompanhe separadamente a taxa de false-merge (que corrompe respostas) e a taxa de missed-merge (que desperdiça espaço no índice).
datasketchdisponibiliza a implementação utilizada abaixo; os parâmetros são ilustrativos:from datasketch import MinHash, MinHashLSH def shingles(text: str, k: int = 5) -> set[str]: text = text.lower() return {text[i:i + k] for i in range(len(text) - k + 1)} def to_minhash(text: str, num_perm: int = 128) -> MinHash: m = MinHash(num_perm=num_perm) for s in shingles(text): m.update(s.encode("utf-8")) return m docs = { "d1": "Mars has two moons, Phobos and Deimos.", "d2": "Mars has two moons, Phobos and Deimos!", # near-dup "d3": "Curiosity rover landed on Mars in 2012.", } lsh = MinHashLSH(threshold=0.8, num_perm=128) for did, text in docs.items(): lsh.insert(did, to_minhash(text)) print(sorted(lsh.query(to_minhash(docs["d1"])))) # ['d1', 'd2'] -
Remoção de PII: precisão e recall, calculados separadamente por tipo de entidade (emails, SSNs, nomes, moradas). Erros de recall criam risco de compliance; erros de precisão prejudicam a qualidade das respostas. Defina o operating point com a equipa jurídica. As ferramentas candidatas incluem Microsoft Presidio,
scrubadubou um modelo NER com fine-tuning num conjunto etiquetado.
O chunking controla a qualidade do retrieval
O chunking altera a evidência que chega à resposta. No benchmark de fornecedores da NVIDIA de 2025, o chunking ao nível da página obteve a maior precisão média de respostas end-to-end na configuração testada, que preservava tabelas e gráficos como unidades completas. Isso mede a precisão da resposta, não o recall do retrieval. Considere esse resultado como evidência para o corpus testado e não como um vencedor universal.
O chunking semântico agrupa frases adjacentes com base na similaridade dos embeddings e corta em fronteiras dissimilares. SemanticChunker do LangChain e SemanticSplitterNodeParser do LlamaIndex implementam esta estratégia. Pode melhorar o recall face a janelas fixas quando as fronteiras temáticas são importantes.
O RecursiveCharacterTextSplitter do LangChain tenta, por predefinição, quebras de linha duplas, quebras de linha simples, espaços e depois caracteres individuais: ["\n\n", "\n", " ", ""]. Não deteta fronteiras de frases, a menos que configure separadores adequados ou utilize um splitter consciente de frases. Escolha valores de janela e overlap adequados à estrutura dos documentos e compare-os no golden set.
Métricas a acompanhar:
- Coerência dos chunks: , onde são embeddings de frases. Chunks saudáveis são internamente semelhantes e diferentes nas fronteiras. Calcule com
sentence-transformerse comcosine_similaritydescikit-learn. - Qualidade das fronteiras: etiquetagem humana de “este corte faz sentido?” numa amostra, além de uma verificação estrutural que assegure que os chunks não dividem tabelas, listas ou secções numeradas.
- Tamanho ótimo do chunk: faça sweep de tamanhos em tokens (128, 256, 512, 1024) e trace Recall@k em função do tamanho no seu golden set. Escolha o knee. Não escolha o valor indicado num tutorial.
- Eficácia do overlap: faça ablation de várias frações de overlap e meça o Recall@k. Pare de aumentar o overlap quando a curva de recall local estabilizar ou o custo da duplicação superar o ganho.
- Fidelidade da atribuição dos chunks: percentagem de chunks que mantêm um ponteiro verificável para a fonte (número da página, âncora da secção, ID do documento). Isto é necessário para a auditabilidade.
- Chunking late vs. early: o late chunking (Günther et al., 2024) cria o embedding do documento completo e só depois o segmenta, preservando o contexto global (implementação de referência em
jina-embeddings-v3). O Contextual Retrieval (Anthropic, 2024) acrescenta a cada chunk contexto gerado por LLM. Ambos aumentam o custo. Faça benchmark no seu corpus antes de adotar qualquer um.
A minha opinião: o chunking estrutural (dividir por headings, tabelas e secções — implementado por parsers como unstructured.io ou percorrendo o AST que o seu parser já produziu) é subutilizado. Se os seus documentos têm estrutura, utilize-a antes de acrescentar heurísticas de similaridade. O recursive character splitting é a baseline; o chunking semântico justifica o overhead sobretudo em prosa não estruturada.
Extração e enriquecimento de metadata
- Precisão/recall/F1 de NER: por tipo de entidade, num subconjunto etiquetado. Standard ao estilo CoNLL/MUC. Calcule com
seqeval(from seqeval.metrics import f1_score) para a versão consciente de tags BIO/IOB, ou com scikit-learn para comparações de conjuntos de spans. CoNLL-2003 e OntoNotes 5.0 são os corpora de referência canónicos. - F1 de relation extraction: ainda mais importante em sistemas baseados em ontologias. Etiquete manualmente um conjunto estratificado por tipo de relação e classe de documento. TACRED e DocRED são benchmarks públicos; as implementações candidatas incluem
opennree pipelines de relation extractionspaCy. - Precisão da extração de títulos/headings: exact match mais similaridade de Levenshtein normalizada () face ao ground truth —
python-Levenshteinourapidfuzzfornecem ambas numa só chamada. - Preservação de metadata hierárquica: percentagem de chunks que retêm corretamente a secção-pai, o documento-pai e o caminho de ancestralidade. Esta é a métrica que decide se o seu RAG consegue responder a perguntas do tipo “o que diz o filho da política X?”.
Geração de embeddings
- Benchmarks de seleção de modelos: utilize os resultados de tarefas de retrieval do MTEB, o BEIR para generalização zero-shot e o MIRACL para retrieval multilingue como pontos de comparação. O retrieval no MTEB reporta habitualmente nDCG@10; outras famílias de tarefas utilizam métricas diferentes. O pacote Python do MTEB executa os benchmarks localmente. Trate a transferência de resultados do MTEB em inglês para uma língua com menos recursos como uma hipótese a testar no conjunto etiquetado dessa língua.
- Avaliação específica do domínio: não trate a posição num benchmark geral como resultado no seu domínio. Dimensione um golden set de domínio a partir da sua matriz de cobertura e da incerteza que a decisão consegue tolerar. Depois faça rerank dos modelos candidatos com
ranxoupytrec_eval. Um conjunto de domínio pode inverter a ordem da leaderboard; publique o slice do dataset, o protocolo de retrieval e o intervalo de confiança juntamente com o resultado. - Deteção de drift dos embeddings: compare uma janela de referência fixa com embeddings em rolling window utilizando MMD ou um classifier de referência-versus-atual validado. A KL necessita de um estimador explícito de distribuição de probabilidade e não pode ser aplicada diretamente a coordenadas brutas de embeddings. Meça também a estabilidade dos nearest neighbors para um conjunto fixo de probes.
evidentlyealibi-detectimplementam detetores baseados em modelos e estatísticos. O estudo comparativo da Evidently é uma avaliação de um fornecedor; compare os métodos em shifts conhecidos nos seus próprios embeddings. - Multi-vector vs. single-vector: a late interaction preserva representações ao nível dos tokens em vez de condensar cada documento num único vetor; ColBERT é o design canónico, com implementações de referência em RAGatouille e PyLate. Essa representação mais rica aumenta o custo do índice e do retrieval. Compare qualidade, armazenamento e latência com uma baseline single-vector no mesmo conjunto de domínio antes de a adotar.
Construção do índice
- Recall@k com aproximação: compare o índice approximate-nearest-neighbour (ANN) com uma baseline exata de brute force no mesmo k — em FAISS, corresponde a
IndexHNSWFlat(ouIndexIVFFlat) vs.IndexFlatIP/IndexFlatL2. Defina a perda de recall aceitável a partir do orçamento de qualidade a jusante. O projetoann-benchmarksacompanha curvas de Pareto recall–QPS entre bibliotecas. - Tuning de HNSW: HNSW (Hierarchical Navigable Small World) é um grafo de proximidade em camadas; consulte Malkov & Yashunin, 2018. É implementado em
hnswlib, noIndexHNSWFlatdo FAISS e na maioria das vector DBs. O HNSW expõe três knobs:M(fan-out do grafo),efConstruction(largura dos candidatos durante a construção) eefSearch(largura dos candidatos durante a query). Comece pelos defaults documentados pela biblioteca e faça sweep dos parâmetros até a curva recall–latência cumprir os requisitos do seu conjunto de avaliação. - Tuning de IVF: IVF (Inverted File index — particiona vetores com k-means em
nlistcélulas e, em tempo de query, pesquisa asnprobecélulas mais próximas; consulteIndexIVFFlateIndexIVFPQdo FAISS). Faça sweep denlistenprobeface ao recall e à latência da pesquisa exata. Faça benchmark de queries filtradas separadamente, porque as famílias de índices e as bases de dados vetoriais implementam a travessia de filtros de forma diferente. - Atraso de freshness das atualizações: tempo entre o commit do documento e a sua disponibilidade para retrieval. Acompanhe p50 e p99. Em sistemas com requisitos regulamentares, acompanhe também a percentagem de queries servidas por índices desatualizados.
Parte 4: Avaliação em tempo de query
O percurso em tempo de query contém as métricas que diagnosticam um caminho de retrieval. O Recall@k, por si só, não mostra se a causa da falha foi o rewriting, a filtragem, o reranking ou a montagem do contexto.
Compreensão e rewriting de queries
- Qualidade da query expansion: uplift de Recall@k no seu golden set, entre a query expandida e a query original. Defina antecipadamente o ganho mínimo útil e a sua incerteza. Se a expansão não cumprir esse requisito, não justifica a latência e o custo. As baselines clássicas de PRF (pseudo-relevance feedback), como RM3 e Bo1, continuam a ser verificações de sanidade úteis; a expansão baseada em LLM deve superá-las.
- Avaliação de HyDE: o HyDE (Gao et al., 2022) gera uma resposta hipotética com o LLM, cria o seu embedding e faz retrieval contra esse embedding. Acrescenta latência de geração e uma nova superfície de falha. Meça o Recall@10 separadamente em slices in-domain, out-of-domain e de baixa confiança; depois decida se pertence ao percurso predefinido, a um fallback ou a nenhum dos dois.
- Geração multi-query: união do Recall@k de N rewrites vs. uma única query. Faça sweep de N e escolha um ponto na sua fronteira recall–latência. Implementações:
MultiQueryRetrieverdo LangChain eQueryFusionRetrieverdo LlamaIndex. - Precisão da classificação de intent: precisão/recall/F1 standard por intent (calcule com
sklearn.metrics.classification_report), mas a métrica operacional é a correção do routing — é invocado o pipeline downstream correto? - Routing adaptativo: o Adaptive-RAG (Jeong et al., NAACL 2024) defende que nem todas as queries merecem a mesma estratégia de retrieval. Acompanhe a precisão do router como um problema de classificação contra um conjunto etiquetado de “não precisa de retrieval / one-shot / iterativa”.
A pesquisa iterativa precisa de um teste end-to-end com orçamento
Um agent pode pesquisar, inspecionar um resultado e pesquisar novamente, em vez de fazer retrieval de uma lista top-k fixa. O tau3 knowledge domain atual expõe RAG configurável e pesquisa agentic baseada em shell, tornando este um caminho concreto de avaliação e não apenas um esboço de arquitetura. Para uma comparação local, dê ao retrieval one-shot e iterativo o mesmo corpus elegível e limites explícitos de tempo, tokens do modelo e tool calls. Registe cada query e a evidência observada em cada turno; avalie a cobertura final da evidência, a correção da resposta, o suporte das citações e as falhas por esgotamento do orçamento. Mais chamadas de pesquisa só são úteis quando a evidência adicional melhora a resposta dentro desses limites.
Métricas de retrieval
Estas são as métricas baseline. Se não as acompanhar, não consegue saber se o retrieval está a melhorar.
| Métrica | O que mede | Quando utilizar |
|---|---|---|
| Recall@k | fração dos documentos relevantes de uma query devolvidos no top k | utilize quando perder qualquer parte do conjunto relevante for importante |
| Precision@k | percentagem do top-k que é relevante | útil quando a context window é o bottleneck |
| MRR | média de 1/rank do primeiro documento relevante | quando os utilizadores só consultam o top-1 ou top-3 |
| nDCG@k | ganho descontado pela posição e ponderado pelos graus de relevância | métrica standard de retrieval para relevância graduada |
| MAP | média, entre queries, da average precision | quando importa a lista ordenada completa |
| Hit Rate@k | se aparece pelo menos um documento relevante no top k | faça a média do resultado binário entre queries como verificação de sanidade rápida |
| Coverage | percentagem de documentos gold alguma vez recuperados em todas as queries | deteta lacunas sistemáticas no índice |
As fórmulas, para referência (relevância binária com conjunto relevante para a query e se o documento recuperado na posição estiver em ):
Para relevância graduada, ; o nDCG binário é o caso especial utilizado no código abaixo. MAP é a média, entre queries, de . Consulte Manning, Raghavan, Schütze, Introduction to Information Retrieval, capítulo 8, para as derivações.
Para código de produção, utilize ranx, pytrec_eval ou ir_measures — implementam toda a família de métricas TREC e tratam corretamente a relevância graduada. Defina objetivos de release com base num golden set realista, na qualidade downstream da resposta e no custo de uma falha. Não herde limiares de um tutorial.
Aqui, k conta IDs de documentos únicos. Rejeite duplicados em vez de dar ganho adicional a um documento repetido. Para experiências de chunking, declare se k conta chunks ou documentos-pai deduplicados e compare também a evidência fornecida dentro de um orçamento fixo de tokens. O mesmo recall de documentos pode esconder qualidades de contexto muito diferentes.
O harness de teste é curto. Pode executá-lo a partir de um notebook antes de escolher sequer uma vector database.
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 k <= 0 or len(ranked) != len(set(ranked)):
raise ValueError("require positive k and unique document IDs")
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):
if len(ranked) != len(set(ranked)):
raise ValueError("require unique document IDs")
# 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):
if k <= 0 or len(ranked) != len(set(ranked)):
raise ValueError("require positive k and unique document IDs")
# 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.get(q, []), gold[q], K) for q in gold):.3f}")
print(f"MRR: {mean(reciprocal_rank(runs.get(q, []), gold[q]) for q in gold):.3f}")
print(f"nDCG@{K}: {mean(ndcg_at_k(runs.get(q, []), gold[q], K) for q in gold):.3f}")
# Recall@5: 0.750
# MRR: 0.625
# nDCG@5: 0.627
assert recall_at_k([], {"d1"}, 5) == 0.0
for score in (lambda ids: recall_at_k(ids, {"d1"}, 2),
lambda ids: reciprocal_rank(ids, {"d1"}),
lambda ids: ndcg_at_k(ids, {"d1"}, 2)):
try:
score(["d1", "d1"])
except ValueError:
pass
else:
raise AssertionError("duplicate IDs must fail")
O aggregate percorre todas as queries esperadas, tratando uma execução em falta como vazia. Este exemplo atribui zero a queries com gold vazio; numa suite real, marque-as como um slice de answerability separado, com o seu próprio denominador, em vez de tratar o zero como recall medido. Reporte timeouts e execuções em falta, além dos scores.
Execute um subconjunto rápido orientado por coverage em cada PR e o golden set completo antes do release. Bloqueie um merge quando uma métrica previamente registada ultrapassar o seu orçamento de regressão.
O repositório complementar fixa os números exatos acima (Recall@5 = 0.750, MRR = 0.625, nDCG@5 = 0.627) como um teste unitário em tests/test_retrieval_metrics.py; o notebook 01 faz sweep de Recall@k / MRR / nDCG sobre um índice SciFact real e o harness com formato de produção está em evaluation/retrieval.py.
Retrieval híbrido e reciprocal rank fusion
O BM25 é um scorer lexical esparso que combina correspondência de termos exatos, ponderação de termos e normalização pelo comprimento. Está disponível em rank_bm25, Elasticsearch, OpenSearch e na maioria dos motores de pesquisa.
A Reciprocal Rank Fusion (Cormack, Clarke e Buettcher, SIGIR 2009) combina rankings BM25 e dense pela posição. A configuração original k=60 é uma baseline útil. A RRF é agnóstica aos scores, evitando a normalização entre percursos necessária na interpolação linear. Com um conjunto etiquetado suficientemente grande para estimar um delta estável, teste também uma combinação convexa e faça tuning de α.
A minha hipótese é que retrieval híbrido mais um reranker cross-encoder pode ajudar em corpora técnicos, de logs e de código. O ganho pode ser pequeno em corpora fortemente semânticos. Meça face aos percursos dense-only e sparse-only, porque uma configuração de fusion fraca pode ter desempenho inferior ao de qualquer uma das entradas. O notebook SciFact complementar é um teste limitado, não um resultado geral.
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)
if k <= 0:
raise ValueError("k must be positive")
for ranking in rankings:
if len(ranking) != len(set(ranking)):
raise ValueError("each ranking must contain unique document IDs")
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
assert [doc for doc, _ in fused[:3]] == ["d3", "d2", "d1"]
try:
rrf([["d1", "d1"]])
except ValueError:
pass
else:
raise AssertionError("one lane must not vote twice for a document")
Repare no que a RRF não faz: nunca consulta os scores brutos de similaridade. Um retriever dense que devolva cosine 0,98 e um percurso BM25 que devolva o score 17,4 não são diretamente comparáveis. A normalização z-score e min-max remove diferenças de escala afins, mas nenhuma delas calibra um score como relevância. Outliers, forma da distribuição e conjunto de candidatos continuam a afetar os valores normalizados e, consequentemente, o resultado da fusion. Valide qualquer combinação baseada em scores com queries etiquetadas.
A RRF utiliza apenas o rank. Se um retriever colocar um documento na posição 2, esse voto vale 1 / (60 + 2), independentemente do score bruto que o produziu.
Hybrid + RRF no SciFact: o notebook 02 compara dense vs. BM25 vs. RRF com deltas por query. O fuser com formato de produção está em retrieval/hybrid_rrf.py; tests/test_rrf.py fixa a ordenação canónica d3 / d2 / d1 em k=60.
Reranking
- ΔnDCG / ΔMRR: uplift face a não fazer rerank, no seu golden set e à profundidade efetivamente utilizada pela aplicação. Calcule as métricas de retrieval com e sem o reranker sobre conjuntos de candidatos idênticos.
- Cross-encoder vs. bi-encoder: um bi-encoder cria o embedding da query e do documento independentemente (um vetor por lado) e calcula o score através do produto escalar; um cross-encoder concatena query+documento e executa um único forward pass que atende conjuntamente a ambos. Os cross-encoders trocam um forward pass por candidato por uma interação query–documento mais rica. Implementação de referência:
sentence-transformersCrossEncoder. Faça benchmark de relevância e latência em hardware, batch size e profundidade de candidatos definidos; não transfira o resultado de um modelo ou serviço gerido para outro ambiente. - Listwise vs. pointwise: o pointwise atribui score a cada par (query, documento) independentemente; o listwise avalia conjuntamente toda a lista de candidatos, permitindo ao modelo comparar candidatos. Avalie ambos nos mesmos conjuntos de candidatos. Calibre qualquer limiar de score por modelo e corpus, em vez de tratar um exemplo publicado como portátil.
from sentence_transformers import CrossEncoder
reranker = CrossEncoder("BAAI/bge-reranker-v2-m3") # Reproducible baseline, not a latest-model ranking
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}")
O código BGE acima é uma baseline pequena. Para uma comparação atual, inclua Cohere Rerank 4.0 fast/pro para ranking de texto multilingue gerido, ou Qwen3-VL-Reranker 2B/8B quando as queries ou os documentos incluírem imagens, screenshots ou vídeo. Mantenha explícitos as modalidades da tarefa, a profundidade dos candidatos, os limites de input, as instruções e o hardware. Um checkpoint de geração Qwen3.8 geral não é o mesmo modelo que o reranker Qwen3-VL especializado.
Um reranker ajuda frequentemente um pipeline RAG básico, mas não é uma melhoria garantida. Meça o seu ΔPrecision@1 e ΔnDCG no golden set e mantenha-o apenas se o ganho superar o orçamento de latência e custo. Compare esse ganho medido com alterações menores no retrieval antes de escolher a próxima otimização.
ΔnDCG e ΔPrecision@1 de um cross-encoder no SciFact: notebook 03; módulo: retrieval/reranker.py.
Construção do contexto e lost-in-the-middle
Muitas falhas de “bom retrieval, má resposta” começam na construção do contexto.
- Relevância do contexto: Ragas
ContextRelevanceavalia a lista de contexto fornecida utilizando dois prompts e classificações normalizadas; não é um score por chunk. Para diagnóstico ao nível dos chunks, avalie explicitamente cada par query–chunk, por exemplo com um cross-encoder, e reporte a distribuição sob um limiar calibrado localmente. - Cobertura de citações do contexto fornecido: chunks fornecidos distintos citados divididos por chunks fornecidos distintos. Reporte separadamente os casos de contexto vazio. Este proxy observável indica que chunks receberam citações, não quais foram utilizados internamente pelo modelo nem se as citações suportam as suas claims. Verifique o suporte das citações separadamente e compare a cobertura com a qualidade da resposta e o custo em tokens.
- Deteção de lost-in-the-middle: avaliação sintética em que coloca o chunk gold nas posições {primeira, intermédia, última} de um contexto longo e mede a correção da resposta. O estudo citado de Liu et al. (TACL 2024) reporta degradação em U nas suas condições de contexto longo. Trate o mesmo padrão num modelo atual como uma hipótese a testar. Mitigações: faça rerank e depois reordene o top-k para que o chunk com maior score fique primeiro ou último (o
LongContextReorderdo LangChain faz exatamente isto) ou comprima agressivamente os chunks intermédios. Meça com uma avaliação estratificada por posição, não apenas com um score agregado. Existe uma avaliação executável e detalhada estratificada por posição no notebook 06 (módulo:evaluation/lost_in_middle.py). - Compressão do contexto: reporte o compression ratio (tokens de input / tokens de output) juntamente com a correção da resposta. As ferramentas incluem
ContextualCompressionRetrieverdo LangChain e LongLLMLingua. Defina antecipadamente a maior perda aceitável de correção com base no risco da aplicação e no orçamento de tokens; depois rejeite configurações que a ultrapassem.
Parte 5: Taxa de false-exclusion do filtro
Esta métrica tem uma secção própria porque os scores agregados de retrieval não conseguem atribuir uma falha a um filtro de relevância. Avalie os filtros de elegibilidade separadamente face às permissões do caller; um documento fora desse conjunto deve continuar excluído.
Um predicado de relevância hard como product = Y AND locale = en-US pode fazer o recall efetivo cair para zero entre os documentos a que o caller tem direito de acesso. Um Recall@k corretamente implementado deteta a perda porque o denominador continua a ser o conjunto original de documentos relevantes elegíveis. Não indica se a causa foi o filtro, o retriever ou o ranker. A Faithfulness avalia claims face ao contexto recuperado. Pode continuar a atribuir um score a claims suportadas por esse contexto incompleto, mas não consegue diagnosticar a causa no filtro ou no retrieval. Uma recusa vazia pode não produzir statements e NaN, dependendo da implementação; não a trate como evidência de que a faithfulness aprovou a recusa.
O ramo destacado é a falha comum: o documento certo existe, mas o filtro remove-o antes do retrieval. O Recall@k regista a queda; apenas a taxa de exclusão atribui a causa ao predicado.
A métrica
filter_false_exclusion_rate =
(# queries where all gold docs were excluded by metadata filter) /
(# queries with at least one entitlement-eligible gold doc)
Esta definição ao nível da query conta exclusões catastróficas: nenhum documento relevante elegível sobrevive. Faça a interseção do conjunto gold de cada query com o conjunto de permissões do caller antes de avaliar; um documento inelegível não é uma false exclusion. Para queries com vários golds, o Recall@k standard continua a expor a perda parcial; acrescente uma taxa de exclusão por documento se essa fronteira for importante. Para calcular qualquer uma das taxas, precisa de (a) IDs de documentos ground truth para cada query de avaliação e (b) instrumentação que registe os predicados de filtro aplicados, 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 amostra de produção.
Eis uma implementação funcional. Compara o recall standard correto com um evaluator inválido que redefine a relevância depois da filtragem.
# A small worked example where relevance predicates remove eligible 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"},
{"id": "d6", "tenant": "acme", "locale": "fr-FR"},
]
# Tenant isolation is an eligibility invariant, not a relevance experiment.
# Verify it independently before evaluating relevance preferences.
eligible_docs = [d for d in docs if d["tenant"] == "acme"]
assert {d["id"] for d in eligible_docs} == {"d1", "d2", "d4", "d5", "d6"}
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 lives in fr-FR but the dynamic filter forced en-US
{"qid": "q3", "gold": {"d6"}, "filter": lambda d: d["locale"] == "en-US"},
# 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, eligible_docs):
n_with_gold, n_excluded = 0, 0
eligible_ids = {d["id"] for d in eligible_docs}
for q in queries:
eligible_gold = q["gold"] & eligible_ids
if not eligible_gold:
continue
n_with_gold += 1
survivors = {d["id"] for d in eligible_docs if q["filter"](d)}
if not (eligible_gold & survivors):
n_excluded += 1
if not n_with_gold:
raise ValueError("false-exclusion rate needs queries with eligible gold")
return n_excluded / n_with_gold, n_with_gold
rate, eligible_query_count = filter_false_exclusion_rate(queries, eligible_docs)
print(f"filter_false_exclusion_rate = {rate:.2%}")
# filter_false_exclusion_rate = 50.00%
print(f"eligible queries = {eligible_query_count} / {len(queries)}")
# eligible queries = 4 / 4
# Correct Recall@k keeps the original eligible gold set as its denominator.
def standard_recall_at_k(queries, eligible_docs, k=10):
recalls = []
eligible_ids = {d["id"] for d in eligible_docs}
for q in queries:
eligible_gold = q["gold"] & eligible_ids
if not eligible_gold:
continue
# demo only: the survivor set stands in for a ranked run.
# A real harness ranks the survivors first, then slices to k.
survivors = [d for d in eligible_docs if q["filter"](d)][:k]
survivor_ids = {d["id"] for d in survivors}
recalls.append(len(eligible_gold & survivor_ids) / len(eligible_gold))
return sum(recalls) / len(recalls) if recalls else 0.0
print(f"standard recall@10 = {standard_recall_at_k(queries, eligible_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, eligible_docs, k=10):
recalls = []
all_doc_ids = {d["id"] for d in eligible_docs}
for q in queries:
all_survivors = {d["id"] for d in eligible_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, eligible_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, eligible_docs) == 0.5
assert invalid == 1.0
# An unauthorized-only gold document belongs in an entitlement test, not this metric.
unauthorized_only = [
{"qid": "q5", "gold": {"d3"}, "filter": lambda d: d["locale"] == "en-US"},
]
assert eligible_query_count == 4
assert filter_false_exclusion_rate(queries + unauthorized_only, eligible_docs) == (rate, 4)
for unscorable in ([], unauthorized_only):
try:
filter_false_exclusion_rate(unscorable, eligible_docs)
except ValueError:
pass
else:
raise AssertionError("an unmeasured exclusion rate must not pass as zero")
A função devolve a taxa e o número de queries com gold elegível. Se esse denominador estiver vazio, gera uma exceção em vez de reportar um zero tranquilizador; uma verificação de release obrigatória deve tratar isso como evidência em falta. Queries cujo gold seja exclusivamente não autorizado permanecem em testes de permissões separados.
Metade das queries perde o documento gold para o filtro, pelo que o Recall@10 correto cai para 50%. Esse score deteta o sintoma, mas não consegue atribuir-lhe a causa. A taxa de false-exclusion mostra que o predicado removeu duas respostas antes de o retriever ser executado. O evaluator deliberadamente inválido reporta 100% apenas porque elimina essas falhas do seu conjunto gold. Nenhum modelo consegue recuperar um documento que foi filtrado.
A taxa de 50% acima é reproduzida como teste unitário no repositório complementar: tests/test_filter_exclusion.py::test_50_percent_exclusion_rate. O Notebook 04 executa-o no SciFact com metadata sintética para que possa observar um filtro real reduzir o recall a zero; a métrica de runtime (com a métrica complementar de precisão/recall do predicado) está em evaluation/filter_exclusion.py.
Métrica complementar: precisão e recall do predicado
Quando a filtragem é dinâmica (por exemplo, quando um LLM extrai predicados de filtro da query), trate o extrator de predicados como um modelo de classificação e avalie-o como tal. Meça a precisão e o recall dos predicados contra um conjunto etiquetado de pares (query, correct predicate). Uma taxa de erro do predicado não corresponde diretamente à mesma perda pontual no recall de retrieval; meça a frequência com que esses erros excluem um documento gold. Assim que um filtro hard remove o documento gold, nenhum reranking consegue ajudar.
Filtros de elegibilidade vs. preferências de relevância
Autorização, isolamento de tenant, jurisdição legal e estado de publicação determinam se um documento pode entrar no conjunto de candidatos. Mantenha-os como filtros hard e valide-os independentemente; Recall@k, false-exclusion rate e precisão de relevância não autorizam o seu relaxamento.
Para uma preferência de relevância como locale, recência ou versão, compare um predicado hard com um boost soft nas mesmas queries elegíveis held-out:
For each relevance preference 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
precision_gain = hard_precision - soft_precision
precision_gain_CI = paired-query uncertainty interval for precision_gain
exclusion_rate = % of queries where the gold doc was filtered out (hard)
Pre-register Δ_min, ε, and recall_loss_max for this workflow.
Use a hard predicate only if:
lower_bound(precision_gain_CI) >= Δ_min
AND exclusion_rate <= ε
AND hard_recall - soft_recall >= -recall_loss_max.
Otherwise prefer a soft boost.
Escolha o ganho mínimo útil de precisão, o intervalo de incerteza, ε e o limite de perda de recall com base no dano de excluir uma resposta que, de outro modo, seria elegível, no benefício de aumentar a precisão e no tamanho da amostra held-out. São critérios de release locais, não limiares universais. Está planeado um artigo dedicado a este trade-off; consulte os follow-ups no final.
Parte 6: Avaliação da geração
As métricas de retrieval dizem-lhe que o sistema poderia responder corretamente. Não dizem que respondeu. As métricas de geração cobrem essa lacuna.
Faithfulness e groundedness
A faithfulness do RAGAS decompõe a resposta em claims atómicos (statements factuais curtos e autónomos) e verifica cada um face ao contexto recuperado através de um LLM judge:
A Faithfulness verifica o suporte na evidência fornecida; não estabelece que a evidência esteja correta ou seja suficiente para responder à pergunta. Registe separadamente as respostas sem claims e reporte a sua contagem, em vez de atribuir faithfulness perfeita a uma resposta vazia.
A documentação atual do Ragas recomenda a collections API abaixo. Num projeto uv, instale ragas e openai com uv add ragas openai, defina OPENAI_API_KEY e guarde isto como um script para executar com uv run. O script faz chamadas ao provider e incorre nos respetivos custos; o score é um resultado de judge, não uma constante esperada determinística. Fixe as dependências resolvidas no lockfile.
import asyncio
from openai import AsyncOpenAI, omit
from ragas.llms import llm_factory
from ragas.metrics.collections import Faithfulness
async def main():
async with AsyncOpenAI() as client:
llm = llm_factory(
"gpt-5.6-terra", client=client, reasoning_effort="none",
max_tokens=omit, max_completion_tokens=1024,
temperature=omit, top_p=omit,
)
scorer = Faithfulness(llm=llm)
result = await scorer.ascore(
user_input="How many moons does Mars have?",
response="Mars has two moons, Phobos and Deimos.",
retrieved_contexts=["Mars has two moons named Phobos and Deimos."],
)
print(result.value)
if __name__ == "__main__":
asyncio.run(main())
Este exemplo utiliza GPT-5.6 Terra como candidato atual com structured outputs; a factory do Ragas encaminha os argumentos do modelo. O Ragas 0.4.3 não reconhece nomes de geração GPT com pontos no seu token-limit mapper. O exemplo omite explicitamente o seu max_tokens legado e os defaults de sampling e fornece max_completion_tokens; verifique o request enviado ao atualizar o adapter. Desligar o reasoning torna a configuração explícita, mas não validada. Compare false passes, false failures e custo do judge com labels humanos antes de substituir um judge calibrado mais barato.
Segue-se o mesmo loop expandido com um judge determinístico de substituição, para que possa observar a forma end-to-end.
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) if verdicts else None
for c, ok in verdicts:
print(f" [{'✓' if ok else '✗'}] {c}")
print(f"faithfulness = {faithfulness:.2f}" if faithfulness is not None else "no_claims")
# faithfulness = 0.75 (one unsupported claim about the atmosphere)
assert faithfulness == 0.75
assert extract_claims("") == []
A estrutura é importante. Em produção, verify_claim torna-se um modelo NLI ou uma chamada a um LLM. Mantenha a estrutura extract–verify–aggregate, mas valide separadamente a extração e o entailment e registe julgamentos sem claims ou falhados. O stand-in offline acima está hard-coded para estes exemplos; não é um detetor de factualidade.
Extração e verificação end-to-end de claims em respostas SciFact geradas: notebook 05; módulo: evaluation/faithfulness.py. O repositório executa o mesmo loop através de duas famílias de judges — o próprio modelo do generator e um judge de outra família (RAG_EVALS_JUDGE_MODEL) — além de uma baseline lexical determinística, para que possa observar onde as famílias discordam.
Uma alternativa concebida especificamente para LLM-as-judge é o HHEM-2.1-Open (Hughes Hallucination Evaluation Model, Vectara), um classifier com fine-tuning para deteção de hallucinations. O model card documenta o checkpoint, o score bruto entre 0 e 1 que emite e os resultados de balanced accuracy em AggreFact e RAGTruth. Não publica um limiar de decisão predefinido, pelo que essa escolha é sua. Trate esses valores como evidência do model card e não como garantia para o seu corpus: calibre o limiar com labels locais e compare-o com o judge escolhido antes da implementação.
Avaliação de factos atómicos
O FActScore (Min et al., EMNLP 2023) decompõe gerações long-form em factos atómicos, recupera evidência para cada facto, etiqueta cada um como supported / not-supported e reporta a fração suportada:
Implementação de referência: shmsw25/FActScore. Funciona bem para biografias, resumos e outros outputs long-form. Atenção: factos triviais repetitivos podem inflacionar o score e statements individualmente verdadeiros podem formar uma resposta enganadora. O MontageLie (EMNLP 2025) testa esta fraqueza através de relações e ordenações enganadoras entre statements verdadeiros. O VeriScore trata claims com modificadores necessários; o filtro Core ajuda a evitar fact-padding.
Precisão das citações
Acompanhe a precisão das citações (os spans citados suportam efetivamente a claim) e o recall das citações (as claims que deveriam ser citadas são citadas):
O TREC 2024 RAG Track define um protocolo reproduzível de support evaluation. Thakur et al. (SIGIR 2025) reportam cerca de 56% de concordância com julgamentos humanos feitos de raiz e 72% noutra condição em que os humanos editaram posteriormente as previsões do LLM. Esta última é anotação assistida, não evidência independente de uma melhoria na precisão do judge. Mantenha a condição de anotação associada ao número. Para uma aproximação automática, o ALCE (Gao et al., EMNLP 2023) implementa precisão/recall de citações com verificação baseada em NLI.
Correção, completude e recusa da resposta
- Correção da resposta face a uma referência: exact match ou token-F1 podem ser adequados para respostas curtas. Para respostas longas, verifique relações factuais, entidades, quantidades, negação e informação obrigatória face a referências revistas. BERTScore e cosine de embeddings medem similaridade; um número ou uma negação errados podem manter um score elevado. O
AnswerCorrectnessdo Ragas combina comparação factual com similaridade, em vez de as equiparar. - Completude através de nuggets: um nugget é uma unidade de informação relevante, distinguindo-se nuggets vitais de outros úteis mas opcionais. Uma pergunta sobre a data de fundação pode exigir o ano; o nome do fundador não é automaticamente obrigatório. O AutoNuggetizer constrói e aperfeiçoa nuggets a partir de pools de documentos avaliados e verifica depois a sua presença nas respostas geradas. O seu relatório TREC 2024 inicial abrangia 21 tópicos e 45 runs. O overview do TREC 2025, publicado em março de 2026, alarga o protocolo a queries narrativas e avalia relevância do retrieval, completude da resposta e attribution. Estes são protocolos públicos de avaliação, não evidência de que todos os sistemas RAG em produção precisem da mesma rubrica de nuggets.
- Comportamento de recusa: etiquete se a evidência fornecida permite responder e depois meça as recusas corretas entre todas as recusas e as recusas entre os casos em que o sistema deveria abster-se. O NoMIRACL (Findings of EMNLP 2024) testa a robustez face a passagens fornecidas relevantes e não relevantes; não prova que o corpus inteiro não tenha resposta. Separe falhas de retrieval de queries genuinamente fora do âmbito na sua própria suite.
Verificação pós-geração
Os ganhos de fiabilidade mais baratos surgem frequentemente de verificações determinísticas posteriores, não de modelos maiores.
-
Sinalização de entidades não observadas: registe strings de entidades nomeadas na resposta que estejam ausentes de uma string de contexto normalizada (por exemplo,
spaCy’sentsmais matching exato). É um sinal barato e específico do domínio para strings de entidades não observadas, não uma verificação de grounding: não consegue estabelecer identidade, relação, negação, tempo ou proveniência. Meça a sua precisão e recall com labels locais antes de o utilizar para aprovar um release e mantenha entailment ao nível das claims ou revisão humana para a verificação.def unseen_entity_flag(answer: str, context: str, entities: list[str]) -> bool: return any(entity.lower() not in context.lower() for entity in entities) context = "Paris was not founded in 1994." answer = "Paris was founded in 1994." assert not unseen_entity_flag(answer, context, ["Paris"]) claim_supported = False # human label or an NLI verdict, not the lexical flag assert not claim_supported -
Verificação de claims: extraia claims, execute NLI face ao contexto e falhe ou sinalize qualquer uma abaixo do limiar. Modelos NLI-as-faithfulness:
cross-encoder/nli-deberta-v3-large,MoritzLaurer/DeBERTa-v3-large-mnli-fever-anli-ling-wanli. Acrescenta latência. Vale a pena em domínios de elevado risco. -
Self-consistency (Wang et al., ICLR 2023): faça sampling de várias gerações a temperature > 0; reporte a taxa de concordância (por exemplo, a proporção de gerações que coincide com a resposta modal ou BERTScore par-a-par); escolha o número de samples a partir da curva estabilidade–custo e sinalize respostas com baixa concordância para revisão humana.
-
Calibração da confiança: recolha a confiança verbalizada (“Quão confiante está, de 0 a 1?”) e compare-a com a correção real no conjunto de avaliação. Trace uma curva de calibração e reporte o Expected Calibration Error: , onde são bins de confiança. Implementações:
netcal,torchmetrics.CalibrationError. Um modelo que reporte confiança 0,9 deve estar correto em aproximadamente 90% dos casos comparáveis; meça o desvio em vez de assumir calibração.
Parte 7: Avaliação de RAG baseado em ontologia
As métricas standard acima cobrem RAG em open corpus. Se o seu RAG fizer retrieval contra uma ontologia estruturada, taxonomia ou knowledge graph, essas métricas são necessárias mas não suficientes. Exemplos incluem produtos num catálogo, condições no SNOMED, componentes numa BOM e técnicas de segurança no MITRE ATT&CK. Também precisa de medir a camada de ontologia.
Precisão da entity linking
A primeira tarefa é mapear uma menção da query para uma entidade da ontologia (“Aspirin” → wikidata:Q18216, “the 737” → aircraft:Boeing_737).
- Precisão/recall/F1 ao nível da menção: standard, face a spans de menções gold (calcule com
seqevalou com um comparador de conjuntos de spans). - Precisão da desambiguação: entre as menções detetadas corretamente, que fração é mapeada para o ID de entidade correto? As referências públicas incluem ReFinED, REL e GENRE; benchmarks como AIDA-CoNLL e BELB mostram que os resultados variam com o sistema e o domínio.
- Tratamento de NIL: precisão/recall em “entidade não existente na ontologia”. Meça separadamente o over-linking para entidades próximas mas erradas e a abstenção correta.
Avaliação consciente da hierarquia
A precisão simples trata “prever Sedan quando a verdade é Hatchback” da mesma forma que “prever Sedan quando a verdade é Submarine.” Esses erros não são equivalentes.
-
Precisão/recall/F1 hierárquicos (Kosmopoulos et al., 2015): atribua crédito aos ancestrais partilhados no DAG da ontologia. Com o nó previsto mais todos os seus ancestrais e o nó verdadeiro mais todos os seus ancestrais:
Implemente com
networkxno grafo da ontologia: acrescente os ancestrais a cada previsão e label e calcule as interseções dos conjuntos acima. -
Similaridade de Wu-Palmer entre a entidade prevista e a entidade gold na taxonomia (Wu & Palmer, 1994):
onde LCA é o lowest common ancestor na taxonomia. Está disponível out of the box no NLTK para WordNet (
from nltk.corpus import wordnet as wn; wn.synset("car.n.01").wup_similarity(wn.synset("truck.n.01"))); para taxonomias personalizadas, calcule o LCA comnetworkx. -
Taxa de confusão com irmãos/pais: acompanhe separadamente confusões com irmãos, pais e filhos —
count_sibling / total_errors,count_parent / total_errors,count_descendant / total_errors. Utilize exemplos revistos para testar se os erros com irmãos resultam de menções ambíguas ou se os erros com pais resultam de generalização excessiva.
Taxa de false-exclusion do filtro (reprise, agora crítica)
Em sistemas baseados em ontologias, os filtros hard provêm frequentemente da própria ontologia (“fazer retrieval apenas de documentos etiquetados com a categoria X”). A métrica de exclusão (definida na Parte 5) torna-se um sinal primário de correção. Uma previsão errada da categoria pode fazer o recall cair para zero; a taxa de exclusão atribui essa perda ao filtro.
Conformidade da geração constrangida
Quando o seu output tem de estar em conformidade com uma ontologia (todos os nomes de entidades na resposta têm de ser membros válidos da ontologia; todos os predicados têm de provir de um vocabulário fechado), meça:
- Taxa de validade do schema: percentagem de outputs que fazem parse e validam contra o schema da ontologia. Valide com
jsonschemaoupydantic. O JSONSchemaBench é o benchmark público para structured output geral; para schemas específicos de ontologias, crie o seu próprio validator. - Conformidade com o vocabulário: percentagem de entidades nomeadas no output que são IDs válidos da ontologia — uma verificação de pertença a um conjunto face ao vocabulário fechado.
- Conformidade semântica: um output sintaticamente válido pode ainda escolher uma entidade errada mas válida. Associe a conformidade à correção da resposta a jusante.
Frameworks de constrained decoding (Outlines, XGrammar, Guidance, OpenAI Structured Outputs) foram concebidos para impor a validade do schema. O JSONSchemaBench compara eficiência, coverage e qualidade entre implementações. Volte a executar os seus casos que correspondem aos schemas e ao serving backend, porque coverage e latência dependem de ambos.
Auditabilidade
Para sistemas baseados em ontologias cujas respostas são revistas:
- Completude das citações: percentagem de claims factuais com pelo menos uma citação verificável.
- Profundidade da proveniência: percentagem de citações que resolve até um documento de origem com um ID estável, e não apenas até um hash de chunk.
- Taxa de reprodutibilidade: repetir a mesma query num snapshot fixo devolve a mesma resposta. Fixe a versão do modelo, o runtime, a configuração de decoding e a seed; depois defina a taxa de repetição exigida pelas necessidades de auditabilidade do workflow. Temperature zero, por si só, não garante determinismo. Uma falha pode ter origem na geração, no serving runtime ou em qualquer etapa a montante.
Parte 8: Avaliação ao nível do sistema
Qualidade holística da resposta
- LLM-as-judge (Zheng et al., NeurIPS 2023): uma abordagem escalável de avaliação baseada em modelos. O G-Eval (Liu et al., EMNLP 2023) gera passos de avaliação a partir da tarefa e dos critérios e depois pondera os níveis de classificação pelas probabilidades dos tokens: . Estas são probabilidades, não log probabilities. A concordância depende do judge, da tarefa, do prompt e do conjunto de calibração.
- Preferência pairwise: apresente ao judge a resposta A vs. a resposta B e registe a preferência. Isto substitui uma classificação absoluta por uma decisão comparativa, mas continua a exigir calibração face a preferências humanas. O MT-Bench reportou concordância do judge GPT-4 superior a 80% tanto com preferências humanas como com concordância entre humanos nas suas condições de benchmark; não transfira essa taxa para outro domínio sem calibração.
LLM-as-judge tem enviesamentos reais:
- Position bias: meça a sensibilidade à ordem em casos etiquetados por humanos para o judge e a tarefa selecionados. A randomização ou a agregação com ordem trocada pode ajudar alguns pares modelo/tarefa, mas mantenha-a apenas se melhorar a concordância humana local; o estudo controlado de 2026 encontrou efeitos prejudiciais da troca de posições nos seus casos adversariais.
- Verbosity bias: os judges podem confundir comprimento com qualidade. O estudo controlado de 2026, versão 2 citado encontrou comportamentos heterogéneos em pares de expansão: três judges preferiram respostas mais longas, Claude preferiu respostas concisas e GPT-4o foi aproximadamente neutro. Os cinco tiveram bom desempenho nos controlos de truncation. Esses resultados estão limitados ao benchmark; diga ao judge como deve tratar completude e filler e reporte o desempenho controlado pelo comprimento na sua própria rubrica.
- Risco de self-preference: Zheng et al. observaram uma taxa de self-win do GPT-4 10% superior e uma taxa de self-win do Claude-v1 25% superior nos seus dados, mas concluíram que os dados limitados e as diferenças pequenas não permitiam estabelecer um enviesamento de self-enhancement. Compare judges da mesma família e de famílias diferentes com labels humanos locais; escolha o judge mais bem calibrado em vez de assumir que qualquer uma das configurações é segura.
Receita prática: selecione um judge com dados de calibração etiquetados por humanos, oculte as identidades dos modelos, meça a sensibilidade à ordem e declare a política de comprimento na rubrica. Repita casos apenas quando as amostras adicionais reduzirem materialmente a incerteza. Em avaliações de elevado risco, compare judges da mesma família e de famílias diferentes e analise as discordâncias face a labels humanos.
Schema-Guided Reasoning para judges
O output livre é uma fonte de variação nas execuções do judge. Duas execuções sobre a mesma resposta podem organizar a rubrica de forma diferente e produzir scores diferentes. O Schema-Guided Reasoning (SGR) torna essa rubrica explícita: defina as etapas da avaliação como um schema Pydantic e utilize constrained output através de Outlines, XGrammar, structured outputs do vLLM ou OpenAI response_format para impor as restrições suportadas pelo schema. A ordem dos campos pode ajudar um reviewer a inspecionar o registo, mas não prova que o modelo tenha raciocinado pelas etapas nessa ordem.
Para a avaliação de RAG, o schema decompõe o julgamento em campos explícitos e auditáveis, em vez de deixar o modelo saltar diretamente para um número:
from pydantic import BaseModel, Field, ValidationError, model_validator
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."
)
outcome: Literal["scored", "no_claims"]
failure_mode: Literal[
"none", "fabrication", "overgeneralization", "wrong_entity", "stale_fact"
]
rationale: str
@model_validator(mode="after")
def require_claim_partition(self):
for claims in (self.extracted_claims, self.supported_claims, self.unsupported_claims):
if any(not claim.strip() for claim in claims):
raise ValueError("claims must contain non-whitespace text")
extracted = set(self.extracted_claims)
supported = set(self.supported_claims)
unsupported = set(self.unsupported_claims)
if not extracted:
if supported or unsupported or self.outcome != "no_claims":
raise ValueError("an empty extraction must be a no_claims outcome")
return self
if (
len(extracted) != len(self.extracted_claims)
or len(supported) != len(self.supported_claims)
or len(unsupported) != len(self.unsupported_claims)
or supported & unsupported
or supported | unsupported != extracted
or self.outcome != "scored"
):
raise ValueError("verdict lists must be a complete, disjoint claim partition")
return self
@property
def score(self) -> float | None:
if not self.extracted_claims:
return None
return len(self.supported_claims) / len(self.extracted_claims)
valid = FaithfulnessJudgment(
extracted_claims=["Mars has two moons", "Mars has a thick atmosphere"],
supported_claims=["Mars has two moons"],
unsupported_claims=["Mars has a thick atmosphere"],
outcome="scored",
failure_mode="fabrication",
rationale="The atmosphere claim lacks support.",
)
assert valid.score == 0.5
empty = FaithfulnessJudgment(
extracted_claims=[],
supported_claims=[],
unsupported_claims=[],
outcome="no_claims",
failure_mode="none",
rationale="Score refusal correctness separately.",
)
assert empty.score is None
try:
FaithfulnessJudgment(
extracted_claims=["Mars has two moons"],
supported_claims=["Mars has two moons"],
unsupported_claims=["Mars has two moons"],
outcome="scored",
failure_mode="none",
rationale="Contradictory verdict.",
)
except ValidationError:
pass
else:
raise AssertionError("overlapping verdicts must fail validation")
for blank in ("", " ", "\t\n"):
for verdict_field in ("supported_claims", "unsupported_claims"):
fields = {"extracted_claims": [blank], "supported_claims": [], "unsupported_claims": []}
fields[verdict_field] = [blank]
try:
FaithfulnessJudgment(
**fields, outcome="scored", failure_mode="none", rationale="Blank claim."
)
except ValidationError:
pass
else:
raise AssertionError("blank claims must fail validation")
Este contrato ilustrativo calcula o score apenas depois de as listas de veredictos formarem uma partição completa e disjunta das claims extraídas. Um resultado sem claims não tem score de faithfulness e deve ficar fora do aggregate de faithfulness; reporte a sua contagem e avalie separadamente a correção da recusa, para que as abstenções não melhorem silenciosamente a média. Preserve estas verificações semânticas em produção; o constrained output garante a forma, não um veredicto imparcial. O modelo Pydantic também torna uma alteração da rubrica visível como um diff de código, enquanto os testes de calibração humana avaliam o próprio julgamento.
Isto funciona para qualquer judge baseado em rubricas, não apenas para faithfulness. Preferência pairwise, suporte de citações e correção de recusas beneficiam do mesmo tratamento.
Uma rubrica simplificada de judge, mais exemplos pairwise, de position bias e de famílias diferentes, está no notebook 07; módulo: evaluation/llm_judge.py. Na revisão analisada, a função chamada g_eval pede uma classificação de um único inteiro; não gera passos de avaliação nem calcula scores ponderados por probabilidade, pelo que não reproduz o protocolo G-Eval. O benchmark sweep (make benchmark no repositório) liga três modelos (gpt-5-mini, claude-haiku-4-5, gemini-2.5-flash) num pairwise A/B com judge rotativo: cada judge avalia respostas das outras duas famílias de modelos. Essa topologia suporta resultados pairwise entre famílias; medir self-preference exige também condições same-family e cross-family comparadas com labels humanos locais.
Latência e custo
- p50, p95, p99 em cada etapa do pipeline. Escolha o percentil do SLO e o limiar de alerta com base na jornada do utilizador, no volume de tráfego e no error budget.
- Time-to-first-token vs. tempo total de geração. Os utilizadores preocupam-se com TTFT numa UX de streaming.
- Decomposição por etapa: retrieval, reranking, geração e pós-processamento. Utilize o trace para localizar a cauda em vez de assumir que sabe que etapa a causou; registe o device do reranker e o batch size ao comparar execuções.
- Total $/query = embedding + retrieval + rerank + geração + armazenamento amortizado. Acompanhe p50 e p99; é na cauda longa que o budget é consumido.
- Taxas de cache hit ao nível do embedding cache, retrieval cache e KV-cache. Defina objetivos separados a partir da repetição observada, da política de invalidação e do custo evitado em cada camada.
p50/p95/p99 por etapa, com decomposição por etapa, estão integrados no notebook 08 e no runner em evaluation/latency.py; o relatório de benchmark combina latência com faithfulness numa matriz única que pode voltar a executar com make benchmark.
Testes A/B
- Unidade de randomização: escolha a unidade a partir do estimand, do carryover e da interferência. Utilize atribuição por utilizador ou sessão quando uma exposição repetida puder alterar o comportamento ou criar uma UX inconsistente. A atribuição por query é defensável apenas quando esses efeitos são negligenciáveis e a análise modela observações repetidas.
- Métricas primárias, guardrails e exploratórias: registe-as antecipadamente. Escolha a métrica primária a partir do resultado do produto; proxies de satisfação incluem thumbs, regenerações e dwell. Trate latência e custo como guardrails quando limitam a experiência.
- Tamanho da amostra: faça power analysis antes do lançamento com base no efeito mínimo que vale a pena detetar, na variância baseline, na unidade de atribuição e na regra de paragem.
Parte 9: Construção do conjunto de testes
Uma métrica só é tão boa quanto o conjunto de testes onde é executada. Se o seu golden set cobrir três intents e o tráfego de produção abranger doze, o Recall@10 mede apenas esses três intents. Pior ainda, um conjunto de testes que sobreajuste a perguntas fáceis (“Qual é a política de reembolsos da empresa?”) pode aprovar um sistema que falha nas difíceis (“Elegibilidade para reembolso de um cancelamento parcial ao abrigo do EU Digital Services Act de 2023, faturado em EUR e com origem na Irlanda?”). O score agregado sobe enquanto o sistema continua a falhar numa parte importante do tráfego de produção.
Labels de relevância incompletos podem distorcer o recall em qualquer direção. Se o conjunto relevante verdadeiro for {a, b} mas os labels contiverem apenas {a}, recuperar {a} obtém 1,0 em vez de 0,5; recuperar {b} obtém zero em vez de 0,5. Versione os julgamentos e reveja evidência nova e não julgada antes de interpretar um delta.
Construa primeiro o conjunto de testes em torno da distribuição e da dificuldade reais das queries. Depois escolha métricas que respondam aos modos de falha-alvo e faça tuning do sistema com base nelas.
Geração sintética de queries
Utilize um LLM para gerar perguntas a partir do seu corpus:
- Por chunk: “Gere 3 perguntas que um utilizador poderia fazer e às quais este chunk responde.”
- Multi-hop: selecione dois chunks e gere uma pergunta que exija ambos.
- Adversarial: gere perguntas com entidades distractoras, formulação quase duplicada e menções ambíguas.
A geração de testes do Ragas utiliza cenários baseados em grafos com queries single-hop e multi-hop e necessidades de informação específicas ou abstratas. O DataMorgana gera benchmarks sintéticos configuráveis entre categorias de utilizadores e perguntas. Dados sintéticos são úteis para cold starts e testes de coverage. Não podem substituir queries reais de utilizadores.
Construção do golden dataset
Dados selecionados por humanos ancoram o golden set.
- Amostre queries reais de utilizadores (ou simuladas, se ainda estiver antes do lançamento), estratificadas por intent.
- Peça a SMEs para responder a cada pergunta e identificar o(s) documento(s) que contém(êm) a resposta.
- Dimensione o conjunto a partir da matriz de coverage e do intervalo de confiança necessário para as decisões de release; a cobertura é mais importante do que um número de queries importado de outro contexto.
- Faça nova curadoria quando a cadência de release, os sinais de drift, o risco do domínio e a capacidade de anotação o justificarem.
Mantenha separadas as queries de desenvolvimento, a calibração do judge, a medição de release held-out e as amostras de monitorização. Agrupe documentos-fonte e sessões partilhados antes de dividir. Assim que uma query ou label orienta o tuning, passa a ser dado de desenvolvimento. Reserve casos não tocados para validar a configuração e o judge escolhidos.
Registe as versões do corpus, da query, dos labels de relevância e das políticas, a unidade de k, o orçamento do contexto fornecido, as revisões do scorer e do judge e as regras de agregação. Compare deltas emparelhados por query na mesma população, com incerteza e contagens por slice. Inclua todas as queries esperadas: um timeout, um resultado em falta ou uma resposta do judge impossível de avaliar têm de permanecer visíveis na contabilização de completude/erros. Não permita que um score de release melhore ao eliminar falhas silenciosamente. O ARES oferece uma abordagem de investigação com judges automáticos, validação humana e inferência prediction-powered para estimativas do sistema quando a anotação é escassa; uma suite local revista pode começar de forma mais simples.
Conjuntos de testes adversariais
- Contrafactuais: troque entidades-chave na query. O sistema recupera os chunks corretos para a query alterada?
- Distractores: queries em que o corpus contém uma resposta plausível mas errada que não deveria ser recuperada. É isto que o RGB (Chen et al., AAAI 2024) testa sob stress: robustez ao ruído, rejeição de negativos, integração de informação e robustez contrafactual.
- Negação e quantificadores: queries com “não”, “exceto” e “apenas”. Os retrievers dense têm frequentemente dificuldades com estes casos.
- Fora do âmbito: queries sem resposta no corpus. O sistema deve dizer “não sei”, não hallucinate. O NoMIRACL disponibiliza testes de relevância/answerability ao nível das passagens; acrescente labels separados ao nível do corpus para casos fora do âmbito. Avalie explicitamente a abstenção nos seus tipos de queries de produção.
Coverage e avaliação contínua
- Construa uma matriz de coverage: intent da query × tipo de documento × ramo da ontologia. Uma query por célula é um inventário inicial de coverage, não poder estatístico suficiente para uma decisão de release. Células vazias expõem coverage em falta; dimensione os slices preenchidos para a incerteza que consegue tolerar.
- Execute um subconjunto de regressão limitado e rápido em cada PR e a suite completa segundo um calendário mais lento.
- Agende a avaliação completa do golden set de acordo com a cadência de release e o custo da avaliação; execute-a nos release candidates.
- Agende a avaliação de drift de acordo com o volume de tráfego, a mudança esperada e o risco. Utilize uma amostra rolling de produção e estratifique por feedback em vez de alterar silenciosamente a distribuição-alvo.
Parte 10: Monitorização em produção
A suite de avaliação que coloca em produção descreve o sistema no lançamento. O tráfego de produção muda depois disso.
Feedback implícito e explícito
- Trate eventos implícitos como sinais candidatos, não como KPIs positivos ou negativos de qualidade, até demonstrarem correlação com revisão cega ou feedback explícito numa amostra local.
- Click-through / open rate nas fontes citadas (se a sua UI as expuser).
- Dwell time na resposta.
- Taxa de regeneração: percentagem de respostas que o utilizador repete ou pede ao sistema para refazer. Trate-a como um sinal de insatisfação e calibre-a face a conversas revistas.
- Taxas de copy / share / export: sinais implícitos candidatos que podem representar utilidade, verificação, handoff ou insatisfação. Meça a sua associação e o intervalo de confiança antes de lhe atribuir uma direção.
- Padrões de follow-up: utilize “Tem a certeza?” ou “E quanto a X?” como estratos de revisão e etiquete a sua associação a desconfiança ou necessidade não resolvida.
- Thumbs up/down com categorias de motivo opcionais (errado, incompleto, fora do tópico, prejudicial, lento). Edições inline podem preservar mais contexto de diagnóstico; avalie esse valor em amostras revistas.
Deteção de drift
- Drift de queries: compare embeddings de queries com uma janela de referência utilizando MMD ou um classifier de referência-versus-atual validado. A KL exige um estimador de probabilidade definido, como histogramas escolhidos; coordenadas brutas de embeddings não são probabilidades. Calibre os alarmes em shifts conhecidos e inspecione os slices afetados.
- Drift de embeddings: fixe uma representação e um conjunto de probes e meça a estabilidade dos vizinhos e a qualidade do retrieval. Versões diferentes do modelo não têm necessariamente as mesmas dimensões ou a mesma base de coordenadas, pelo que o cosine entre versões pode não ter significado. Migre os encoders de queries e documentos em conjunto, avalie o novo índice e mantenha snapshots versionados para rollback.
- Drift de desempenho: acompanhe métricas equivalentes às de produção (taxa de regeneração por intent) ao longo do tempo. Alterações súbitas e graduais sugerem hipóteses diferentes, mas a sua forma não estabelece a causa; inspecione alterações nos dados, tráfego, provider, políticas e deployment.
Shadow evaluation e human-in-the-loop
Execute o sistema candidato em paralelo com a produção, compare os outputs offline e não os sirva aos utilizadores. Isto pode expor regressões antes do lançamento. Shadow inference continua a consumir capacidade e pode invocar ferramentas: isole recursos, suprima writes e verifique que a comparação não degrada a latência de produção.
Para revisão human-in-the-loop (HITL):
- Envie outputs de baixa confiança para uma fila de revisão.
- Inclua uma amostra aleatória do tráfego de produção para revisão cega; defina a taxa com base no volume de tráfego, risco e capacidade dos reviewers.
- Faça oversampling de outputs com thumbs-down para revisão, juntamente com a amostra aleatória.
- Utilize os outputs revistos para expandir o golden set.
O conjunto mínimo de guardrails
Escolha prioridades e limiares de alertas com base no dano para o utilizador, nos SLOs e no desempenho validado dos detetores. Os sinais candidatos incluem:
- Score de Faithfulness/HHEM abaixo do limiar numa amostra de produção rolling.
- Latência p95 acima do SLO.
- Taxa de false-exclusion do filtro acima do limiar (baseada em amostra).
- Taxa de regeneração fora de uma control band calibrada localmente que tenha em conta o tamanho da janela, o tráfego, a sazonalidade e o orçamento de falsos alertas.
- Custo/query acima do budget.
Utilize o momento do release para orientar o diagnóstico e depois verifique-o face aos traces e aos slices afetados. Um deployment pode coincidir com drift de tráfego e uma alteração do provider ou dos dados pode ocorrer sem um release da aplicação. Os alertas são evidência para investigar; o seu lead time face aos reports dos utilizadores é algo que deve medir.
Limitações
- Os objetivos são locais, não universais. Qualquer número identificado como ilustrativo neste guia é uma configuração de exemplo ou um resultado trabalhado, não um limiar de release. Calibre os limiares ao seu domínio, ao risco, à incerteza do conjunto de avaliação e às expectativas dos utilizadores.
- O espaço de frameworks evolui rapidamente. Versões do HHEM, nomes de métricas do RAGAS, model cards e ordem das leaderboards podem mudar após a publicação. Volte a verificar a fonte associada e faça novo benchmark antes de se comprometer.
- Os números de concordância do LLM-as-judge têm ressalvas. O valor de 80% entre GPT-4 e humanos provém das condições do MT-Bench / Chatbot Arena. Esse resultado não estabelece concordância num domínio de nicho ou num slice adversarial. Utilize judges como multiplicadores de força, não como substitutos da revisão pontual.
- Os uplifts de benchmarks de fornecedores frequentemente não são reproduzíveis de forma independente. Reproduza-os nos seus próprios dados antes de acreditar num número, sobretudo no caso de rerankers e sistemas de OCR mais recentes.
- Nenhuma métrica substitui a inspeção dos outputs. Agende revisão cega de uma amostra aleatória de produção de acordo com o tráfego, o risco e a capacidade dos reviewers. As métricas escalam esse hábito; não o substituem.
Próximos artigos desta série
Este foi o índice. Os follow-ups que estou a planear:
- Soft Boosts vs. Hard Filters: análise aprofundada da taxa de false-exclusion do filtro, com código, exemplos reais de produção e um framework de decisão.
- Chunking Is the Hidden Variable: experiência controlada com chunking recursivo, semântico, late e estrutural em três corpora.
- Reranker Selection in 2026: BGE vs. Cohere vs. ZeRank vs. modelos cross-encoder atuais, comparados diretamente em custo, latência e uplift.
- Ontology-Grounded RAG: An End-to-End Walkthrough: construção do harness completo de avaliação para um sistema de retrieval baseado em entidades.
- LLM-as-Judge Without the Self-Preference Trap: receitas práticas para avaliação automática sem enviesamento.
- Online Evaluation in Production: padrões de instrumentação, políticas de alertas e dashboards que detetam regressões reais.
Referências
Frameworks e benchmarks
- Es et al., Ragas: Automated Evaluation of Retrieval Augmented Generation, 2023.
- Documentação do RAGAS e GitHub.
- Saad-Falcon et al., ARES: An Automated Evaluation Framework for Retrieval-Augmented Generation Systems, NAACL 2024.
- TruLens, DeepEval, Arize Phoenix.
- Thakur et al., BEIR: A Heterogenous Benchmark for Zero-shot Evaluation of Information Retrieval Models, NeurIPS 2021.
- MTEB Leaderboard.
- TREC 2024 RAG Track.
- Pradeep et al., Initial Nugget Evaluation Results for the TREC 2024 RAG Track with the AutoNuggetizer Framework, 2024.
Retrieval e ranking
- Cormack, Clarke, Buettcher, Reciprocal Rank Fusion Outperforms Condorcet and Individual Rank Learning Methods, SIGIR 2009.
- Gao et al., Precise Zero-Shot Dense Retrieval Without Relevance Labels (HyDE), 2022.
- Jeong et al., Adaptive-RAG: Learning to Adapt Retrieval-Augmented Large Language Models through Question Complexity, NAACL 2024.
- Anthropic, Introducing Contextual Retrieval, setembro de 2024.
- Günther et al., Late Chunking: Contextual Chunk Embeddings Using Long-Context Embedding Models, 2024.
Geração, faithfulness e judges
- Min et al., FActScore: Fine-grained Atomic Evaluation of Factual Precision in Long Form Text Generation, EMNLP 2023.
- Liu et al., Lost in the Middle: How Language Models Use Long Contexts, TACL 2024.
- Chen et al., Benchmarking Large Language Models in Retrieval-Augmented Generation (RGB), AAAI 2024.
- Vectara, HHEM-2.1-Open hallucination evaluation model.
- Zheng et al., Judging LLM-as-a-Judge with MT-Bench and Chatbot Arena, NeurIPS 2023.
- Thakur et al., Support Evaluation for the TREC 2024 RAG Track: Comparing Human versus LLM Judges, SIGIR 2025.
- Thakur et al., Knowing When You Don’t Know: A Multilingual Relevance Assessment Dataset for Robust Retrieval-Augmented Generation, Findings of EMNLP 2024.
- Geng et al., JSONSchemaBench: A Rigorous Benchmark of Structured Outputs for Language Models, 2025.
- Kosmopoulos et al., Evaluation Measures for Hierarchical Classification: a unified view and novel approaches, 2015.
Drift e produção
- Evidently, Embedding drift detection methods compared.
Código complementar
slavadubrov/rag-evals-demo— harness executável para métricas selecionadas neste artigo sobre o corpus SciFact, além de um benchmark sweep de chunking × embedding × LLM. Notebooks 00–09, testes unitários que fixam os exemplos trabalhados acima e um índice Qdrant embebido, para que possa ser executado sem Docker.