Curso
Às vezes, só a busca por texto completo ou apenas a busca vetorial não bastam para consultar um banco de dados e encontrar exatamente o que você procura. A combinação das duas é ótima quando o desenvolvedor lida com grandes volumes de dados multimodais e não estruturados que se beneficiam dos dois tipos de busca. Isso é a chamada busca híbrida — uma solução e tanto para um desafio complexo.
O que exatamente é busca híbrida?
Para entender bem a busca híbrida, primeiro precisamos entender o que é a busca por texto completo.
Busca por texto completo é um modo de pesquisa que casa termos literais da sua consulta com os documentos. Esse tipo de busca tradicional é, na verdade, o que muitos desenvolvedores já conhecem bem.
Por exemplo, se você procurar por “cafeteria fofa com área externa”, o mecanismo de busca vai procurar essas palavras exatas no banco de dados. Em resumo, a busca por texto completo é super precisa e eficiente, mas não funciona tão bem se você quiser resultados semelhantes usando sinônimos, paráfrases ou até quando há um erro de digitação na consulta.
Já a busca vetorial converte todos os dados em números, ou embeddings. Em vez de bater palavra por palavra, a busca vetorial compara o significado semântico da sua consulta com os documentos armazenados no banco.
Buscar por “cafeteria fofa com área externa” pode trazer “pães e café do lado de fora”, mesmo sem usar as mesmas palavras. A busca vetorial não é só semântica; ela também é muito flexível, mas às vezes pode retornar resultados amplos demais para a consulta especificada.
E onde entra a busca híbrida? Ela combina a busca por texto completo com a busca vetorial. Assim, os desenvolvedores aproveitam tanto a inteligência semântica dos vetores quanto os filtros precisos da busca por texto completo. É o melhor dos dois mundos — e muito útil ao trabalhar com grandes conjuntos de dados não estruturados.
Por que a busca híbrida é importante
A busca híbrida é valiosa em várias aplicações reais, como e-commerce, saúde e até recrutamento, entre outras.
No e-commerce, imagine entrar no seu site favorito e procurar por “cadeira de escritório confortável até US$ 100”. Com a busca vetorial, você verá itens semanticamente parecidos (como cadeiras ergonômicas), enquanto a busca por texto completo foca na palavra “cadeira” e ajuda a reforçar o teto de preço.
No recrutamento, se um recrutador busca em currículos por “engenheiro com experiência em NLP”, a busca híbrida captura tanto “processamento de linguagem natural” quanto a palavra-chave exata “engenheiro”.
Ou seja: você recebe resultados mais relevantes e confiáveis do que usando qualquer uma das abordagens isoladamente.
Busca híbrida no MongoDB
Vamos ver como fazer busca híbrida no MongoDB Atlas. Para acompanhar este tutorial, você vai precisar de alguns pré-requisitos:
Neste tutorial, vamos focar na coleção embedded movies no nosso banco de dados sample_mflix, acompanhando este tutorial de busca híbrida com $rankFusion, com alguns ajustes.
Embora o novo operador $rankFusion esteja disponível em clusters a partir da versão 8.1+ e facilite muito a busca híbrida no MongoDB, ele ainda está em Public Preview — e clusters do free tier ficam automaticamente na versão 8.0. Então, vamos seguir o tutorial, fazer algumas mudanças e concluir com tudo.
Providencie seu cluster
Ao criar o cluster, garanta que você está baixando o dataset sample_mflix. É com ele que vamos trabalhar durante todo o tutorial, focando na coleção sample_mflix.embedded_movies.

Confira rapidamente se o mongosh está instalado.
mongosh --version
Para este tutorial, estou usando a versão 2.4.2.
Agora, conecte-se ao seu cluster do MongoDB Atlas:
mongosh "mongodb+srv://<yourclusterhere>.mongodb.net/" --apiVersion 1 --username <yourusername>
O terminal vai pedir a senha e, depois de conectado, a saída será algo assim:

