Accéder au contenu principal

Comment implémenter la recherche sémantique dans MongoDB

Apprenez Ă  implĂ©menter la recherche sĂ©mantique dans MongoDB avec Python. GĂ©nĂ©rez des embeddings vectoriels, crĂ©ez un index Vector Search et exĂ©cutez des requĂȘtes $vectorSearch.
· 9 min lire

Explorez l'IA

Voici des situations oĂč la recherche textuelle classique montre ses limites. ConcrĂštement, cela donne :

  • Un portail d'assistance ne renvoie aucun rĂ©sultat lorsqu'un utilisateur saisit « ne s'allume pas » au lieu de l'expression exacte utilisĂ©e dans l'article d'aide.
  • Une plateforme e-commerce ne trouve rien quand un client cherche « quelque chose de chaud pour l'hiver » parce qu'aucune fiche produit ne contient exactement ces mots.
  • Une base de connaissances passe Ă  cĂŽtĂ© du bon document car l'utilisateur a formulĂ© sa question diffĂ©remment du libellĂ© du document.

La recherche sĂ©mantique rĂ©sout ce problĂšme. Elle comprend le sens et l'intention derriĂšre une requĂȘte plutĂŽt que de faire correspondre des mots Ă  l'identique.

Ce tutoriel vous montre comment implémenter une recherche sémantique dans MongoDB avec Python et le modÚle d'embedding gratuit nomic-embed-text-v1.

Remarque : nomic-embed-text-v1 pÚse environ 0,27 Go une fois chargé en mémoire. Assurez-vous que votre machine dispose d'au moins 1 Go de RAM disponible avant d'exécuter un script de ce tutoriel. Lors de la premiÚre exécution, le modÚle est téléchargé depuis Hugging Face : prévoyez un délai supplémentaire selon votre connexion. Les exécutions suivantes chargent le modÚle depuis le cache local. L'inférence sur CPU est nettement plus lente que sur GPU. Sur une machine sans GPU dédié, le modÚle s'exécute sur CPU et utilise la RAM plutÎt que la VRAM, ce qui peut rallonger la génération des embeddings.

Dans ce tutoriel, vous apprendrez :

  • Ce que sont les embeddings vectoriels et comment ils reprĂ©sentent le sens
  • Comment gĂ©nĂ©rer et stocker des embeddings dans MongoDB
  • Comment crĂ©er un index MongoDB Vector Search
  • Comment convertir une requĂȘte utilisateur en vecteur
  • Comment exĂ©cuter un pipeline d'agrĂ©gation $vectorSearch et interprĂ©ter les rĂ©sultats

Vous trouverez tous les extraits de code de ce tutoriel dans le référentiel GitHub.

Recherche textuelle vs recherche sémantique

La recherche textuelle retrouve les documents qui contiennent exactement les mots de votre requĂȘte. La recherche sĂ©mantique retourne des documents qui portent le mĂȘme sens que votre requĂȘte, mĂȘme s'ils utilisent des mots complĂštement diffĂ©rents.

Le tableau suivant illustre ce que chaque approche retourne pour la requĂȘte « heart problems » :

Texte du documentRecherche textuelleRecherche sémantiquePourquoi la recherche textuelle le retourne
"...heart problems in adults..."OuiOuiContient exactement les mots « heart problems »
"...cardiac conditions and symptoms..."NonOuiNe contient pas exactement les mots « heart problems »
"...risk factors for heart disease..."NonOuiNe contient pas exactement les mots « heart problems »
"...chest pain and shortness of breath..."NonOuiNe contient pas exactement les mots « heart problems »

Notions clés de la recherche sémantique

Trois concepts sont centraux pour la recherche sémantique dans MongoDB. Ils vous aideront à comprendre pourquoi chaque étape de ce tutoriel fonctionne comme elle le fait.

Embeddings vectoriels

Un modÚle d'embedding convertit un texte en une liste de nombres de longueur fixe appelée vecteur. Chaque nombre du vecteur représente une dimension de sens.

Deux textes qui ont des sens proches produisent des vecteurs numĂ©riquement proches. « cardiac conditions » et « heart problems » se retrouvent voisins dans l'espace vectoriel, mĂȘme s'ils ne partagent aucun mot. Cette proximitĂ© rend la recherche sĂ©mantique possible.

Ce tutoriel utilise le modÚle d'embedding nomic-embed-text-v1, gratuit, open source et entiÚrement exécutable en local. Le modÚle est automatiquement téléchargé depuis Hugging Face lors de la premiÚre exécution, puis mis en cache localement pour les suivantes. Le modÚle produit 768 nombres par entrée.

