RAG avançado: busca híbrida e reranking na prática
Seu RAG responde bem na demo, mas erra perguntas simples em produção. Na maioria dos casos, o problema não está no LLM — está na busca. Embedding sozinho deixa documentos críticos fora do contexto. Busca híbrida com reranking resolve isso, e este artigo mostra exatamente como.
Por que embedding sozinho falha — e quando
Embedding (ou busca densa) é poderoso para capturar similaridade semântica: "automóvel" e "carro" ficam próximos no espaço vetorial mesmo sem compartilhar nenhuma letra. Isso parece ótimo até você se deparar com casos reais:
- Termos técnicos exatos: um usuário busca "cláusula 12.3 do contrato n.º 2024-0891". O embedding dilui a especificidade numérica — o BM25 acha na hora.
- Siglas e códigos internos: "NF-e 3.1", "SKU-7743", "CNPJ 12.345.678/0001-90" são praticamente opacos para modelos de embedding treinados em textos gerais.
- Perguntas curtas e literais: "qual o prazo?" em um documento de dezenas de páginas — a busca vetorial retorna parágrafos "contextualmente parecidos" mas sem a resposta literal.
Em benchmarks públicos (BEIR, MS MARCO), a busca puramente densa fica 5–15% abaixo do BM25 em domínios técnicos e jurídicos. Em contextos empresariais com documentação proprietária, essa diferença pode chegar a 30%.
Os três paradigmas de busca: comparação direta
Antes de combinar estratégias, é preciso entender o que cada uma oferece — e o que cada uma sacrifica.
| Dimensão | Busca Densa (Vetorial) | Busca Esparsa (BM25/TF-IDF) | Busca Híbrida (RRF) |
|---|---|---|---|
| Mecanismo | Similaridade coseno entre embeddings de query e chunk | Frequência e raridade de termos (TF × IDF) | Fusão das duas listas de ranking via Reciprocal Rank Fusion |
| Força principal | Sinônimos, paráfrases, variações semânticas | Termos exatos, códigos, siglas, números | Cobre ambas as fraquezas individualmente |
| Fraqueza principal | Termos raros, literais, numéricos | Perguntas com paráfrases, variações de vocabulário | Mais complexidade operacional; requer ambos os índices |
| Infraestrutura | Vector store (Qdrant, Weaviate, Pinecone, PGVector) | Elasticsearch, OpenSearch, Solr, BM25s (Python) | Vector store + motor de busca léxica (ou Weaviate/Qdrant nativos) |
| Latência típica | 5–50ms (ANN search) | 5–30ms (índice invertido) | 10–80ms (paralelo) + ~5ms para fusão |
| Quando usar | FAQs, documentação geral, atendimento ao cliente | Busca em contratos, logs, SKUs, IDs | Produção com documentos mistos — é o padrão recomendado |
Reciprocal Rank Fusion: como a mágica acontece
O RRF é um algoritmo simples e robusto para combinar múltiplas listas de ranking. A fórmula é:
RRF_score(doc d) = Σ 1 / (k + rank_i(d))
onde:
k = constante de suavização (tipicamente 60)
rank_i(d) = posição do documento d na lista i (1-indexed)
Σ = soma sobre todas as listas de ranking (densa + esparsa)
Intuição: um documento que aparece em 1º lugar em ambas as listas recebe score 1/(60+1) + 1/(60+1) ≈ 0,033. Um documento que aparece em 1º na busca densa mas em 100º na esparsa recebe 1/61 + 1/160 ≈ 0,022. Documentos relevantes para os dois métodos flutuam naturalmente para o topo.
O valor k=60 foi determinado empiricamente e é relativamente estável — você não precisa ajustá-lo na maioria dos casos. Algumas implementações usam pesos diferentes para cada lista (weighted RRF), o que pode ajudar quando um método é claramente mais confiável para o seu domínio.
Reranking com cross-encoder: a segunda camada de qualidade
A busca (densa, esparsa ou híbrida) usa bi-encoders: a query e cada chunk são codificados separadamente, e a similaridade é calculada depois. Isso é rápido, mas superficial.
O cross-encoder recebe query + chunk juntos como uma única entrada e produz um score de relevância muito mais preciso — porque pode analisar a interação entre os dois textos ao nível de atenção. A desvantagem: é lento demais para buscar em corpus completo. A solução: usá-lo apenas para reordenar os top-K (tipicamente 20–50) chunks já recuperados.
Pipeline de reranking: pseudocódigo
# Etapa 1: Busca híbrida (BM25 + vetorial)
resultados_densos = vector_store.buscar(query, top_k=30)
resultados_esparsos = bm25_index.buscar(query, top_k=30)
# Etapa 2: Fusão com RRF
def rrf_score(rank, k=60):
return 1.0 / (k + rank)
scores = {}
for i, doc in enumerate(resultados_densos):
scores[doc.id] = scores.get(doc.id, 0) + rrf_score(i + 1)
for i, doc in enumerate(resultados_esparsos):
scores[doc.id] = scores.get(doc.id, 0) + rrf_score(i + 1)
candidatos = sorted(scores.items(), key=lambda x: x[1], reverse=True)[:20]
# Etapa 3: Reranking com cross-encoder
pares = [(query, corpus[doc_id].texto) for doc_id, _ in candidatos]
scores_rerank = cross_encoder.predict(pares)
ranking_final = sorted(
zip(candidatos, scores_rerank),
key=lambda x: x[1],
reverse=True
)[:5] # top-5 para contexto do LLM
# Etapa 4: Geração com LLM
contexto = "\n\n".join([corpus[doc_id].texto for (doc_id, _), _ in ranking_final])
resposta = llm.gerar(prompt=f"Contexto:\n{contexto}\n\nPergunta: {query}")
Modelos de reranking disponíveis: open-source vs. API
Você não precisa treinar um cross-encoder do zero. Existem opções prontas para uso:
- bge-reranker-v2-m3 (BAAI): multilíngue, suporte nativo a português, roda em GPU com ~4GB de VRAM. Melhor opção open-source para documentos em PT-BR.
- ms-marco-MiniLM-L-6-v2: leve, roda em CPU, latência ~50ms para 20 pares. Treinado em inglês — use com cautela para PT-BR.
- Cohere Rerank 3: API paga (~US$ 1/mil queries), suporte a PT-BR, sem necessidade de infraestrutura. Bom ponto de entrada para provas de conceito.
- Jina Reranker v2: API freemium, multilíngue, contexto de até 8K tokens — útil para chunks maiores.
Para uma PME com volume de 10.000 queries/mês, a Cohere Rerank custa US$ 10/mês — negligível comparado ao custo do LLM. Para volumes acima de 100K queries, a GPU própria com bge-reranker se paga em 3–4 meses.
Implementação prática: Weaviate e Qdrant já suportam busca híbrida
A boa notícia: você não precisa manter dois sistemas separados (vector store + Elasticsearch). As principais soluções já integram busca híbrida nativamente:
- Weaviate: parâmetro
alphacontrola o peso entre densa (alpha=1.0) e esparsa (alpha=0.0). Usa BM25 internamente para a busca esparsa. - Qdrant: suporte a sparse vectors (SPLADE) combinados com dense vectors via RRF. Mais flexível que o Weaviate para customizações.
- Elasticsearch / OpenSearch: suporte nativo a dense vectors desde a versão 8.x. Permite queries híbridas com um único endpoint.
- PGVector + ParadeDB: opção para quem já usa PostgreSQL. ParadeDB adiciona BM25 ao Postgres, habilitando busca híbrida sem sair do banco.
Quando a busca híbrida não resolve o problema
Busca híbrida não é bala de prata. Ela não resolve:
- Chunking ruim: se seus chunks cortam frases no meio ou misturam tópicos, nem o melhor reranker salva. O problema está antes da busca. Veja estratégias de chunking semântico e hierárquico.
- Ausência do documento no índice: se a informação simplesmente não está indexada (documento esquecido, formato não suportado, atualização pendente), a busca retorna lixo com confiança.
- Perguntas multi-hop: "qual fornecedor tem o menor prazo para o produto mais vendido do Q1?" requer raciocínio encadeado em múltiplos documentos. Isso exige arquiteturas de agente, não apenas melhor retrieval.
- Qualidade de avaliação inexistente: se você não tem métricas para medir o que melhorou, pode estar trocando seis por meia dúzia. Veja como avaliar RAG com faithfulness e precision.
Ganhos realistas em produção: o que esperar
Com base em projetos que acompanhei e benchmarks públicos, estes são os ganhos típicos ao migrar de busca densa pura para híbrida com reranking:
- Context Recall: melhora de 15–30% (documentos relevantes que estavam sendo perdidos passam a entrar no contexto).
- Faithfulness: melhora de 10–20% (menos alucinação porque o contexto é mais preciso).
- Taxa de "não sei" / fallback: redução de 20–40% (o sistema para de dizer "não encontrei informação" quando a informação existe).
- Latência: acréscimo de 50–200ms. Em aplicações de atendimento ao cliente, isso é aceitável. Em aplicações de tempo real (< 500ms total), avalie com cuidado.
Um caso real: implementei busca híbrida com Cohere Rerank em um sistema de consulta de contratos para uma distribuidora com ~8.000 documentos PDF. A taxa de respostas corretas subiu de 61% para 84% no golden dataset, e o tempo médio de resposta foi de 1,8s para 2,3s — trade-off muito positivo para o contexto.
Próximos passos para o seu time
Se você já tem um RAG em produção com busca puramente vetorial, o caminho mais pragmático é:
- Monte um golden dataset de 50–100 pares pergunta/resposta esperada. Sem isso, você não vai saber se melhorou ou piorou. Veja como montar e usar um golden dataset.
- Adicione BM25 em paralelo usando a mesma base de chunks. BM25 é stateless e fácil de implementar — a biblioteca
rank_bm25em Python faz isso em 20 linhas. - Implemente RRF para fusão. O algoritmo cabe em 10 linhas de código.
- Adicione reranking com Cohere ou bge-reranker. Meça antes e depois no golden dataset.
- Itere no chunking se os ganhos forem menores que 10%. O gargalo provavelmente está antes da busca.