Avançado 11 minOtimização

Pesquisa híbrida: BM25 + vectoriel

Resposta direta

A pesquisa híbrida inicia em paralelo uma busca por palavras-chave (BM25) e uma busca vetorial sobre a mesma pergunta, depois combina as duas classificações, geralmente por Reciprocal Rank Fusion (RRF). Ela compensa os casos em que o embedding falha (identificadores, nomes próprios, termos raros) sem perder a compreensão de paráfrases. Qdrant, Weaviate e Elasticsearch a oferecem nativamente; com ChromaDB, ela é construída em trinta linhas de Python.

Uma base vetorial encontra o sentido, não as correspondências exatas: uma referência como «RG/2024-117» ou um número de ticket pode passar despercebido por ela. Por outro lado, a busca por palavras-chave não entende que «automóvel» e «carro» designam a mesma coisa. Este guia mostra como combinar as duas abordagens, qual método de fusão escolher, o que fazem Qdrant e Weaviate e uma armadilha de tokenização em francês que compromete silenciosamente a parte BM25.

Por Mohamed Meguedmi·Atualização 2026-09-30·Testado no Windows, macOS e Linux

#Por que a busca puramente vetorial falha em algumas perguntas

A busca vetorial transforma cada trecho em um vetor que resume seu significado geral e depois retorna os trechos cujo vetor é mais próximo do vetor da pergunta. Essa compressão funciona muito bem para paráfrases, mas mal para tudo que precisa ser encontrado exatamente: um identificador, um código de erro, um nome de pessoa, uma sigla interna. Para o embedding, "PROD-4817" e "PROD-4871" se parecem, e um ticket sem relação pode aparecer antes do correto. A busca híbrida corrige essa falha: ela adiciona uma segunda classificação baseada na presença exata das palavras e combina as duas para que cada método compense os pontos cegos do outro.

Identificadores e referências
Números de ticket, de processo, de contrato, SKU, códigos de erro: são strings que um embedding não conserva de forma confiável e que BM25 encontra assim que aparecem no trecho.
Vocabulário especializado pouco frequente
Termos médicos, jurídicos ou técnicos pouco comuns: o peso de uma palavra rara é alto no BM25, enquanto seu embedding pode ser vago.
Consultas muito curtas
Dois termos como 'fatura 2024' não fornecem muita base para um embedding; as palavras-chave, por outro lado, são comparadas exatamente como estão.
Paráfrases e reformulações
O caso inverso: 'contrato de duração determinada' e 'CDD' ou 'carro' e 'automóvel' não compartilham nenhuma palavra; apenas a busca vetorial os aproxima
i
Um caso típico
Em uma base de tickets, a consulta «PROD-4817» feita por busca puramente vetorial retorna tickets com conteúdo semelhante, enquanto a busca por palavras-chave isola imediatamente o único ticket com esse número.

#BM25: o que faz a pesquisa por palavras-chave

O kit RAG Local

Seus documentos, sua IA: um RAG local confiável para seus PDFs, notas e e-mails — sem enviar nada para a nuvem.

  • Espaço online vitalício
  • PDF + arquivos
  • Atualizações vitalícias

BM25 é a função de classificação lexical usada pelo Lucene, pelo Elasticsearch e pelo OpenSearch, e que o SQLite oferece no módulo FTS5 (a documentação descreve sua função bm25() como retornando um valor que indica o grau de correspondência da linha com a consulta). Ela se baseia em três ideias: uma palavra rara tem mais peso que uma palavra frequente, uma palavra repetida conta cada vez menos após um certo número de ocorrências, e um trecho longo é ligeiramente penalizado em relação a um curto. O parâmetro k1 regula essa saturação: segundo a descrição da Elastic, ele limita a influência que um único termo da consulta pode ter sobre a pontuação de um documento.

Pontos fortes
Termos raros, identificadores, consultas curtas, vocabulário especializado, nenhum modelo para carregar ou treinar, índice compacto, resultado explicável (sabemos qual palavra fez o trecho aparecer nos resultados).
Fraquezas
Sinônimos, reformulações, paráfrases, erros de digitação; sem lematização, 'assinado' e 'assinatura' são palavras diferentes.
Requisitos prévios
Uma tokenização cuidadosa: conversão para minúsculas, remoção dos acentos e, eventualmente, redução ao radical (stemming). É nesse ponto que a maioria das implementações próprias erra; veja abaixo.