Index de recherche vectorielle

Un index de recherche vectorielle indique Ă  MongoDB quel champ contient les embeddings, combien de dimensions attendre et quelle fonction de similaritĂ© utiliser pour la comparaison. Vous devez crĂ©er cet index avant d'exĂ©cuter des requĂȘtes $vectorSearch.

L'étape d'agrégation $vectorSearch

$vectorSearch est l'Ă©tape d'agrĂ©gation qui exĂ©cute la recherche. Elle accepte un vecteur de requĂȘte, parcourt le champ indexĂ© et renvoie les documents classĂ©s par similaritĂ© sĂ©mantique. Vous pouvez l'enchaĂźner avec $project et d'autres Ă©tapes comme dans n'importe quel pipeline d'agrĂ©gation.

Allons-y.

Prérequis

Avant de commencer, assurez-vous d'avoir :

  • Python 3.8 ou version ultĂ©rieure installĂ©
  • Un compte MongoDB Atlas avec un cluster M0 (offre Free tier) configurĂ©
  • pymongo 4.7 ou ultĂ©rieur, et les packages Python sentence-transformers et einops installĂ©s
  • Des notions de base en Python et sur les collections MongoDB
  • Votre chaĂźne de connexion Atlas, disponible dans l'interface Atlas sous **Database > Connect > Drivers**
  • Votre adresse IP autorisĂ©e dans Atlas avant d'exĂ©cuter des scripts. Allez dans **Security > Network Access** dans l'interface Atlas et ajoutez votre adresse IP actuelle.

Configurer votre projet

Créez un dossier pour votre projet et placez-vous dedans via le terminal :

mkdir mongodb-semantic-search
cd mongodb-semantic-search

Créez et activez un environnement virtuel pour isoler les dépendances du projet :

python -m venv venv
source venv/bin/activate

Sous Windows, activez l'environnement virtuel avec :

venv\Scripts\activate

Installez maintenant les packages requis :

pip install pymongo sentence-transformers einops

Vous allez créer un fichier Python pour chaque étape de ce tutoriel. Tous les fichiers iront dans le dossier mongodb-semantic-search.

Créer le fichier d'utilitaires d'embedding

Créez un fichier nommé embedding_utils.py. Ce fichier contient les deux fonctions d'embedding utilisées dans ce tutoriel :

from sentence_transformers import SentenceTransformer

# Load the free, open-source embedding model.
# The model downloads from Hugging Face on first run and saves locally.
# trust_remote_code=True is required by this model:
model = SentenceTransformer("nomic-ai/nomic-embed-text-v1", trust_remote_code=True)


def get_embedding(text, precision="float32"):
    # Use this function when embedding text you plan to store in MongoDB:
    return model.encode(text, precision=precision).tolist()


def get_query_embedding(text, precision="float32"):
    # Use this function when embedding a user's search query:
    return model.encode(text, precision=precision).tolist()

Avertissement : trust_remote_code=True autorise l'exécution sur votre machine de code Python spécifique au modÚle et téléchargé depuis Hugging Face. Si le code source du modÚle sur Hugging Face est compromis ou contient des modifications malveillantes, ce code s'exécutera automatiquement dans votre environnement. Faites preuve de prudence avant d'utiliser ce paramÚtre en production.

Générer et stocker les embeddings vectoriels

Créez un fichier nommé generate_embeddings.py. Ce fichier importe la fonction d'embedding depuis embedding_utils.py, génÚre un vecteur pour le champ texte de chaque document et stocke le document complet, y compris l'embedding, dans MongoDB :

from embedding_utils import get_embedding
from pymongo import MongoClient

# Replace the placeholder with your Atlas connection string:
mongodb_client = MongoClient(
    "mongodb+srv://<USERNAME>:<PASSWORD>@<HOST>/",
    appname="devrel-tutorial-python-semantic-search"
)

collection = mongodb_client["sample_db"]["documents"]

# Sample data:
sample_documents = [
    {
        "title": "MongoDB Atlas",
        "text": "MongoDB Atlas is a fully managed cloud database."
    },
    {
        "title": "Vector Search",
        "text": "Vector search finds results based on semantic meaning."
    },
    {
        "title": "Nomic AI",
        "text": "nomic-embed-text-v1 is a free, open-source embedding model."
    },
]

docs_to_insert = []

for doc in sample_documents:
    embedding = get_embedding(doc["text"])
    docs_to_insert.append({
        "title": doc["title"],
        "text": doc["text"],
        # The vector lives alongside your original data in the same document:
        "embedding": embedding
    })

