De 71% a 100% de hit@5:
como afinamos a recuperação do RAG
Um agente que "sabe" seus documentos só é útil se a busca entrega o trecho certo para o modelo. Contamos como o re-ranking sobre uma base híbrida levou o hit@5 do nosso RAG de 5/7 (71%) para 7/7 (100%) num benchmark determinístico — e por que os braços semântico e léxico precisam trabalhar juntos.

Pipeline medido
Dois braços, vinte candidatos, cinco evidências
- Denso + léxicoConceito e correspondência literal.
- Fusão RRFRankings combinados por posição.
- Top 20Over-fetch preserva candidatos.
- Top 5Re-ranking listwise fecha o contexto.
RAG (retrieval-augmented generation) tem duas metades: a recuperação e a geração. Quase toda a atenção vai para a segunda — qual modelo, qual prompt. Mas se a recuperação entrega o chunk errado, o melhor modelo do mundo responde com confiança sobre o contexto errado. A métrica que importa aqui é o hit@k: em que fração das perguntas o trecho correto aparece entre os k primeiros resultados que você injeta no prompt.
Medimos o nosso com um benchmark determinístico — um conjunto fixo de perguntas com o chunk-alvo conhecido para cada uma. Sem essa fixação, "melhorar o RAG" vira opinião. Com ela, cada mudança tem um número antes e depois.
O ponto de partida do A/B: 71% com híbrido, sem re-ranking
A configuração A do experimento já usava recuperação híbrida com RRF, mas deixava o re-ranking desligado. Em sete perguntas com chunk-alvo conhecido, acertou cinco: 5/7, ou 71% de hit@5. Os quatro identificadores exatos estavam recuperados entre as primeiras posições; as três perguntas de grounding mais profundas produziram as duas falhas.
O A/B também mostrou por que a base híbrida era indispensável. Embeddings capturam significado, mas podem diluir tokens raros e literais. Na pergunta por AVX-039, o braço denso deixou o trecho certo em 14º; o braço léxico o colocou em 1º, e a fusão RRF o trouxe para o topo. O salto de 71% para 100% veio depois, quando o re-ranker corrigiu as três consultas de grounding — mas sem o braço léxico o exact-ID nem chegaria bem posicionado àquela etapa.
A busca semântica é ótima para "sobre o que isso fala" e ruim para "onde está exatamente este código". Produtos reais precisam das duas.
A base do pipeline: busca léxica e densa fundidas com RRF
A busca léxica (BM25) faz exatamente o que falta: casa tokens literais. "AVX-006" na pergunta encontra "AVX-006" no chunk, com peso alto porque é um token raro no corpus. O problema é combinar as duas listas — a densa e a léxica — porque os scores vivem em escalas incomparáveis: a similaridade de cosseno fica num intervalo fixo e estreito, o score do BM25 é ilimitado e depende do tamanho do corpus e da frequência dos termos. Somar os dois é somar metros com graus Celsius; normalizar exige calibração manual que quebra a cada corpus novo.
A solução é o Reciprocal Rank Fusion (RRF), que descarta os scores e usa só as posições: cada documento ganha 1/(k + rank_denso) + 1/(k + rank_léxico), onde k é uma constante de suavização da fórmula (não confundir com o k do top-k) que evita que o primeiro lugar de uma lista atropele tudo. Rank é adimensional — primeiro lugar é primeiro lugar em qualquer escala — então a fusão dispensa qualquer normalização. Um trecho bem posicionado nas duas listas soma duas contribuições altas e sobe; um que só aparece numa delas recebe um único termo e fica atrás. E a fusão não precisa ser simétrica: um peso léxico (lexical_weight, de 0 a 1, default 0.5) inclina o resultado para o braço léxico quando o corpus é denso em códigos e tabelas, ou para o denso quando é prosa conceitual.
É o híbrido com RRF que resgata os exact-ID lookups. O chunk do AVX-039 que o ranking denso deixou em 14º ranqueou em 1º no braço léxico — e a fusão o promoveu ao topo do conjunto de candidatos. Essa recuperação já estava verde na configuração de 71%; o re-ranking da camada seguinte foi o que resolveu as perguntas de grounding restantes.
A correção, camada 2: over-fetch e re-ranking listwise
A fusão melhora a ordem, mas continua sendo uma heurística barata sobre dois rankings aproximados. A segunda camada começa com um over-fetch: em vez de buscar só os 5 finais, o pipeline puxa um conjunto maior de candidatos (k_overfetch, default 20). Esse número importa porque nenhum re-ranker salva um trecho que não chegou à mesa — com 20 candidatos, o chunk certo que a fusão deixou em 12º ainda está no jogo e pode ser promovido.
Sobre esses 20 roda um re-rank listwise via LLM: o modelo recebe a pergunta e a lista inteira de candidatos de uma vez e reordena o conjunto — diferente de um re-ranker pointwise, que pontua cada par pergunta-trecho isoladamente e não enxerga redundância nem complementaridade entre candidatos. Só depois vem o corte no top-k final (default 5). É a diferença entre "quais 20 trechos são plausíveis" (rápido, aproximado) e "quais 5 desses 20 respondem de fato à pergunta" (mais caro, mais preciso, aplicado a poucos candidatos — por isso o custo não explode). Com o re-rank ligado sobre o híbrido, o hit@5 fechou em 100% no benchmark.
O caminho completo de uma pergunta até o contexto do modelo fica assim:
pergunta
|
+---------------+---------------+
| |
v v
braço denso braço léxico
(embeddings, cosseno) (BM25, tokens literais)
| |
v v
ranking denso ranking léxico
| |
+---------------+---------------+
|
v
fusão RRF — por posição, não por score
1/(k + rank_denso) + 1/(k + rank_léxico)
|
v
over-fetch: ~20 candidatos (k_overfetch)
|
v
re-rank listwise via LLM (pergunta + lista)
|
v
corte no top-k (default 5)
A terceira peça: cards de Q&A destilados
Além dos chunks crus, mantemos uma camada de Q&A destilado (o que chamamos de Refinery): perguntas e respostas curtas geradas a partir do corpus. Para consultas diretas, um card "qual o EC50 do AVX-006 → 22,4 µM" bate mais forte e mais limpo do que um parágrafo do PDF original. O modo qa_first prioriza esses cards na recuperação; o corpus cru continua lá para o que os cards não cobrem. E dá para forçar os dois extremos: qa_only responde só com cards, chunks ignora os cards e busca só no corpus cru. Os três modos estão expostos na API — o que nos leva ao próximo ponto.
Os botões reais: inspecionando a busca pela API
Tudo isso seria caixa-preta se você não pudesse ver o que a recuperação devolve antes de ela virar resposta. Cada agente expõe a própria busca em GET /v1/agents/{id}/knowledge/search — o mesmo pipeline que alimenta o chat, chamável direto. São quatro parâmetros: q (a pergunta, obrigatório), k (quantos trechos retornar, default 5, teto 20), source_id (restringe a busca a um único arquivo do corpus) e mode (qa_first, qa_only ou chunks).
curl -s "https://agents-api.catcher.one/v1/agents/AGENT_ID/knowledge/search?q=qual%20o%20EC50%20do%20AVX-006&mode=qa_first" \
-H "X-API-Key: ctc_SUA_CHAVE"
A resposta ecoa os parâmetros e devolve cada trecho com conteúdo, arquivo de origem e score à vista:
{
"query": "qual o EC50 do AVX-006",
"k": 5,
"source_id": "",
"mode": "qa_first",
"chunks": [
{
"content": "AVX-006: EC50 de 22,4 µM no ensaio de inibição...",
"source": "avx-006-relatorio-preclinico.pdf",
"score": 0.97
}
]
}
Debaixo desse endpoint ficam os botões internos que este post descreveu, com os defaults que o benchmark fixou: hybrid (liga a fusão RRF dos braços denso e léxico), lexical_weight (0 a 1, default 0.5 — inclina a fusão para o braço léxico), k_overfetch (quantos candidatos puxar antes do re-rank, default 20), rerank (o passe listwise, ligado por padrão) e qa_first (prioriza os cards do Refinery). Há ainda structured, um roteamento analítico gateado que só entra quando a pergunta pede agregação em vez de trechos. Na prática, você afina por chamada com k, mode e source_id, e usa o score e o source devolvidos para validar a recuperação no seu próprio corpus — antes de culpar o modelo pela resposta. No Console, a tela de testar busca mostra ainda o debug por braço: em qual dos dois o trecho casou e o rank em cada um (foi assim que enxergamos o "léxico 1º, denso 14º" lá de cima).
Por que um benchmark determinístico importa tanto
Nada disso seria confiável sem a régua fixa: o mesmo conjunto de sete perguntas, cada uma com o chunk-alvo conhecido, rodado antes e depois de cada mudança — um A/B real sobre a recuperação, não impressão de quem testou três perguntas no chat. "Melhorou a busca" é achismo; "o hit@5 saiu de 5/7 para 7/7 ao ligar o re-rank sobre o mesmo híbrido" é engenharia. E como o alvo é conhecido, o benchmark revela o mecanismo: RRF resgatou o exact-ID AVX-039; o re-ranker corrigiu as consultas de grounding. Quando alguém propõe a próxima mudança, a régua diz na hora se ela ajudou, atrapalhou ou não mudou nada.
O que isso significa na prática
Um hit@5 de 100% no benchmark não quer dizer que o agente nunca erra — quer dizer que, para as perguntas medidas, o trecho certo sempre chega ao modelo. A partir daí, a qualidade da resposta é do modelo e do prompt, não da busca. E como o agente cita a fonte de cada afirmação, você vê de onde a resposta veio — e quando a informação não está no corpus, ele responde "não consta" em vez de inventar. Foi essa combinação — recuperação forte + citação + recusa honesta — que validamos também com um corpus de 40 papers fictícios, impossível de acertar por conhecimento prévio do modelo.
Perguntas frequentes
O que é hit@5?
É a fração de perguntas em que o trecho correto aparece entre os 5 primeiros resultados da busca. Um hit@5 de 100% significa que, para todas as perguntas do benchmark, o chunk certo estava no top-5 entregue ao modelo.
Por que busca semântica sozinha falha?
Embeddings capturam significado, mas diluem tokens raros e exatos como códigos, SKUs e identificadores. Uma pergunta por "AVX-006" pode ranquear trechos semanticamente próximos acima do trecho que contém exatamente "AVX-006". A busca léxica resolve isso.
O que é RRF?
Reciprocal Rank Fusion combina duas listas ranqueadas (a densa e a léxica) somando 1/(k+posição) de cada documento. É robusto porque usa posições, não scores de escalas diferentes.
Como testo a recuperação do meu agente?
Pelo endpoint GET /v1/agents/{id}/knowledge/search, passando q (a pergunta) e, opcionalmente, k (default 5, teto 20), source_id (restringe a um arquivo) e mode (qa_first, qa_only ou chunks). A resposta devolve os chunks com content, source e score — o suficiente para ver exatamente o que chegaria ao modelo.
Suba seus documentos e veja a busca funcionando
A recuperação híbrida com re-ranking está pronta em cada agente da Catcher Agents. Crie um agente, suba um PDF e teste a busca no Console — com o score de cada trecho à vista.