Objetivo da Aula
Ordenar a sequência correta de operações de recuperação: filtro de RBAC antes de similaridade, não depois
Escolher top-k com critério de custo-benefício em tokens de contexto, não com um número arbitrário copiado de tutorial
Explicar por que similaridade de cosseno alta não é sinônimo de relevância, e o que fazer a respeito
Desenhar um pipeline de re-ranking em duas etapas (recall amplo → precisão no topo) como decisão de arquitetura, não otimização prematura
Instrumentar métricas de qualidade de retrieval que sobrevivem à mudança de modelo de embedding ou de vector store
Por que isso importa
Você já sabe calcular similaridade de cosseno e já rodou uma busca top-k contra um vector store. O que normalmente falta nesse conhecimento é a ordem certa das operações e os limites do que "similaridade alta" realmente garante. Um erro recorrente em pipelines RAG de primeira geração é tratar retrieval como uma função pura — query entra, chunks relevantes saem — quando na realidade é uma cadeia de decisões: quem pode ver o quê, quantos candidatos avaliar, como reordenar o que veio da busca vetorial antes de entregar ao LLM.
Impacto direto no seu trabalho: quando um agente em produção começa a "alucinar com base em fonte errada" — cita um documento real, mas do domínio errado, ou desatualizado, ou de um chunk que não deveria estar visível para aquele usuário — a causa quase nunca é o LLM. É um pipeline de retrieval que devolveu o candidato errado no top-k, ou que aplicou o filtro de permissão depois da busca de similaridade em vez de antes. Diagnosticar isso exige entender a ordem exata das operações — é o que este capítulo estabelece.
Conceitos Fundamentais
A ordem das operações: filtro antes de similaridade, sempre
O erro arquitetural mais caro em retrieval é inverter esta ordem: buscar por similaridade primeiro, filtrar por permissão depois. Parece equivalente — o resultado final teoricamente é o mesmo conjunto de chunks — mas não é, por dois motivos: correção e custo.
ORDEM ERRADA (filtro depois):
1. Embedding da query
2. Busca vetorial: top-20 por similaridade, ignorando RBAC
3. Filtra por escopos_permitidos → sobram 3 dos 20
4. Problema: se os 20 mais similares forem todos de um domínio
ao qual o agente não tem acesso, o resultado final fica vazio
ou raso — mesmo existindo chunks relevantes E permitidos
mais abaixo no ranking geral
ORDEM CORRETA (filtro antes):
1. Embedding da query
2. Resolve escopos_permitidos do agente (harness, RBAC — Parte 1.3)
3. Busca vetorial já restrita ao subconjunto permitido
(filtro pré-busca no índice, ou índice fisicamente separado
por domínio — decisão do capítulo 2.2)
4. Top-k calculado só dentro do universo permitidoA ordem correta não é só mais segura — é estruturalmente mais correta, porque garante que o top-k representa "os k mais relevantes entre os que o agente pode ver", que é a pergunta real sendo feita. A ordem errada responde a uma pergunta diferente ("os k mais relevantes no mundo, dos quais alguns sobreviveram ao filtro") e sistematicamente empobrece o resultado quando o universo permitido é uma fração pequena do índice total.
Fundamento: Similaridade de Cosseno
A maioria dos vector stores usa similaridade de cosseno para comparar o embedding da query com os embeddings indexados: mede o ângulo entre os dois vetores, ignorando magnitude — dois vetores apontando na "mesma direção" no espaço de embeddings têm similaridade 1.0, perpendiculares têm 0, opostos têm -1.0. Isso importa arquiteturalmente porque similaridade de cosseno mede proximidade semântica aproximada, não relevância factual, não atualidade, não autoridade da fonte. Um chunk pode ter alta similaridade de cosseno com a query e ainda assim estar desatualizado, contradizer outro chunk mais autoritativo, ou simplesmente estar fora de escopo — a métrica de similaridade não sabe nada disso.
Top-k: dimensionamento como orçamento de contexto, não número mágico
k=5 aparece em praticamente todo tutorial de RAG como se fosse universal. Na prática, top-k é uma decisão de orçamento: quantos tokens de contexto o agente pode gastar com material recuperado, dado o que sobra para instruções do sistema, histórico de conversa e a resposta esperada.
Orçamento de contexto de um agent loop (exemplo, janela de 32k tokens):
| Componente do orçamento | Tokens (estimado) |
|---|---|
| Instruções de sistema (harness, Parte 1.2) | ~2.000 |
| Estado/memória do agente (Parte 1.4) | ~4.000 |
| Histórico de conversa recente | ~6.000 |
| Espaço reservado para resposta | ~4.000 |
| Sobra para chunks recuperados (grounding) | ~16.000 |
Se cada chunk tem ~500 tokens (decisão do capítulo 2.2), o top-k máximo sustentável ≈ 16.000 / 500 ≈ 32 chunks. Mas isso é o TETO, não o alvo. Na prática, k alto demais dilui o sinal: o LLM recebe 32 chunks, muitos marginalmente relevantes, e a atenção se espalha. k=5 a k=10 costuma ser o ponto de equilíbrio para a maioria dos domínios — o teto acima só importa para saber quanta folga existe antes de estourar a janela.
O erro simétrico ao "k fixo copiado de tutorial" é "k generoso para não perder nada" — aumentar k indiscriminadamente na esperança de capturar mais contexto relevante. Isso raramente melhora a resposta e frequentemente piora: mais chunks concorrendo por atenção do LLM, mais chance de um chunk irrelevante ser citado como se fosse autoritativo, e mais tokens gastos (custo, tema da Parte 5) por consulta.
Re-ranking em duas etapas: recall amplo, depois precisão no topo
Busca vetorial pura otimiza para recall — trazer candidatos relevantes dentro de um top-N maior — melhor do que otimiza para precisão no topo — garantir que os primeiros k resultados sejam exatamente os mais relevantes entre os relevantes. Um pipeline de retrieval maduro separa essas duas etapas em vez de tratar o resultado bruto do vector store como final.
| Etapa | Processo | Objetivo | Custo |
|---|---|---|---|
| 1 — Recall amplo (busca vetorial) | query → embedding → busca no índice → top-50 candidatos | Não deixar nenhum chunk verdadeiramente relevante de fora | Barato, é a operação nativa do vector store |
| 2 — Re-ranking de precisão (cross-encoder ou scoring adicional) | top-50 candidatos → modelo de re-ranking avalia cada par (query, chunk) individualmente → reordena → top-5 final entregue ao agente | Dos 50 candidatos plausíveis, ordenar pelos que realmente respondem à query | Mais caro por candidato (avalia par a par, não em batch vetorial), por isso só faz sentido aplicado ao subconjunto reduzido da Etapa 1, nunca ao índice inteiro |
A justificativa técnica: embeddings de busca (bi-encoders) codificam query e documento separadamente, otimizando para velocidade de busca em milhões de vetores — isso é o que torna o índice vetorial viável em escala. Um re-ranker (cross-encoder) processa o par query-documento junto, capturando interação mais fina entre os dois textos, mas é caro demais para rodar contra o índice inteiro. A combinação em duas etapas — recall barato e amplo, seguido de precisão cara e restrita — é o padrão que equilibra custo computacional com qualidade de resultado.
Aprofundamento Técnico
Similaridade alta não é relevância: os três modos de falha
Tratar score de similaridade como proxy direto de "isso responde a pergunta" produz três classes de erro recorrentes em produção:
| Modo de falha | Query | Chunk recuperado | Explicação |
|---|---|---|---|
| 1 — Similaridade lexical disfarçada de semântica | "Como cancelar minha assinatura?" | Score alto: "Este documento não trata de cancelamento de assinatura, mas sim de renovação automática." | Alta similaridade porque compartilha vocabulário ("assinatura", "cancelamento"), mas o CONTEÚDO nega exatamente o que a query pede. |
| 2 — Chunk desatualizado com score alto | "Qual é o SLA de suporte atual?" | Política de SLA de 2023, substituída em 2025, mas nunca removida do índice (falha de invalidação — capítulo 2.2) | Score de similaridade não carrega noção de "atualidade"; isso precisa vir de metadado (data_documento) e lógica de desempate explícita, não da busca vetorial sozinha. |
| 3 — Fragmento sem contexto suficiente | "Essa cláusula se aplica a contratos internacionais?" | Só o corpo da cláusula, sem o cabeçalho que define o escopo de aplicação (que ficou em chunk anterior, fora do top-k) | Sintoma de overlap insuficiente ou granularidade errada (capítulo 2.2), não um problema do algoritmo de ranking em si. |
Os três modos de falha têm mitigação diferente: o primeiro pede um re-ranker semântico mais forte (Etapa 2 acima); o segundo pede desempate por metadado de atualidade combinado ao score; o terceiro é resolvido lá atrás, na política de chunking — reforçando que retrieval não conserta um chunking mal feito, só trabalha dentro dos limites que ele impõe.
Filtros de metadado como segunda dimensão de ranking
Além do filtro binário de RBAC (pode/não pode ver), metadado pode entrar como sinal de ranking suave — não excluindo candidatos, mas ajustando a ordem final.
score_final = (peso_similaridade × score_cosseno)
+ (peso_recencia × fator_recencia(data_documento))
+ (peso_autoridade × fator_autoridade(dono, tipo_doc))Exemplo com pesos (0.7 similaridade, 0.2 recência, 0.1 autoridade):
| Atributo | Chunk A | Chunk B |
|---|---|---|
| score_cosseno | 0.82 | 0.79 |
| Documento (ano) | 2024 | 2026 |
| Fonte | wiki interna | política oficial |
| score_final | 0.7×0.82 + 0.2×0.4 + 0.1×0.3 = 0.684 | 0.7×0.79 + 0.2×0.9 + 0.1×0.9 = 0.823 |
Chunk B assume o topo mesmo com similaridade bruta menor, porque é mais recente e vem de fonte mais autoritativa.
Esse tipo de composição de score é a diferença entre um retrieval "tecnicamente correto" (mais similar textualmente) e um retrieval "arquiteturalmente correto" (mais confiável dado o contexto de negócio). Os pesos não são universais — dependem do domínio: em busca de documentação de código, recência pesa mais que em busca de textos legais consolidados, onde a cláusula vigente pode ser antiga e ainda válida.
Métricas de qualidade de retrieval que sobrevivem a trocas de infraestrutura
Avaliar retrieval só "olhando se a resposta final do LLM pareceu boa" mistura dois sistemas (retrieval e geração) numa métrica só, dificultando diagnóstico. Métricas específicas de retrieval, independentes do LLM usado depois, permitem trocar modelo de embedding ou vector store e continuar comparando maçã com maçã.
| Métrica | Definição | Fórmula |
|---|---|---|
| Precision@k | Dos k chunks recuperados, quantos são de fato relevantes? | Precision@5 = (chunks relevantes nos top-5) / 5 |
| Recall@k | Dos chunks verdadeiramente relevantes que existem no índice, quantos apareceram no top-k? | Recall@10 = (relevantes recuperados no top-10) / (total de relevantes) |
| MRR (Mean Reciprocal Rank) | Para perguntas com uma resposta certa conhecida, em que posição do ranking ela apareceu, em média? | MRR = média de (1 / posição_da_resposta_certa) sobre um conjunto de queries de avaliação. MRR=1.0 significa "sempre em primeiro lugar"; MRR=0.25 significa "em média na 4ª posição" |
Essas métricas exigem um conjunto de avaliação — um punhado de perguntas com resposta esperada conhecida, curado manualmente e versionado junto com a política de chunking. Sem esse conjunto, qualquer mudança de granularidade, modelo de embedding ou peso de re-ranking é avaliada "no olho", e regressões silenciosas de qualidade (retrieval piorou 15% depois da última reindexação) passam despercebidas até virar reclamação de usuário — o gatilho errado para descobrir um problema de arquitetura.
Busca híbrida: quando similaridade vetorial sozinha não basta
Similaridade vetorial captura proximidade semântica, mas falha sistematicamente num tipo de consulta específico: identificadores exatos. Nome de cláusula, número de contrato, código de erro, nome próprio pouco comum — esses termos costumam ter representação fraca no espaço de embeddings (o modelo nunca viu aquele identificador o suficiente para posicioná-lo com precisão), mas são exatamente o tipo de termo em que correspondência léxica exata é trivial e decisiva.
Query de exemplo: "cláusula 14.3.2 do contrato MZK-2024-0091"
| Abordagem | Mecanismo | Resultado |
|---|---|---|
| Busca vetorial pura | embedding da query ≈ embedding de "cláusula sobre valores contratuais" | Recupera cláusulas semanticamente parecidas, possivelmente de OUTROS contratos, porque "MZK-2024-0091" não tem representação vetorial distintiva o suficiente |
| Busca lexical (BM25 ou equivalente) | Match exato/quase-exato do token "MZK-2024-0091" e "14.3.2" | Recupera o chunk certo com alta confiança, mesmo que a formulação da pergunta seja diferente da formulação do documento |
| Busca híbrida (combinação dos dois scores) | score_final = peso_vetorial × score_cosseno_normalizado + peso_lexical × score_bm25_normalizado | Captura tanto a intenção semântica quanto o match exato de identificador, sem depender de um método cobrir a fraqueza do outro |
A implicação arquitetural é que "escolher um vector store" não é a mesma decisão que "escolher uma estratégia de retrieval". Um índice puramente vetorial é insuficiente para domínios com muitos identificadores estruturados (contratos, tickets numerados, códigos de produto, referências legais) — nesses casos, a infraestrutura de grounding (capítulo 2.1) precisa expor tanto um índice vetorial quanto um índice lexical/invertido, com a etapa de fusão de score (reciprocal rank fusion ou combinação ponderada, como acima) sendo parte do contrato de retrieval, não um detalhe de implementação escondido dentro do vector store escolhido.