# Drop the collection before each run to avoid inserting duplicate documents:
collection.drop()

result = collection.insert_many(docs_to_insert)
print(f"Inserted {len(result.inserted_ids)} documents with embeddings.")

mongodb_client.close()

Dans le code ci-dessus, collection.drop() supprime tous les documents et index de la collection avant chaque exĂ©cution. Relancer generate_embeddings.py sans cette ligne insĂšre des doublons, ce qui amĂšne $vectorSearch Ă  retourner plusieurs fois le mĂȘme document. La suppression de la collection efface aussi l'index de recherche vectorielle : vous devrez donc relancer create_vector_index.py Ă  chaque fois que vous relancez generate_embeddings.py.

Exécutez le script :

python generate_embeddings.py

Vous verrez la sortie suivante dans votre terminal :

<All keys matched successfully>
Inserted 3 documents with embeddings.

Chaque document dans MongoDB contient dĂ©sormais Ă  la fois le texte d'origine et son vecteur Ă  768 dimensions dans le champ embedding. Le vecteur vit aux cĂŽtĂ©s de vos donnĂ©es, dans le mĂȘme document : aucun stockage sĂ©parĂ© ni recherche additionnelle n'est nĂ©cessaire au moment de la requĂȘte.

Créer un index MongoDB Vector Search

CrĂ©ez un fichier nommĂ© create_vector_index.py. Ce fichier dĂ©finit et crĂ©e un index de recherche vectorielle sur le champ embedding afin que MongoDB puisse exĂ©cuter des requĂȘtes $vectorSearch sur votre collection.

La définition de l'index requiert trois champs :

  • path : le champ qui contient les embeddings (embedding dans ce tutoriel)
  • numDimensions : doit correspondre Ă  la taille de sortie du modĂšle (768 pour nomic-embed-text-v1)
  • similarity : la fonction de comparaison (cosine convient Ă  ce modĂšle)

La valeur de numDimensions doit correspondre exactement à celle de votre modÚle d'embedding. En cas d'écart, la création de l'index échoue :

from pymongo.mongo_client import MongoClient
from pymongo.operations import SearchIndexModel
import time

# Replace the placeholder with your Atlas connection string:
mongodb_client = MongoClient(
    "mongodb+srv://<USERNAME>:<PASSWORD>@<HOST>/",
    appname="devrel-tutorial-python-semantic-search"
)

# Point to the same database and collection you used in the previous step:
database = mongodb_client["sample_db"]
collection = database["documents"]

# Define the vector search index.
# The three required fields tell MongoDB what to index and how to compare vectors:
search_index_model = SearchIndexModel(
    definition={
        "fields": [
            {
                "type": "vector",
                "path": "embedding",      # The field name from generate_embeddings.py
                "numDimensions": 768,     # nomic-embed-text-v1 always outputs 768 dimensions
                "similarity": "cosine"    # Recommended similarity function for this model
            }
        ]
    },
    name="vector_index",
    type="vectorSearch"
)

result = collection.create_search_index(model=search_index_model)
print("New search index named " + result + " is building.")

# Poll every five seconds until the index is ready to accept queries:
print("Polling to check if the index is ready. This may take up to a minute.")

predicate = lambda index: index.get("queryable") is True

while True:
    indices = list(collection.list_search_indexes(result))
    if len(indices) and predicate(indices[0]):
        break
    time.sleep(5)

print(result + " is ready for querying.")

mongodb_client.close()

Exécutez le script :

python create_vector_index.py

Vous verrez la sortie suivante dans votre terminal :

New search index named vector_index is building.
Polling to check if the index is ready. This may take up to a minute.
vector_index is ready for querying.

La construction de l'index peut prendre jusqu'Ă  une minute. La boucle d'interrogation vĂ©rifie toutes les 5 secondes et ne s'arrĂȘte que lorsque MongoDB confirme que l'index est interrogeable. N'avancez pas tant que vous n'avez pas vu « vector_index is ready for querying. »

Convertir votre requĂȘte de recherche en vecteur

Créez un fichier nommé generate_query_vector.py. Ce fichier importe la fonction get_query_embedding() depuis embedding_utils.py. Exécutez generate_query_vector.py pour vérifier que le modÚle d'embedding se charge correctement et produit un vecteur à 768 dimensions :

  • Il dĂ©finit la fonction get_query_embedding() qui sera importĂ©e Ă  l'Ă©tape suivante.
  • Vous pouvez l'exĂ©cuter seul pour vĂ©rifier que le modĂšle fonctionne correctement.