Agora podemos nos conectar ao banco. Rode o comando:
use sample_mflix
A saída será:
switched to db sample_mflix
Crie seus índices
Vamos criar os dois índices necessários para este tutorial: o índice vetorial e o índice de busca por texto completo. Lembre que estamos criando ambos na coleção embedded_movies.
Índice vetorial:
db.embedded_movies.createSearchIndex(
"hybrid-vector-search",
"vectorSearch",
{
fields: [
{ type: "vector", path: "plot_embedding_voyage_3_large", numDimensions: 2048, similarity: "dotProduct" }
]
}
)
Índice de texto completo:
db.embedded_movies.createSearchIndex(
"hybrid-full-text-search",
"search",
{ mappings: { dynamic: true } }
)
Confira no seu cluster do MongoDB Atlas se os índices estão prontos:

Consulte o banco
Agora vamos consultar os dados da coleção sample_mflix.embedded_movies por “star wars” no campo plot_embedding_voyage_3_large.
Seguindo o tutorial, como não vamos usar nenhuma API, podemos salvar todos os embeddings necessários em um arquivo separado chamado query_embeddings.js.

Agora podemos carregar os embeddings para usar na consulta. Rode:
load('/Users/<PATH NAME>/query_embeddings.js')
const queryVec = STAR_WARS_EMBEDDING
Para garantir que os embeddings foram carregados, rode:
STAR_WARS_EMBEDDING.length

