Accéder au contenu principal

Hybrid Search: Combining Vector and Keyword Queries in MongoDB

Learn about hybrid search and how to utilize it in MongoDB.
Actualisé 19 sept. 2026  · 6 min lire

Explorer avec l’IA

ChatGPTClaudePerplexity

Parfois, une simple recherche en texte intégral ou uniquement la recherche vectorielle ne suffisent pas pour interroger correctement une base de données et obtenir les résultats souhaités. La combinaison des deux est idéale lorsque l'on gère de grands volumes de données multimodales et non structurées, qui bénéficient des deux types de recherche. C’est ce qu’on appelle la recherche hybride, et elle offre aux développeurs une excellente réponse à un défi complexe. 

Qu’est-ce qu’exactement la recherche hybride? 

Pour bien comprendre la recherche hybride, commençons par définir la recherche en texte intégral. 

La recherche en texte intégral consiste à faire correspondre les termes exacts de votre requête avec ceux de vos documents. C’est la forme traditionnelle de recherche, très familière aux développeurs.

Par exemple, si vous cherchez « joli café avec terrasse », votre moteur va rechercher ces mots exacts dans la base. En bref, la recherche en texte intégral est très précise et efficace, mais elle montre ses limites si vous comptez sur des synonymes, des paraphrases, ou même en cas de faute de frappe dans la requête. 

La recherche vectorielle, à l’inverse, convertit toutes les données en nombres, ou embeddings. Plutôt que de comparer des mots exacts, elle confronte le sens de votre requête au sens des documents stockés dans la base. 

Chercher « joli café avec terrasse » pourra ainsi renvoyer « viennoiseries et café en plein air », même si les mots diffèrent. La recherche vectorielle est non seulement sémantique, mais aussi très souple; elle peut toutefois retourner des résultats trop larges par rapport à la requête spécifiée. 

Où intervient la recherche hybride? Elle combine recherche en texte intégral et recherche vectorielle. Les développeurs bénéficient ainsi à la fois de l’intelligence sémantique des vecteurs et des capacités de filtrage précises de la recherche plein texte. Le meilleur des deux mondes, particulièrement utile avec de grands jeux de données non structurées. 

Pourquoi la recherche hybride compte

La recherche hybride s’avère utile dans de nombreux cas concrets, notamment en e-commerce, en santé, et même dans le recrutement. 

En e-commerce, imaginez chercher sur votre site préféré « chaise de bureau confortable à moins de 100 $ ». La recherche vectorielle proposera des articles sémantiquement proches (par exemple des chaises ergonomiques), tandis que la recherche plein texte s’accrochera au mot « chaise » et aidera à faire respecter le critère de prix. 

Côté recrutement, si un ou une recruteuse parcourt des CV avec la requête « ingénieur avec expérience en NLP », la recherche hybride captera à la fois « natural language processing » et le mot-clé exact « engineer ».

Résultat : des réponses plus pertinentes et plus fiables qu’avec une approche unique. 

Recherche hybride dans MongoDB

Voyons comment réaliser une recherche hybride dans MongoDB Atlas. Pour suivre ce tutoriel, prévoyez les prérequis suivants : 

  1. Un compte MongoDB Atlas  
  2. Un cluster MongoDB Atlas gratuit (free-tier)
  3. mongosh installé dans votre terminal

Dans ce tutoriel, nous travaillerons sur la collection embedded movies de la base sample_mflix et suivrons ce tutoriel de recherche hybride $rankFusion, avec quelques ajustements. 

Bien que le nouvel opérateur $rankFusion soit disponible sur les clusters en version 8.1 et supérieure et simplifie grandement la recherche hybride dans MongoDB, il est encore en Public Preview et les clusters free tier sont automatiquement en 8.0. Nous suivrons donc ce tutoriel, adapterons quelques éléments, et irons jusqu'au bout.

Approvisionner votre cluster

Lors de l’approvisionnement, assurez-vous de télécharger le jeu de données sample_mflix. C’est celui que nous utiliserons tout au long du tutoriel. Nous nous concentrons sur la collection sample_mflix.embedded_movies

Vérifiez rapidement que mongosh est bien installé.

mongosh --version

Pour ce tutoriel, j'utilise la version 2.4.2. 

Connectez-vous maintenant à votre cluster MongoDB Atlas :

mongosh "mongodb+srv://<yourclusterhere>.mongodb.net/" --apiVersion 1 --username <yourusername> 

Le mot de passe vous sera demandé, et une fois connecté, l’interface ressemblera à ceci :

Nous pouvons maintenant nous connecter à la base. Exécutez :

use sample_mflix

La sortie attendue : 

switched to db sample_mflix

Créer vos index

Créons les deux index nécessaires : l’index vectoriel et l’index de recherche plein texte. Rappel : nous les créons tous deux sur la collection embedded_movies

Index vectoriel :

db.embedded_movies.createSearchIndex(
  "hybrid-vector-search",
  "vectorSearch",
  {
    fields: [
      { type: "vector", path: "plot_embedding_voyage_3_large", numDimensions: 2048, similarity: "dotProduct" }
    ]
  }
)