#Busca híbrida: o princípio

Executamos as duas buscas com a mesma pergunta, cada uma retorna sua lista de candidatos (20 a 50 passagens), depois mesclamos as duas listas em um único ranking. O ganho vem de uma observação simples: os trechos que aparecem nas duas listas são quase sempre relevantes, e cada método traz também passagens que o outro deixou de recuperar. O Weaviate define a pesquisa híbrida assim: combina os resultados de uma pesquisa vetorial e de pesquisa por palavras-chave ao mesclar os dois conjuntos de resultados, com uma metodologia de fusão e pesos relativos configuráveis. O ganho em números depende do corpus e das perguntas: nenhum valor geral é confiável, e deve ser medido em seus próprios documentos (ver abaixo).

#Combinar sem normalizar: Reciprocal Rank Fusion

O erro comum é somar as pontuações brutas. Uma pontuação BM25 é um número positivo sem limite superior; uma pontuação de similaridade vetorial é uma distância ou um cosseno, em uma faixa limitada: as duas escalas são incomparáveis, e a menor mudança no corpus as desloca. A Reciprocal Rank Fusion contorna o problema ao usar apenas as posições nos rankings. A documentação do Elasticsearch a apresenta como um método que não exige ajustes e cujos indicadores de relevância não precisam estar relacionados entre si.

Fórmula RRF
score(doc) = somme, sur chaque liste i où le doc apparaît, de 1 / (k + rang_i(doc))

