RAG híbrido é rodar duas buscas para cada pergunta, uma lexical (BM25, que casa palavras exatas) e uma vetorial densa (embeddings, que casa significado), e fundir as duas listas antes de mandar os trechos ao modelo. Em documentação técnica isso costuma fazer diferença, porque as perguntas misturam linguagem natural (“a impressora parou de puxar papel”) com termos que precisam bater letra por letra: códigos de erro, siglas, números de peça e nomes de parâmetro.
A fusão mais usada é a Reciprocal Rank Fusion (RRF), que combina as listas pela posição de cada documento, sem precisar comparar notas de escalas diferentes. Elasticsearch, OpenSearch, Weaviate e Qdrant já trazem híbrido nativo; no PostgreSQL você monta com pgvector e a busca textual do próprio banco. O que decide a qualidade, porém, é a avaliação com perguntas reais e o cuidado com o português: stemming, acentos e stopwords.
Resumo rápido
- BM25 acerta termos exatos (E-104, NBR 5410, SKU 7731-B); embeddings acertam paráfrases e sinônimos. Em manuais técnicos você precisa dos dois.
- RRF soma 1/(k + posição) de cada lista; é simples, robusta e dispensa normalizar notas.
- Elasticsearch tem o retriever
rrf; OpenSearch usa queryhybridcom pipeline; Weaviate temhybridcomalpha; Qdrant usaprefetchcom fusão; no Postgres você faz a fusão em SQL.- Meça recall@k e MRR com um conjunto de perguntas rotuladas antes e depois de ligar o híbrido.
- Em português, configure analisador com stemming do idioma e trate acentos dos dois lados (indexação e consulta).
BM25 e embeddings densos: o que cada um enxerga
BM25 é uma função de ranqueamento lexical da família TF-IDF. Ela pontua um documento pela frequência dos termos da consulta nele, dá mais peso a termos raros no acervo e normaliza pelo tamanho do documento. Dois parâmetros controlam o comportamento: k1 (saturação da frequência, para que repetir uma palavra 50 vezes não valha 50 vezes mais) e b (quanto o tamanho do documento penaliza). No Elasticsearch, BM25 é a similaridade padrão, com k1 = 1.2 e b = 0.75.
Embeddings densos transformam cada trecho em um vetor de números que representa o sentido do texto. A busca encontra os vetores mais próximos do vetor da pergunta. Se você quer revisar o conceito, veja nosso guia sobre o que são embeddings.
Os pontos cegos são opostos. Um modelo de embeddings pode achar que “erro E-104” e “erro E-140” são quase a mesma coisa, porque o sentido geral é parecido. O BM25 não confunde os dois, mas não sabe que “não liga” e “sem energia” falam do mesmo sintoma.
| Critério | BM25 (lexical) | Embeddings densos |
|---|---|---|
| Códigos de erro, SKUs, números de norma | Forte: casa o token exato | Fraco: códigos parecidos viram vizinhos |
| Siglas (CLP, IHM, NR-12) | Forte, se a sigla estiver no texto | Variável: depende do modelo conhecer a sigla |
| Paráfrase e sinônimo | Fraco | Forte |
| Pergunta longa em linguagem natural | Médio: muitas palavras irrelevantes | Forte |
| Dependência de idioma | Alta: precisa de analisador do idioma | Depende do modelo ser multilíngue |
| Explicabilidade | Alta: dá para ver quais termos casaram | Baixa |
Como a fusão RRF funciona
As notas do BM25 não têm teto, enquanto similaridade de cosseno fica em uma faixa limitada. Somar as duas diretamente não faz sentido. A Reciprocal Rank Fusion, proposta por Cormack, Clarke e Büttcher no SIGIR 2009, ignora as notas e olha só a posição:
rrf(d) = soma, para cada lista L, de 1 / (k + posição de d em L)
Um documento que aparece em 1º no BM25 e em 3º na busca vetorial ganha das duas listas; um que só aparece em uma delas ganha de uma. A constante k suaviza a diferença entre as primeiras posições. No artigo original ela foi fixada em 60, e esse é o padrão do Elasticsearch e do OpenSearch. O Qdrant documenta um padrão diferente para o seu k, então não assuma que todos os bancos usam o mesmo valor.
A alternativa é a fusão por nota normalizada (min-max, z-score ou similar), oferecida pelo OpenSearch no normalization-processor, pelo Weaviate na relativeScoreFusion e pelo Qdrant na DBSF. Ela preserva a informação de “quão melhor” um resultado é, mas fica mais sensível a distribuições estranhas de nota. Comece por RRF e só troque se a avaliação mostrar ganho.
Quando o híbrido ganha (e quando não compensa)
O híbrido costuma ajudar em acervos com muito identificador e pouca redundância de texto:
- Códigos de erro e alarmes: “F0002”, “E-104”, “ALM 0x1F”.
- Siglas e jargão interno: nomes de sistema, módulos, setores.
- Números de peça e modelos: o técnico digita o código impresso na etiqueta.
- Nomes de parâmetro e comandos:
max_connections,systemctl restart. - Normas: NBR, NR, ISO com número e ano.
Em FAQ curto e bem escrito, com perguntas sempre em linguagem natural, a busca vetorial sozinha pode bastar, e o BM25 só acrescenta ruído. Por isso a decisão sai da avaliação, não da moda. Depois da fusão, um reranker cross-encoder pode reordenar os melhores candidatos; mostramos isso em RAG com reranker BGE e ColBERT para documentos técnicos.
Como implementar em cada ferramenta
Elasticsearch
O retriever rrf combina um retriever standard (BM25) com um knn (vetorial). Os padrões documentados são rank_constant 60 e rank_window_size 10. Confira na página de assinaturas da Elastic se o recurso está no seu plano.
GET manuais/_search
{
"retriever": {
"rrf": {
"retrievers": [
{ "standard": { "query": { "match": { "texto": "erro E-104 bomba" } } } },
{ "knn": { "field": "vetor", "query_vector": [0.12, -0.03, 0.88],
"k": 20, "num_candidates": 100 } }
],
"rank_window_size": 50
}
}
}
OpenSearch
Você cria um search pipeline e envia uma query hybrid com as subconsultas. Para RRF, o processador é o score-ranker-processor (introduzido na versão 2.19, com rank_constant padrão 60). Para fusão por nota, o normalization-processor (desde a 2.10).
PUT /_search/pipeline/rrf-pipeline
{ "phase_results_processors": [
{ "score-ranker-processor": { "combination": { "technique": "rrf" } } } ] }
GET /manuais/_search?search_pipeline=rrf-pipeline
{ "query": { "hybrid": { "queries": [
{ "match": { "texto": "erro E-104 bomba" } },
{ "knn": { "vetor": { "vector": [0.12, -0.03, 0.88], "k": 20 } } } ] } } }
Weaviate
A consulta hybrid junta busca por palavra-chave (BM25F) e vetorial. O parâmetro alpha controla o peso: 1 é vetorial pura, 0 é palavra-chave pura. A fusão padrão é a relativeScoreFusion desde a versão 1.24; a rankedFusion usa posições.
manuais = client.collections.use("Manual")
r = manuais.query.hybrid(query="erro E-104 bomba", alpha=0.5, limit=10)
Qdrant
A Query API (desde a 1.10) usa prefetch para rodar a busca esparsa e a densa e depois funde com rrf ou dbsf. O BM25 entra como vetor esparso: crie o vetor com "modifier": "idf" e gere os pesos no servidor com o modelo qdrant/bm25 ou no cliente com FastEmbed. Atenção: a documentação diz que o BM25 do Qdrant usa por padrão configurações de inglês para tokenização, stemming e stopwords. Ajuste o parâmetro language e confira se o português está entre as opções da sua versão.
PostgreSQL com pgvector
O README do pgvector recomenda combinar com a busca textual nativa do Postgres e fundir por RRF ou cross-encoder. Um detalhe importante: ts_rank e ts_rank_cd não são BM25. A documentação do Postgres avisa que essas funções não usam informação global do acervo, ou seja, não há IDF. Funciona bem como sinal lexical, mas não espere o mesmo comportamento do Elasticsearch.
WITH lex AS (
SELECT id, row_number() OVER (ORDER BY ts_rank_cd(tsv, q) DESC) AS pos
FROM trechos, websearch_to_tsquery('pt_unaccent', 'erro E-104 bomba') q
WHERE tsv @@ q ORDER BY pos LIMIT 50),
vet AS (
SELECT id, row_number() OVER (ORDER BY embedding <=> $1) AS pos
FROM trechos ORDER BY embedding <=> $1 LIMIT 50)
SELECT id, SUM(1.0 / (60 + pos)) AS rrf
FROM (SELECT * FROM lex UNION ALL SELECT * FROM vet) t
GROUP BY id ORDER BY rrf DESC LIMIT 10;
Para a parte vetorial, crie o índice com CREATE INDEX ON trechos USING hnsw (embedding vector_cosine_ops); e, para a textual, um índice GIN sobre a coluna tsv.
Pegadinhas do português: stemming, acentos e stopwords
O BM25 só é tão bom quanto o analisador que quebra o texto em termos. Em português, três cuidados resolvem a maior parte dos problemas:
- Stemming: reduz “bombas”, “bombeamento” e “bombear” a radicais. No Elasticsearch, o analisador
portugueseusa o stemmerlight_portuguese; no Postgres, a configuraçãoportugueseusa o dicionário Snowball do idioma. Analisador em inglês num acervo em português faz o BM25 perder recall sem dar erro. - Acentos: o técnico digita “manutencao” e o manual diz “manutenção”. O analisador
portuguesedo Elasticsearch não remove acentos sozinho; acrescente o filtroasciifoldingnum analisador próprio. No Postgres, a extensãounaccentpode ser encadeada antes do stemmer numa configuração copiada daportuguese. - Códigos não podem ser “stemizados”: o tokenizador pode quebrar “E-104” em “e” e “104”. Indexe o mesmo texto também num campo
keywordou com um analisador simples, e consulte os dois campos.
CREATE EXTENSION IF NOT EXISTS unaccent;
CREATE TEXT SEARCH CONFIGURATION pt_unaccent (COPY = portuguese);
ALTER TEXT SEARCH CONFIGURATION pt_unaccent
ALTER MAPPING FOR hword, hword_part, word WITH unaccent, portuguese_stem;
A regra de ouro: o mesmo tratamento na indexação e na consulta. A documentação do Qdrant repete esse alerta para as opções de BM25, e ele vale para qualquer ferramenta.
Como avaliar: recall@k e MRR
Monte de 50 a 200 perguntas reais (de chamados, chat de suporte, e-mails) e marque, para cada uma, qual trecho ou trechos contêm a resposta. Inclua de propósito perguntas com códigos e siglas, que são onde o híbrido deve mostrar valor.
- recall@k: fração das perguntas em que pelo menos um trecho correto aparece entre os k primeiros (ou, na versão estrita, fração dos trechos corretos recuperados). Use o k que você realmente envia ao modelo.
- MRR (Mean Reciprocal Rank): média de 1/posição do primeiro trecho correto. Acertar em 1º vale 1; em 2º, 0,5; fora da lista, 0. Mostra se o correto está no topo ou escondido.
Compare três configurações com as mesmas perguntas: só BM25, só vetorial e híbrido. Separe os resultados por tipo de pergunta, porque a média geral esconde que o híbrido pode ganhar nas perguntas com código e empatar nas demais. A geração (fidelidade da resposta) se avalia depois, como mostramos em esteiras de avaliação de alucinação com Ragas.
Exemplo: suporte técnico de uma fabricante de bombas em Joinville
Imagine uma fabricante de bombas hidráulicas em Joinville (SC) com manuais em PDF, boletins técnicos e uma tabela de códigos de alarme dos inversores. Os técnicos de campo perguntam coisas como “bomba centrífuga com E-104 depois da troca do selo” ou “como resetar o inversor depois de queda de energia”.
Na primeira versão, só com busca vetorial, a equipe percebe na revisão que perguntas com código traziam boletins de alarmes vizinhos. O caminho seria: separar a tabela de códigos em um trecho por código, indexar os textos no Postgres com a configuração pt_unaccent e um campo extra só com os códigos, manter os embeddings no pgvector e fundir por RRF. Em seguida, rodar a mesma planilha de perguntas rotuladas nas três configurações e decidir pelo recall@k e MRR medidos, separando perguntas com código das demais. Nenhuma ferramenta nova precisaria entrar na stack, já que o banco era o mesmo da aplicação.
Erros comuns
- Analisador errado. Usar o padrão em inglês num acervo em português. O BM25 funciona, mas pior, e ninguém percebe.
- Acento tratado só de um lado. Remover acentos na indexação e não na consulta (ou o contrário) quebra o casamento exato.
- Somar notas cruas. Adicionar nota BM25 a similaridade de cosseno sem normalizar. Use RRF ou um normalizador documentado.
- Janela pequena demais. Fundir só os 5 primeiros de cada lista. Busque mais candidatos (dezenas) antes da fusão e corte depois.
- Tratar
ts_rankcomo BM25. No Postgres não há IDF; termos comuns pesam como raros. - Avaliar só pela média. Sem separar perguntas com código, o ganho do híbrido some na média.
- Esquecer a citação. Busca melhor não dispensa mostrar ao técnico de qual manual e página veio a resposta, como explicamos em RAG na prática.
Perguntas frequentes
Preciso de um banco diferente para ter busca híbrida?
Não necessariamente. Elasticsearch, OpenSearch, Weaviate e Qdrant fazem os dois tipos de busca no mesmo lugar, e no Postgres o pgvector convive com a busca textual nativa. Use o que a equipe já opera.
Qual valor de k usar na RRF?
Comece pelo padrão da ferramenta (60 no Elasticsearch e no OpenSearch) e só mude se a avaliação mostrar ganho. Valores menores dão mais peso aos primeiros colocados de cada lista.
Vetores esparsos aprendidos (como SPLADE) substituem o BM25?
São outra opção de sinal lexical e alguns bancos aceitam vetores esparsos de qualquer origem. Mas exigem um modelo, e a qualidade em português depende de como ele foi treinado. BM25 com bom analisador é o ponto de partida mais simples.
O híbrido deixa a busca mais lenta?
Ele roda duas consultas e uma fusão, então há custo extra. Em acervos de documentação técnica de porte médio, isso costuma ser pequeno perto da chamada ao modelo, mas meça na sua infraestrutura.
Ainda preciso de reranker?
O reranker é uma etapa posterior e independente. A fusão melhora o conjunto de candidatos; o cross-encoder reordena esse conjunto com mais precisão. Avalie cada etapa separadamente.
Fontes e leitura recomendada
- Cormack, Clarke e Büttcher (2009): Reciprocal Rank Fusion outperforms Condorcet and individual Rank Learning Methods
- Elasticsearch: RRF retriever
- Elasticsearch: analisadores de idioma (inclui português)
- OpenSearch: score ranker processor (RRF)
- Weaviate: busca híbrida
- Qdrant: consultas híbridas
- pgvector: repositório e documentação
- PostgreSQL: ranqueamento na busca textual
- PostgreSQL: extensão unaccent