Index de recherche plein texte :

db.embedded_movies.createSearchIndex(
  "hybrid-full-text-search",
  "search",
  { mappings: { dynamic: true } }
)

Vérifiez dans votre cluster MongoDB Atlas que les index sont prêts :

Interroger la base

Nous allons maintenant interroger les données de sample_mflix.embedded_movies avec « star wars » sur le champ plot_embedding_voyage_3_large

En suivant le tutoriel et puisqu’aucune API n’est nécessaire, nous pouvons stocker tous les embeddings requis dans un fichier séparé nommé query_embeddings.js

Chargeons désormais ces embeddings pour la requête. Exécutez :

load('/Users/<PATH NAME>/query_embeddings.js')
const queryVec = STAR_WARS_EMBEDDING

Pour vérifier que les embeddings sont bien chargés, exécutez :

STAR_WARS_EMBEDDING.length

Une longueur de 2048 est correcte. 

Pipeline d’agrégation

Nous pouvons maintenant créer un pipeline d’agrégation pour la recherche hybride. Gardez à l’esprit quelques contraintes et la logique qui nous amènent à cette approche. 

Comme indiqué plus haut, bien que $rankFusion existe dans MongoDB Atlas, il est en Public Preview et requiert un cluster 8.0 ou supérieur. Sur un cluster free tier, vous ne pouvez pas utiliser $rankFusion pour l’instant. Cela complique un peu la tâche, car MongoDB Atlas ne fusionnera pas nativement les classements texte et vecteur. Nous devons donc effectuer notre propre fusion, c’est-à-dire un mélange pondéré des deux listes classées. 

L’ordre des étapes dans les pipelines d’agrégation est également strict :

  • $search doit être la première étape du pipeline. 
  • $vectorSearch doit être au début d’un pipeline et ne peut pas se trouver dans $facet.

Quelle astuce? $vectorSearch peut être la première étape d’un sous-pipeline à l’intérieur de $unionWith, puisque ce sous-pipeline est considéré comme un pipeline à part entière. 

De ce fait, nous devons commencer par $search pour que les résultats plein texte conservent leur score, puis utiliser $unionWith pour un second mini-pipeline qui démarre par $vectorSearch afin que ce score soit également conservé. 

Nous devons ensuite regrouper par _id pour éviter les doublons et tirer parti du meilleur de $search et de $vectorSearch

Enfin, nous calculons le score hybride : le meilleur score $search, le meilleur score $vectorSearch, multiplié par un poids de notre choix (ici 0,35), puis nous trions les résultats. 

Le poids détermine l’influence de la recherche vectorielle par rapport à la recherche plein texte dans le résultat final. 

Notre formule hybride est : hybrid_score = text_score + (vector_score x weight). Un poids élevé (par ex. 0,7–1,0) donne la priorité à la recherche vectorielle et à la similarité sémantique. 

Un poids plus faible (0,1–0,3) favorise au contraire la recherche plein texte et les correspondances exactes de mots-clés. 

Copiez-collez ce pipeline d’agrégation dans votre 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()

Voici la sortie :

  {
    _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
  }
]).toArray()

As we can see, we have some results that are identical to our query, “star wars,” and other results that are clearly off of meaning.

Conclusion 

Congratulations! You have successfully completed hybrid search in MongoDB. While $rankFusion will simplify this process, this method shows a workaround while the operator is still in Public Preview. Through this tutorial, we have successfully incorporated both full-text search and vector search into one pipeline to retrieve the most optimal results for our given query. For more information on hybrid search in MongoDB, please refer to the MongoDB documentation. If you’re still getting up to speed with MongoDB, I recommend the Introduction to MongoDB in Python course.

MongoDB Hybrid Search FAQs

Do I need MongoDB 8.1 and `$rankFusion` to do hybrid search

No. On 8.0, you can actually fuse the scores yourself. Do this by running $search and $vectorSearch in two pipelines and combine them with $unionWith + $group and a weighted formula. On 8.1+, $rankFusion will do this for you.

What is hybrid search?

It’s combining full-text search (exact words) and vector search (meaning) for the most optimal results possible from a given query.

When should I use hybrid search?

Use hybrid search when your text is varied (synonyms, paraphrases, typos) but you still want exact terms.

How do I pick the weight I want to use?

It’s best practice to begin around 0.2-0.5. The lower is more for full-text search influence, and higher is for semantic influence. It’s important to tune the weight after testing and viewing the results provided.

Can hybrid search work with image and audio data?

Yes! As long as your data can be turned into vector embeddings and you can combine them with any specific wording constraints, you can perform hybrid search on a dataset.

Sujets
MongoDB
Bases de données vectorielles

Top DataCamp Courses

Cours

Introduction à MongoDB en Python

3 h
24.3K
Apprenez à manipuler et analyser des données structurées de manière flexible avec MongoDB.
Afficher les détailsRight Arrow
Commencer Le Cours
Voir plusRight Arrow