k = 60 par défaut (valeur par défaut d'Elasticsearch)
rang_i = 1 pour le premier de la liste, 2 pour le deuxième, etc.
Un doc absent d'une liste n'ajoute rien pour cette liste.

Um trecho classificado em primeiro lugar pelo BM25 e em quinto pela busca vetorial obtém 1/61 + 1/65, ou seja, aproximadamente 0,0318; um trecho classificado em décimo quinto nas duas listas obtém 2/75, ou seja, aproximadamente 0,0267. A constante k atenua a vantagem da primeira posição: quanto maior ela for, mais as posições distantes contam. O Elasticsearch documenta essa constante com o nome rank_constant, com valor padrão de 60, e um tamanho de janela (rank_window_size) que define o comprimento de cada lista antes da fusão. Uma janela maior melhora a relevância à custa do desempenho, segundo a documentação.

#E a fusão por pontuação?

Alguns mecanismos oferecem uma alternativa: normalizar as pontuações de cada lista e depois combiná-las usando um peso. O Weaviate documenta os dois métodos: uma classificação por posições e uma fusão por pontuações relativas, sendo esta última o método padrão desde a versão 1.24; ela é necessária para usar o autocut com o operador híbrido. O Qdrant oferece RRF e DBSF, sendo que esta última preserva as pontuações brutas, mas normaliza sua distribuição (média e desvio padrão) antes da combinação. A fusão por posições é mais robusta quando não se conhece a distribuição das pontuações; a fusão por pontuações permite um ajuste mais fino quando é possível medir.

#Qual ferramenta: mecanismos com suporte nativo à busca híbrida

Busca híbrida conforme a ferramenta (documentação oficial, setembro de 2026)
FerramentaHíbrido nativoFusãoAjuste do peso
QdrantSim, via API Query (disponível a partir da versão 1.10)RRF ou DBSFPeso por requisição e constante k ajustáveis nas versões recentes
WeaviateSim, operador hybridPosições no ranking ou pontuações relativas (padrão desde a versão 1.24)Parâmetro alpha: 1 = puramente vetorial, 0 = apenas palavras-chave
ElasticsearchSim, recuperador rrfRRFrank_constant (60 por padrão) e rank_window_size
SQLite FTS5 + extensão vetorialA ser montadoA ser escritoA seu critério
ChromaDB + rank_bm25Para montar em PythonA ser escrito (RRF em 6 linhas)A seu critério

A diferença fundamental não está na fusão em si, que cabe em poucas linhas, mas no índice: um mecanismo nativo mantém os dois índices atualizados em conjunto, enquanto uma solução montada por conta própria mantém o índice BM25 na memória e precisa reconstruí-lo a cada adição de documento. Para um corpus de alguns milhares de trechos que muda raramente, essa solução é mais do que suficiente. Acima disso, ou assim que os documentos passam a mudar diariamente, um mecanismo nativo evita inconsistências entre os dois índices. O guia sobre o Weaviate apresenta essa ferramenta em detalhes.

#Implementação própria: ChromaDB, rank_bm25 e RRF

O código a seguir combina as três peças. Ele corrige um defeito comum em exemplos: a função de normalização deve remover os sinais diacríticos antes de filtrar; caso contrário, cada letra acentuada divide uma palavra em duas.

Híbrido BM25 + ChromaDB com RRF
import re, unicodedata
import chromadb
from rank_bm25 import BM25Okapi

def normalize(txt):
    txt = unicodedata.normalize("NFKD", txt.lower())
    txt = "".join(c for c in txt if not unicodedata.combining(c))  # retire les accents
    return re.findall(r"[a-z0-9]+", txt)

# Indexation BM25 (en mémoire) : l'indice de la liste = l'identifiant du passage
all_docs = [d["text"] for d in load_docs()]
bm25 = BM25Okapi([normalize(d) for d in all_docs])

# ChromaDB : les ids doivent être les mêmes, sous forme de chaînes "0", "1", ...
coll = chromadb.PersistentClient("./chroma_db").get_collection("docs")

def rrf_fuse(rankings, weights=None, k=60):
    weights = weights or [1.0] * len(rankings)
    scores = {}
    for ranking, w in zip(rankings, weights):
        for rank, doc_id in enumerate(ranking, start=1):
            scores[doc_id] = scores.get(doc_id, 0) + w / (k + rank)
    return sorted(scores, key=scores.get, reverse=True)

def hybrid_search(question, top_k=5, n_candidates=20):
    s = bm25.get_scores(normalize(question))
    bm25_top = sorted(range(len(all_docs)), key=lambda i: -s[i])[:n_candidates]
    bm25_ranking = [str(i) for i in bm25_top]
    vec_ranking = coll.query(query_texts=[question], n_results=n_candidates)["ids"][0]
    fused = rrf_fuse([bm25_ranking, vec_ranking])
    return [all_docs[int(i)] for i in fused[:top_k]]
!
Armadilha de tokenização em francês
Com a normalização comum “NFKD e depois substituir tudo que não for a-z ou dígito por um espaço”, “référence” vira “re fe rence”: cada acento deixa uma marca combinante que é substituída por um espaço. A parte BM25 continua funcionando com identificadores, mas deixa de encontrar todas as palavras acentuadas. Teste normalize em três frases antes de indexar.

Duas precauções práticas. Primeiro, os identificadores devem ser iguais nos dois índices: aqui, o índice da lista, convertido em string, serve de identificador no Chroma. Em seguida, o índice BM25 em memória desaparece quando o programa é encerrado: reconstrua-o ao iniciar, o que leva alguns segundos para dezenas de milhares de passagens, ou salve a lista de documentos ao lado da base.

#Ajustar o equilíbrio entre BM25 e busca vetorial

Por padrão, RRF dá o mesmo peso às duas listas. Se o seu corpus for rico em referências (jurisprudência, tickets, catálogos), dê mais peso ao BM25; se as perguntas forem conversacionais, mantenha o equilíbrio ou favoreça a busca vetorial. Na função anterior, basta passar weights=[0.6, 0.4] para dar 60% de peso ao BM25. Qdrant permite o mesmo ajuste: sua documentação indica que o peso de cada consulta vale 1 por padrão, o que restaura a fórmula original do RRF, e que a constante k é ajustável nas versões recentes. No Weaviate, ajusta-se alpha: 1 corresponde à busca puramente vetorial, 0 à busca exclusivamente por palavras-chave.

Ponto de partida conforme o tipo de corpus
Corpus e perguntasPesos iniciais BM25 / vetorialO que você está monitorando
Referências, números, nomes próprios (área jurídica, tickets, catálogos)60 / 40As perguntas por identificador aparecem primeiro?
Documentação redigida, perguntas em linguagem natural40 / 60As reformulações conseguem recuperar o trecho correto?
Corpus misto ou desconhecido50 / 50Recall@5 em 30 a 50 perguntas reais
Siglas e jargão profissional55 / 45, com um dicionário de sinônimos na busca BM25A sigla e sua forma por extenso encontram o mesmo trecho?
→
Medir antes de ajustar
Esses valores são pontos de partida, não resultados medidos. Monte 30 a 50 perguntas reais com o trecho esperado, calcule o recall nos 5 primeiros resultados para BM25 sozinho, busca vetorial sozinha e, em seguida, busca híbrida, e mantenha a busca híbrida apenas se ela apresentar resultados melhores nos seus documentos.

#Após a fusão: adicionar um reranker

A fusão gera um conjunto mais diversificado de candidatos do que cada método isoladamente. Um reranker pode então ordenar esse conjunto lendo cada trecho junto com a pergunta: as duas técnicas podem ser combinadas. A ordem habitual é busca híbrida, seguida da fusão, depois a aplicação de um reranker aos 20 a 50 primeiros resultados e, por fim, a inclusão dos 3 a 5 melhores trechos no prompt. O guia sobre o reranker detalha essa última etapa, e o guia sobre chunking explica por que o tamanho dos trechos influencia tanto o BM25 quanto os embeddings.

#Perguntas frequentes sobre busca híbrida

FAQ
O que é a pesquisa híbrida em um RAG?+
É a combinação de duas pesquisas sobre a mesma pergunta: uma pesquisa vetorial, que localiza trechos com significado próximo, e uma pesquisa por palavras-chave (BM25), que localiza trechos contendo os mesmos termos. As duas classificações são fundidas em uma só, geralmente por Reciprocal Rank Fusion, antes de enviar os melhores trechos para o modelo.
É necessário normalizar as pontuações BM25 e vetoriais antes de somá-las?+
Isso não é necessário com o RRF, que utiliza apenas as posições na classificação, e esse é justamente o objetivo: as duas escalas não são comparáveis. Se você preferir combinar as pontuações, é necessário normalizá-las primeiro, como faz a fusão por pontuações relativas do Weaviate ou o DBSF do Qdrant, e verificar se o resultado permanece estável quando o corpus muda.
Qual valor de k escolher em RRF?+
Mantenha 60, o valor padrão do Elasticsearch, a menos que tenha uma razão para fazer diferente. Um valor menor favorece mais as primeiras posições, e um maior dá mais influência às posições mais baixas na classificação. Esse ajuste tem pouca influência em relação à qualidade da tokenização e ao número de candidatos em cada lista.
É possível fazer busca híbrida com ChromaDB?+
Sim, combinando: a busca vetorial com Chroma, um índice BM25 em Python (a biblioteca rank_bm25) e uma fusão RRF de algumas linhas. Certifique-se de usar os mesmos identificadores nos dois índices e remova os acentos na tokenização. Para um corpus que muda frequentemente, um motor híbrido nativo como Qdrant ou Weaviate evita manter dois índices.
A busca híbrida torna as consultas muito mais lentas?+
Muito pouco, em geral: BM25 em um índice na memória é muito rápido, e a busca vetorial já ocorre. O custo real vem do eventual reranker aplicado em seguida e da memória do índice lexical. Meça o tempo de ponta a ponta das suas consultas, e não o tempo de cada etapa isolada.
Como saber se o híbrido vale a pena para meus documentos?+
Compare, usando de 30 a 50 perguntas reais para as quais você conhece o trecho esperado, o recall nos 5 primeiros resultados do BM25 sozinho, da busca vetorial sozinha e da busca híbrida. Se a busca híbrida não trouxer nenhum ganho, seu corpus pode ser puramente conversacional, e a busca vetorial é suficiente. Se o ganho ocorrer principalmente nos identificadores, aumente o peso do BM25.
Este guia ajudou você?

Um comentário, um erro ou uma observação? Avise-nos; isso ajuda a melhorar o guia para todos.