Vous devez utiliser le mĂȘme modĂšle ici que lors de la premiĂšre Ă©tape. Un modĂšle diffĂ©rent produit des vecteurs dans un espace numĂ©rique diffĂ©rent, rendant la comparaison inutile :

from embedding_utils import get_query_embedding

user_query = "I need an automated, scalable system for serious information storage"
query_vector = get_query_embedding(user_query)
print(f"Query: '{user_query}'")
print(f"Vector dimensions: {len(query_vector)}")
print(f"First 5 values: {query_vector[:5]}")

Exécutez le script :

python generate_query_vector.py

Vous obtiendrez une sortie semblable Ă  celle-ci :

<All keys matched successfully>
Query: 'I need an automated, scalable system for serious information storage'
Vector dimensions: 768
First 5 values: [0.0025914530269801617, 0.09862980246543884, -0.023092379793524742, -0.0171672236174345, -0.05548065900802612]

Dans la sortie ci-dessus, Vector dimensions: 768 confirme que la sortie du modÚle correspond à vos embeddings stockés.

ExĂ©cuter une requĂȘte $vectorSearch

Créez un fichier nommé run_vector_search.py. Ce fichier importe la fonction get_query_embedding() depuis embedding_utils.py et convertit le terme recherché par l'utilisateur en vecteur. run_vector_search.py exécute un pipeline d'agrégation $vectorSearch et affiche les résultats classés par similarité sémantique :

from embedding_utils import get_query_embedding
from pymongo import MongoClient

# Replace the placeholder with your Atlas connection string:
mongodb_client = MongoClient(
    "mongodb+srv://<USERNAME>:<PASSWORD>@<HOST>/",
    appname="devrel-tutorial-python-semantic-search"
)

collection = mongodb_client["sample_db"]["documents"]

# Generate a query vector from the user's search input:
user_query = "I need an automated, scalable system for serious information storage"
query_vector = get_query_embedding(user_query)

# Define the $vectorSearch aggregation pipeline:
pipeline = [
    {
        "$vectorSearch": {
            "index": "vector_index",      # The index created in create_vector_index.py
            "path": "embedding",          # The field that holds your stored vectors
            "queryVector": query_vector,  # The vector generated from the user's query
            "numCandidates": 150,         # How many neighbors MongoDB considers
            "limit": 3                    # How many results to return
        }
    },
    {
        "$project": {
            "_id": 0,
            "title": 1,
            "text": 1,
            "score": {
                "$meta": "vectorSearchScore"  # Relevance score for each result
            }
        }
    }
]

results = collection.aggregate(pipeline)

print(f"\nTop results for query: '{user_query}'\n")

for doc in results:
    print(f"Title: {doc['title']}")
    print(f"Text:  {doc['text']}")
    print(f"Score: {doc['score']:.4f}")
    print()

mongodb_client.close()

Dans le code ci-dessus, deux paramĂštres pilotent le comportement de la recherche :

  • numCandidates dĂ©finit la taille du premier ensemble de candidats que MongoDB examine avant de restreindre les rĂ©sultats finaux. Une valeur plus Ă©levĂ©e amĂ©liore le rappel, au prix d'un temps de requĂȘte lĂ©gĂšrement supĂ©rieur.
  • limit dĂ©finit le nombre de rĂ©sultats renvoyĂ©s. Dans ce tutoriel, limit est fixĂ© Ă  3 et numCandidates Ă  150. Un bon point de dĂ©part consiste Ă  rĂ©gler numCandidates Ă  10 Ă  15 fois votre limit.

Exécutez maintenant le script :

python run_vector_search.py

Vous obtiendrez une sortie similaire Ă  celle-ci :

<All keys matched successfully>

Top results for query: 'I need an automated, scalable system for serious information storage.'

Title: MongoDB Atlas
Text:  MongoDB Atlas is a fully managed cloud database.
Score: 0.7211

Title: Nomic AI
Text:  nomic-embed-text-v1 is a free, open-source embedding model.
Score: 0.6818

Title: Vector Search
Text:  Vector search finds results based on semantic meaning.
Score: 0.6642