Uma saída 2048 está correta.
Pipeline de agregação
Agora vamos criar um pipeline de agregação para a busca híbrida. Vale lembrar que há algumas limitações e motivos para estruturarmos o pipeline desta forma.
Como dissemos acima, embora exista o $rankFusion no MongoDB Atlas, ele está em Public Preview e você precisa de um cluster 8.0 ou superior. No free tier, você não pode usar o $rankFusion por enquanto. Isso complica um pouco, pois o Atlas não fará a fusão nativa dos rankings de texto e vetor. Então temos que fazer nossa própria fusão — uma mescla ponderada das duas listas ranqueadas.
As posições das stages no pipeline de agregação também são rígidas:
$searchprecisa ser a primeira stage do pipeline.$vectorSearchtem que iniciar o pipeline e não pode estar dentro de$facet.
Qual o workaround? $vectorSearch pode ser a primeira stage de um subpipeline dentro de $unionWith, já que esse subpipeline é tratado como um pipeline à parte.
Por isso, começamos com $search para os resultados de texto completo manterem sua pontuação e, em seguida, usamos $unionWith como um mini-pipeline que inicia com $vectorSearch para também preservar sua pontuação.
Depois, agrupamos por _id para evitar duplicados e aproveitar o melhor de cada um dos termos de $search e $vectorSearch.
Feito isso, calculamos a busca híbrida: pegamos o melhor de $search, o melhor de $vectorSearch, multiplicamos por um peso à nossa escolha (usei 0,35) e ordenamos a partir daí.
O peso define quanto a busca vetorial influencia em relação à busca por texto completo no resultado final.
Nossa fórmula híbrida é: hybrid_score = text_score + (vector_score x weight). Um peso maior (ex.: 0,7–1,0) faz a busca vetorial dominar, priorizando similaridade semântica.
Um peso menor (0,1–0,3), por outro lado, faz a busca por texto completo dominar, priorizando correspondências exatas de palavras-chave.
Copie e cole este pipeline de agregação no seu terminal:
const WEIGHT = 0.35;
db.embedded_movies.aggregate([
// Stage 1: full-text search
{ $search: { index: "hybrid-full-text-search", text: { query: "star wars", path: ["title","plot"] } } },
{ $set: { t: { $meta: "searchScore" } } },
{ $project: { _id: 1, title: 1, plot: 1, t: 1 } },
// Stage 2: Union with vector search
{ $unionWith: {
coll: "embedded_movies",
pipeline: [
// Vector search subpipeline
{ $vectorSearch: {
index: "hybrid-vector-search",
path: "plot_embedding_voyage_3_large",
queryVector: queryVec,
numCandidates: 200,
limit: 150
}},
{ $project: { _id: 1, title: 1, plot: 1, v: { $meta: "vectorSearchScore" } } }
]
}},
// Stage 3: Combine and rank results
{ $group: { _id: "$_id", title: { $first: "$title" }, plot: { $first: "$plot" }, t: { $max: "$t" }, v: { $max: "$v" } } },
{ $set: { h: { $add: [ { $ifNull: ["$t", 0] }, { $multiply: [ { $ifNull: ["$v", 0] }, WEIGHT ] } ] } } },
{ $sort: { h: -1 } },
{ $limit: 20 }
]).toArray()
Aqui está a nossa saída:
{
_id: ObjectId('573a139af29313caabcf124d'),
title: 'Star Wars: Episode III - Revenge of the Sith',
plot: 'As the Clone Wars near an end, the Sith Lord Darth Sidious steps out of the shadows, at which time Anakin succumbs to his emotions, becoming Darth Vader and putting his relationships with Obi-Wan and Padme at risk.',
t: 4.957413673400879,
v: 0.756283700466156,
h: 5.2221129685640335
},
{
_id: ObjectId('573a13a6f29313caabd17d08'),
title: 'Star',
plot: 'The Driver now carries an arrogant rock star who is visiting a major city (not Pittsburgh as earlier believed). Played by Madonna, this title character wants to get away from her bodyguards...',
t: 5.151784420013428,
v: null,
h: 5.151784420013428
},
{
_id: ObjectId('573a1397f29313caabce8cdb'),
title: 'Star Wars: Episode VI - Return of the Jedi',
plot: 'After rescuing Han Solo from the palace of Jabba the Hutt, the rebels attempt to destroy the second Death Star, while Luke struggles to make Vader return from the dark side of the Force.',
t: 4.726564407348633,
v: 0.7782549858093262,
h: 4.998953652381897
},
{
_id: ObjectId('573a13aef29313caabd2da15'),
title: 'Star Runner',
plot: 'Get ready for the ultimate martial arts competition, where anything goes and lives are bought and sold. Tank is the celebrated Champion Star Runner and is deemed invincible among the ...',
t: 4.702453136444092,
v: 0.696668267250061,
h: 4.9462870299816135
},
{
_id: ObjectId('573a13c0f29313caabd62f62'),
title: 'Star Wars: The Clone Wars',
plot: 'Anakin Skywalker and Ahsoka Tano must rescue the kidnapped son of Jabba the Hutt, but political intrigue complicates their mission.',
t: 4.537533760070801,
v: 0.7561323642730713,
h: 4.802180087566375
},
{
_id: ObjectId('573a1397f29313caabce6f53'),
title: 'Message from Space',
plot: 'In this Star Wars take-off, the peaceful planet of Jillucia has been nearly wiped out by the Gavanas, whose leader takes orders from his mother (played a comic actor in drag) rather than ...',
t: 4.401333808898926,
v: 0.7913960218429565,
h: 4.678322416543961
},
{
_id: ObjectId('573a139df29313caabcfa90b'),
title: 'Message from Space',
plot: 'In this Star Wars take-off, the peaceful planet of Jillucia has been nearly wiped out by the Gavanas, whose leader takes orders from his mother (played a comic actor in drag) rather than ...',
t: 4.401333808898926,
v: 0.7913960218429565,
h: 4.678322416543961
},
{
_id: ObjectId('573a1398f29313caabce9851'),
title: 'Gymkata',
plot: 'Johnathan Cabot is a champion gymnast. In the tiny, yet savage, country of Parmistan, there is a perfect spot for a "star wars" site. For the US to get this site, they must compete in the ...',
t: 4.284864902496338,
v: 0.7193170189857483,
h: 4.53662585914135
},
{
_id: ObjectId('573a1397f29313caabce68f6'),
title: 'Star Wars: Episode IV - A New Hope',
plot: "Luke Skywalker joins forces with a Jedi Knight, a cocky pilot, a wookiee and two droids to save the universe from the Empire's world-destroying battle-station, while also attempting to rescue Princess Leia from the evil Darth Vader.",
t: 2.9629626274108887,
v: 0.7987099885940552,
h: 3.242511123418808
},
{
_id: ObjectId('573a139af29313caabcf0f5f'),
title: 'Star Wars: Episode I - The Phantom Menace',
plot: 'Two Jedi Knights escape a hostile blockade to find allies and come across a young boy who may bring balance to the Force, but the long dormant Sith resurface to reclaim their old glory.',
t: 2.9629626274108887,
v: 0.7771433591842651,
h: 3.2349628031253816
},
{
_id: ObjectId('573a1397f29313caabce77d9'),
title: 'Star Wars: Episode V - The Empire Strikes Back',
plot: 'After the rebels have been brutally overpowered by the Empire on their newly established base, Luke Skywalker takes advanced Jedi training with Master Yoda, while his friends are pursued by Darth Vader as part of his plan to capture Luke.',
t: 2.7174296379089355,
v: 0.7810181975364685,
h: 2.9907860070466996
},
{
_id: ObjectId('573a139af29313caabcf1258'),
title: 'Star Wars: Episode II - Attack of the Clones',
plot: 'Ten years after initially meeting, Anakin Skywalker shares a forbidden romance with Padmè, while Obi-Wan investigates an assassination attempt on the Senator and discovers a secret clone army crafted for the Jedi.',
t: 2.7174296379089355,
v: 0.7400004267692566,
h: 2.9764297872781755
},
{
_id: ObjectId('573a1394f29313caabcdf65b'),
title: 'Ugetsu',
plot: 'A fantastic tale of war, love, family and ambition set in the midst of the Japanese Civil Wars of the sixteenth century.',
t: 2.858368396759033,
v: null,
h: 2.858368396759033
},
{
_id: ObjectId('573a13a4f29313caabd1137f'),
title: 'S1m0ne',
plot: "A producer's film is endangered when his star walks off, so he decides to digitally create an actress to substitute for the star, becoming an overnight sensation that everyone thinks is a real person.",
t: 2.842216730117798,
v: null,
h: 2.842216730117798
},
{
_id: ObjectId('573a13b8f29313caabd4c3c3'),
title: 'Star Trek',
plot: "The brash James T. Kirk tries to live up to his father's legacy with Mr. Spock keeping him in check as a vengeful, time-traveling Romulan creates black holes to destroy the Federation one planet at a time.",
t: 2.577816963195801,
v: 0.7295459508895874,
h: 2.8331580460071564
},
{
_id: ObjectId('573a13b5f29313caabd42e99'),
title: 'Sars Wars',
plot: "The fourth generation of the virus SARS is found in Africa! It's more dangerous and causes the patients to transform into bloodthirsty zombies. The virus quickly lands to Thailand, Dr. ...",
t: 2.826810598373413,
v: null,
h: 2.826810598373413
},
{
_id: ObjectId('573a13b5f29313caabd42e1b'),
title: 'Sars Wars',
plot: "The fourth generation of the virus SARS is found in Africa! It's more dangerous and causes the patients to transform into bloodthirsty zombies. The virus quickly lands to Thailand, Dr. ...",
t: 2.826810598373413,
v: null,
h: 2.826810598373413
}
Como podemos ver, temos alguns resultados idênticos à nossa consulta, “star wars”, e outros resultados que claramente não têm a ver com o significado.
Conclusão
Parabéns! Você concluiu com sucesso a busca híbrida no MongoDB. Embora o $rankFusion simplifique esse processo, este método mostra um workaround enquanto o operador ainda está em Public Preview. Com este tutorial, incorporamos com sucesso a busca por texto completo e a busca vetorial em um único pipeline para recuperar os resultados mais otimizados para nossa consulta. Para mais informações sobre busca híbrida no MongoDB, consulte a documentação do MongoDB. Se você ainda está se familiarizando com o MongoDB, recomendo o curso Introdução ao MongoDB em Python.
Perguntas frequentes sobre busca híbrida no MongoDB
Preciso do MongoDB 8.1 e do $rankFusion para fazer busca híbrida?
Não. Na versão 8.0, você pode fundir as pontuações manualmente. Faça isso rodando $search e $vectorSearch em dois pipelines e combinando-os com $unionWith + $group e uma fórmula ponderada. Na versão 8.1+, o $rankFusion fará isso para você.
O que é busca híbrida?
É a combinação da busca por texto completo (palavras exatas) e busca vetorial (significado) para obter os resultados mais otimizados possíveis para uma determinada consulta.
Quando devo usar a busca híbrida?
Use a busca híbrida quando seu texto for variado (sinônimos, paráfrases, erros de digitação), mas você ainda quiser termos exatos.
Como escolho o peso que quero usar?
A melhor prática é começar em torno de 0,2–0,5. Valores menores dão mais influência à busca por texto completo, e valores maiores dão mais influência à semântica. É importante ajustar o peso após testar e visualizar os resultados obtidos.
A busca híbrida funciona com dados de imagem e áudio?
Sim! Desde que seus dados possam ser transformados em embeddings vetoriais e você possa combiná-los com restrições de palavras específicas, você pode realizar busca híbrida em um dataset.



