Como avaliar RAG: golden dataset
Como medir um RAG com golden dataset e recall@k: 20 perguntas rotuladas, o número antes e depois de mudar o corte, e por que 5 casos enganam.
O cliente pergunta se o assistente de suporte "ficou melhor" depois que você trocou a forma de cortar os documentos. Você testou três perguntas na mão, as três responderam bem, e a resposta honesta é "parece que sim". Na semana seguinte alguém reclama de uma pergunta que funcionava e deixou de funcionar, e você não tem como dizer se foi a sua mudança.
O que é um golden dataset de RAG
Um golden dataset é uma lista de perguntas com a resposta certa já marcada, que você roda sempre igual a cada mudança. Em RAG, a marcação mais barata fica na etapa de busca: para cada pergunta, o trecho do documento que precisa chegar ao modelo. O arquivo é JSON simples:
[
{"pergunta": "Qual o prazo do reembolso?", "trecho": "7 dias úteis"},
{"pergunta": "Tem juros no parcelamento?", "trecho": "sem juros até 6 vezes"}
]
A métrica é o recall@k: a fração das perguntas em que algum dos k primeiros
chunks devolvidos pela busca contém o trecho. Recall@1 pergunta se o melhor
resultado serve. Recall@3 pergunta se a resposta estava entre os três primeiros.
Avaliar a busca separado da geração isola a causa: se o trecho não chegou, o
modelo de linguagem nunca teve chance.
Medir a busca em código
O código abaixo roda com embedding local, sem chave de API. acertou implementa a
regra do recall@k e avaliar percorre o golden dataset inteiro.
# bench.py (pip install sentence-transformers)
import json
from sentence_transformers import SentenceTransformer
modelo = SentenceTransformer("sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2")
golden = json.load(open("golden.json", encoding="utf-8"))
def ranking(pergunta, chunks):
# Índices dos chunks, do mais parecido com a pergunta para o menos parecido
emb_chunks = modelo.encode(chunks, normalize_embeddings=True)
emb_pergunta = modelo.encode([pergunta], normalize_embeddings=True)[0]
scores = emb_chunks @ emb_pergunta
return sorted(range(len(chunks)), key=lambda i: scores[i], reverse=True)
def acertou(caso, chunks, indices, k):
# Acerto: algum dos k primeiros chunks contém o trecho que responde
return any(caso["trecho"] in chunks[i] for i in indices[:k])
def avaliar(chunks, ks=(1, 3)):
resultados = []
for caso in golden:
indices = ranking(caso["pergunta"], chunks)
resultados.append({
"pergunta": caso["pergunta"],
**{f"acerto@{k}": acertou(caso, chunks, indices, k) for k in ks},
})
return resultados
A comparação usa um documento de política de uma empresa fictícia, com 20 fatos curtos, e 20 perguntas rotuladas. Duas versões do mesmo pipeline: corte a cada 200 caracteres, sem olhar o conteúdo, e corte só em fim de frase, com uma frase repetida entre vizinhos.
| Corte | Chunks | recall@1 | recall@3 |
|---|---|---|---|
| fixo (200 caracteres) | 6 | 0,75 | 0,90 |
| por frase | 7 | 0,85 | 1,00 |
Com o corte por frase, todas as respostas aparecem entre os três primeiros chunks. Esse é o número que vai para o cliente, com a data e a versão do pipeline ao lado. O mecanismo do corte que parte respostas ao meio está em RAG: como funciona na prática.
O recall médio esconde quem piorou
Uma média maior não quer dizer que nenhuma pergunta piorou. Comparar por pergunta mostra o saldo real:
def comparar(antes, depois, chave="acerto@1"):
a = {r["pergunta"]: r[chave] for r in antes}
b = {r["pergunta"]: r[chave] for r in depois}
return {
"consertou": [q for q in a if not a[q] and b[q]],
"quebrou": [q for q in a if a[q] and not b[q]],
}
Na troca de corte fixo para corte por frase, o recall@1 subiu de 0,75 para 0,85. Três perguntas passaram a acertar: o tempo de resposta do suporte, o celular perdido do autenticador e o prazo da exclusão de dados. Uma quebrou: "Qual o horário do suporte?". No chunk novo, a frase com o horário ficou colada em "Suporte." no fim de um chunk que fala de estorno, e a busca preferiu outro trecho. O saldo é positivo, e ainda assim existe uma pergunta que hoje falha e antes funcionava. É essa lista que você mostra ao cliente e que vira a próxima tarefa.
Quantas perguntas o golden dataset precisa ter
Cinco perguntas parecem um teste, e não são. Sorteando 1.000 amostras de 5 perguntas dentro das mesmas 20, o recall@1 do corte fixo variou de 0,20 a 1,00, embora o valor real seja 0,75. Com amostras de 10, a faixa foi de 0,50 a 1,00. O corte por frase ficou entre 0,40 e 1,00 com 5 perguntas e entre 0,70 e 1,00 com 10. Quem avalia com poucos casos pode concluir que uma mudança ajudou ou piorou só pelo sorteio das perguntas.
Um ponto de partida prático é de 20 a 30 perguntas, escritas do jeito que o usuário digita (com erro, com gíria, sem o termo exato do documento) e revisadas por quem conhece o negócio. Cada reclamação real que chega depois vira uma pergunta nova no arquivo, e o golden dataset cresce a partir de falhas de verdade.
Quando esse método não serve
Recall@k mede só a busca. Um trecho recuperado não garante uma boa resposta: o modelo ainda pode ignorar o contexto ou misturar fatos. A geração precisa de outra avaliação, com critério de aceite por caso.
Casar texto exato é frágil. A regra trecho in chunk quebra se o documento
mudar a redação. Para documentos que mudam com frequência, guarde o identificador
da seção como rótulo, em vez da frase.
Perguntas sem resposta ficam de fora. O golden dataset acima só tem perguntas que o documento responde. Um assistente também precisa recusar o que não sabe, e isso pede casos rotulados como "sem resposta" e um piso de score.
Vinte perguntas ainda são pouco para uma base grande. Em milhares de documentos, a cobertura de 20 casos é fina, e o recall vale como alarme de regressão, não como garantia de qualidade. Os números deste artigo valem para este modelo, este documento e estas perguntas. O que se leva é o procedimento.
O código completo, com o golden dataset, o comparativo por pergunta e as amostras, está no repositório do experimento.
O que levar para amanhã
Antes de mostrar uma melhoria de RAG ao cliente, rode as mesmas perguntas rotuladas nas duas versões e entregue o recall@k com a lista de perguntas que melhoraram e a de perguntas que pioraram.
A trilha AI Engineer (R$ 697) inclui uma lição de golden datasets e suítes de avaliação, validada por testes automatizados, para você levar ao cliente um número em vez de uma impressão.