« MongoDB Atlas » obtient le meilleur score, mĂȘme si la requĂȘte « I need an automated, scalable system for serious information storage » ne partage aucun mot avec « MongoDB Atlas is a fully managed cloud database. » Le rĂ©sultat est retournĂ© car leurs vecteurs sont proches en termes de sens : « ...un systĂšme automatisĂ© et scalable de stockage d'informations » correspond sĂ©mantiquement Ă  « ...une base de donnĂ©es cloud entiĂšrement managĂ©e ». Une recherche textuelle avec la mĂȘme requĂȘte renverrait zĂ©ro rĂ©sultat, puisque aucun mot exact n'apparaĂźt dans les documents. C'est prĂ©cisĂ©ment le fonctionnement attendu de la recherche sĂ©mantique.

Points clés à retenir

  • MongoDB Vector Search fonctionne sur un cluster M0 Free tier. Aucun plan payant n'est requis.
  • Vous devez utiliser le mĂȘme modĂšle d'embedding pour gĂ©nĂ©rer les embeddings stockĂ©s et les vecteurs de requĂȘte. MĂ©langer les modĂšles produit des rĂ©sultats dĂ©nuĂ©s de sens.
  • La valeur numDimensions dans la dĂ©finition de votre index doit correspondre exactement Ă  la taille de sortie de votre modĂšle d'embedding. nomic-embed-text-v1 produit toujours 768 dimensions.
  • input_type="document" optimise les embeddings pour le stockage. input_type="query" les optimise pour la recherche. Utilisez le bon type Ă  chaque Ă©tape.
  • numCandidates contrĂŽle l'ampleur du filet de recherche que MongoDB jette avant de restreindre aux rĂ©sultats finaux dĂ©finis par limit. Une valeur plus Ă©levĂ©e amĂ©liore le rappel au dĂ©triment du temps de requĂȘte.
  • vectorSearchScore classe les rĂ©sultats par similaritĂ© sĂ©mantique. Les rĂ©sultats n'ont pas besoin de contenir les mots exacts de la requĂȘte. Les plages de score varient selon le modĂšle et le jeu de donnĂ©es, mais les seuils suivants constituent un bon point de dĂ©part pour nomic-embed-text-v1 avec la similaritĂ© cosinus :
    • 0,9 et plus : sens quasi identique. Le document et la requĂȘte sont presque Ă©quivalents sĂ©mantiquement.
    • 0,7 Ă  0,9 : forte pertinence. Le document correspond clairement Ă  l'intention de la requĂȘte.
    • 0,5 Ă  0,7 : pertinence modĂ©rĂ©e. Le document est liĂ© au sujet mais avec un cadrage ou un contexte diffĂ©rent.
    • En dessous de 0,5 : faible pertinence. Le lien est tĂ©nu et le rĂ©sultat peut ĂȘtre peu utile.

Vous trouverez tous les extraits de code de ce tutoriel dans le référentiel GitHub.

Pour aller plus loin

FAQ

Ai-je besoin d'un abonnement Atlas payant pour implémenter la recherche sémantique ?

Non. Les quatre étapes de ce tutoriel s'exécutent sur un cluster M0 Free tier, qui est gratuit.

Que se passe-t-il si j'utilise un autre modĂšle d'embedding pour ma requĂȘte que pour mes documents stockĂ©s ?

Vos rĂ©sultats seront dĂ©nuĂ©s de sens. Les vecteurs ne seront pas comparables, car diffĂ©rents modĂšles projettent le texte dans des espaces numĂ©riques distincts. Utilisez toujours le mĂȘme modĂšle pour l'indexation et la requĂȘte.

Quelle est la différence entre `numCandidates` et `limit` ?

numCandidates correspond au nombre de vecteurs que MongoDB examine pendant la recherche. limit est le nombre de meilleurs rĂ©sultats retournĂ©s. Une valeur plus Ă©levĂ©e de numCandidates amĂ©liore la qualitĂ© des rĂ©sultats, avec des requĂȘtes lĂ©gĂšrement plus lentes. Un bon point de dĂ©part est de rĂ©gler numCandidates Ă  10 Ă  15 fois votre limit.

Puis-je utiliser la recherche sémantique et la recherche textuelle ensemble ?

Oui. MongoDB prend en charge la recherche hybride, qui combine $vectorSearch et $search dans un seul pipeline.

La recherche sémantique fonctionne-t-elle pour des langues autres que l'anglais ?

Cela dépend de votre modÚle d'embedding. nomic-embed-text-v1 est entraßné principalement sur des textes en anglais. Pour des cas d'usage multilingues, choisissez un modÚle d'embedding multilingue entraßné sur les langues présentes dans vos données.

Damilola Oladele

Auteur

Damilola Oladele

Sujets

Contenus associés

Tutoriel

Données JSON Python : Un guide illustré d'exemples