Ga naar hoofdinhoud

Zo implementeer je semantisch zoeken in MongoDB

Leer hoe je semantisch zoeken in MongoDB implementeert met Python. Genereer vector-embeddings, maak een Vector Search-index en voer $vectorSearch-queries uit.
Bijgewerkt 31 jul 2026  · 9 min lezen

Verkennen met AI

Openen in ChatGPTOpenen in ClaudeOpenen in Perplexity

Dit zijn de momenten waarop gewone tekstzoekopdrachten tekortschieten. In de praktijk ziet dat er zo uit:

  • Een klantenportaal geeft geen resultaten wanneer een gebruiker "gaat niet aan" typt in plaats van de exacte zin uit het helpartikel.
  • Een e-commerceplatform vindt niets wanneer een shopper zoekt op "iets warms voor de winter" omdat geen enkele productbeschrijving precies die woorden bevat.
  • Een kennisbank mist het juiste document omdat de gebruiker de vraag anders formuleerde dan het document is geschreven.

Semantisch zoeken lost dit op. Het begrijpt de betekenis en intentie achter een query in plaats van exacte woorden te matchen.

Deze tutorial laat je zien hoe je semantisch zoeken in MongoDB implementeert met Python en het gratis nomic-embed-text-v1 embeddingmodel.

Let op: nomic-embed-text-v1 is ongeveer 0,27 GB wanneer het in het geheugen is geladen, dus zorg dat je machine minstens 1 GB vrije RAM heeft voordat je een script in deze tutorial uitvoert. Bij de eerste run wordt het model van Hugging Face gedownload; reken op extra tijd afhankelijk van je internetverbinding. Alle volgende runs laden het model uit je lokale cache. CPU-inferentie is aanzienlijk langzamer dan GPU-inferentie. Op machines zonder dedicated GPU draait het model op de CPU en gebruikt het RAM in plaats van VRAM, waardoor het genereren van embeddings langer kan duren.

In deze tutorial leer je het volgende:

  • Wat vector-embeddings zijn en hoe ze betekenis representeren
  • Hoe je embeddings genereert en opslaat in MongoDB
  • Hoe je een MongoDB Vector Search-index maakt
  • Hoe je een gebruikersquery omzet in een vector
  • Hoe je een $vectorSearch aggregatiepijplijn uitvoert en de resultaten interpreteert

Je vindt alle codevoorbeelden voor deze tutorial in de GitHub-repository.

Tekstzoekopdracht vs. semantisch zoeken

Tekstzoekopdrachten matchen documenten die de exacte woorden in je query bevatten. Semantisch zoeken matcht documenten die dezelfde betekenis hebben als je query, zelfs als ze totaal andere woorden gebruiken.

De volgende tabel laat zien wat elke benadering oplevert voor de zoekopdracht "hartproblemen":

Documenttekst Tekstzoekopdracht Semantisch zoeken Waarom tekstzoekopdracht dit teruggeeft
"...hartproblemen bij volwassenen..." Ja Ja Bevat de exacte woorden "hartproblemen"
"...cardiale aandoeningen en symptomen..." Nee Ja Bevat niet de exacte woorden "hartproblemen"
"...risicofactoren voor hartziekte..." Nee Ja Bevat niet de exacte woorden "hartproblemen"
"...pijn op de borst en kortademigheid..." Nee Ja Bevat niet de exacte woorden "hartproblemen"

Concepten van semantisch zoeken

Drie concepten staan centraal bij semantisch zoeken in MongoDB. Ze helpen je te begrijpen waarom elke stap in deze tutorial werkt zoals hij werkt.

Vector-embeddings

Een embeddingmodel zet tekst om in een lijst met getallen van vaste lengte, een vector. Elk getal in de vector vertegenwoordigt een dimensie van betekenis.

Twee teksten met vergelijkbare betekenis leveren vectors op die numeriek dicht bij elkaar liggen. "cardiale aandoeningen" en "hartproblemen" liggen dicht bij elkaar in de vectorruimte, ook al delen ze geen woorden. Die nabijheid maakt semantisch zoeken mogelijk.

Deze tutorial gebruikt het embeddingmodel nomic-embed-text-v1, dat gratis en open-source is en volledig lokaal op je machine draait. Het model wordt automatisch van Hugging Face gedownload bij de eerste run en lokaal opgeslagen voor alle volgende runs. Het model produceert 768 getallen per invoer.

Vectorzoekindexen

Een vectorzoekindex vertelt MongoDB welk veld de embeddings bevat, hoeveel dimensies verwacht worden en welke similariteitsfunctie voor de vergelijking wordt gebruikt. Je moet deze index maken voordat je $vectorSearch-queries kunt uitvoeren.

De $vectorSearch-aggregatiestap

$vectorSearch is de aggregatiestap die de zoekopdracht uitvoert. Hij accepteert een queryvector, doorzoekt het geïndexeerde veld en retourneert documenten gerangschikt op semantische overeenkomst. Je koppelt hem met $project en andere stappen zoals elke andere aggregatiepijplijn.

Laten we beginnen.

Vereisten

Zorg voordat je start dat je het volgende klaar hebt:

  • Python 3.8 of later geïnstalleerd
  • Een MongoDB Atlas-account met een M0 (gratis tier) cluster ingesteld
  • pymongo 4.7 of later, sentence-transformers en einops Python-pakketten geïnstalleerd
  • Basiskennis van Python en MongoDB-collecties
  • Je Atlas-verbindingstekenreeks, beschikbaar in de Atlas-UI onder **Database > Connect > Drivers**
  • Je IP-adres is toegestaan in Atlas voordat je scripts uitvoert. Ga naar **Security > Network Access** in de Atlas-UI en voeg je huidige IP-adres toe.

Je project instellen

Maak een map voor je project en navigeer ernaartoe in je terminal:

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

Maak en activeer een virtuele omgeving om de afhankelijkheden van je project geïsoleerd te houden:

python -m venv venv
source venv/bin/activate

Op Windows activeer je de virtuele omgeving met:

venv\Scripts\activate

Installeer nu de vereiste pakketten:

pip install pymongo sentence-transformers einops

Je maakt één Python-bestand voor elke stap van deze tutorial. Alle bestanden komen in de map mongodb-semantic-search.

Maak het bestand met embedding-hulpfuncties

Maak een bestand met de naam embedding_utils.py. Dit bestand bevat de twee embeddingfuncties die in deze tutorial worden gebruikt:

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()

Waarschuwing: trust_remote_code=True staat toe dat modelspecifieke Python-code die van Hugging Face wordt gedownload op je machine wordt uitgevoerd. Als de broncode van het model op Hugging Face gecompromitteerd is of met kwaadwillende wijzigingen wordt bijgewerkt, draait die code automatisch in je omgeving. Wees voorzichtig voordat je deze parameter in een productieomgeving gebruikt.

Genereer en sla vector-embeddings op

Maak een bestand met de naam generate_embeddings.py. Dit bestand importeert de embeddingfunctie uit embedding_utils.py, genereert een vector voor het tekstveld van elk document en slaat het volledige document, inclusief de embedding, op in 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()

In de bovenstaande code verwijdert collection.drop() voor elke run alle documenten en indexen in de collectie. Als je generate_embeddings.py opnieuw uitvoert zonder deze regel, worden dubbele documenten ingevoegd, waardoor $vectorSearch hetzelfde document meerdere keren retourneert. Het droppen van de collectie verwijdert ook de vectorzoekindex, dus je moet create_vector_index.py opnieuw uitvoeren wanneer je generate_embeddings.py opnieuw uitvoert.

Voer het script uit:

python generate_embeddings.py

Je krijgt de volgende output in je terminal:

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

Elk document in MongoDB bevat nu zowel de originele tekst als de 768-dimensionale vector in het veld embedding. De vector staat naast je data in hetzelfde document, dus er is geen aparte opslag of lookup nodig tijdens het queryen.

Maak een MongoDB Vector Search-index

Maak een bestand met de naam create_vector_index.py. Dit bestand definieert en maakt een vectorzoekindex op het veld embedding zodat MongoDB $vectorSearch-queries kan uitvoeren op je collectie.

De indexdefinitie vereist drie velden:

  • path: het veld dat de embeddings bevat (embedding in deze tutorial)
  • numDimensions: moet overeenkomen met de outputgrootte van het model (768 voor nomic-embed-text-v1)
  • similarity: de vergelijkingsfunctie (cosine is correct voor dit model)

De waarde numDimensions moet exact overeenkomen met je embeddingmodel. Een mismatch zorgt ervoor dat het maken van de index mislukt:

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()

Voer het script uit:

python create_vector_index.py

Je krijgt de volgende output in je 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.

Het bouwen van de index duurt tot een minuut. De polling-lus controleert elke 5 seconden en stopt pas wanneer MongoDB bevestigt dat de index queryable is. Ga niet door naar de volgende stap totdat je "vector_index is ready for querying." ziet.

Zet je zoekquery om in een vector

Maak een bestand met de naam generate_query_vector.py. Dit bestand importeert de functie get_query_embedding() uit het bestand embedding_utils.py. Voer het bestand generate_query_vector.py uit om te verifiëren dat het embeddingmodel correct laadt en een 768-dimensionale vector produceert:

  • Het definieert de functie get_query_embedding() die in de volgende stap wordt geïmporteerd.
  • Je kunt het los draaien om te verifiëren dat het model correct werkt.

Je moet hier hetzelfde model gebruiken als in de eerste stap. Een ander model produceert vectors die een andere numerieke ruimte bezetten, waardoor vergelijken zinloos wordt:

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]}")

Voer het script uit:

python generate_query_vector.py

Je krijgt een output vergelijkbaar met dit:

<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]

In de bovenstaande output bevestigt Vector dimensions: 768 dat de modeloutput overeenkomt met je opgeslagen embeddings.

Voer een $vectorSearch-query uit

Maak een bestand met de naam run_vector_search.py. Dit bestand importeert de functie get_query_embedding() uit het bestand embedding_utils.py en zet een zoekterm van een gebruiker om in een vector. run_vector_search.py voert een $vectorSearch-aggregatiepijplijn uit en print de resultaten gerangschikt op semantische overeenkomst:

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()

In de bovenstaande code bepalen twee parameters het zoekgedrag:

  • numCandidates stelt de grootte in van de initiële zoekpool die MongoDB bekijkt voordat wordt teruggebracht tot de uiteindelijke resultaten. Een hogere waarde verbetert recall maar duurt iets langer.
  • limit bepaalt hoeveel resultaten je terugkrijgt. In deze tutorial is limit ingesteld op 3 en numCandidates op 150. Een veelvoorkomend startpunt is om numCandidates op 10-15 keer je limit te zetten.

Voer nu het script uit:

python run_vector_search.py

Je krijgt een output vergelijkbaar met dit:

<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" scoort het hoogst, ook al deelt de query "I need an automated, scalable system for serious information storage" geen woorden met "MongoDB Atlas is a fully managed cloud database." De zoekopdracht geeft dit terug omdat hun vectors qua betekenis dicht bij elkaar liggen: "...een geautomatiseerd, schaalbaar systeem voor informatieopslag" komt semantisch overeen met "...een volledig beheerde clouddatabase". Een tekstzoekopdracht voor dezelfde query zou nul resultaten opleveren, omdat geen van de exacte querywoorden in een document voorkomt. Dat is semantisch zoeken zoals bedoeld.

Belangrijkste punten

  • MongoDB Vector Search draait op een M0 Free tier-cluster. Er is geen betaald abonnement nodig.
  • Je moet hetzelfde embeddingmodel gebruiken voor zowel het genereren van opgeslagen embeddings als het genereren van queryvectors. Modellen mixen levert zinloze resultaten op.
  • De waarde numDimensions in je indexdefinitie moet exact overeenkomen met de outputgrootte van je embeddingmodel. nomic-embed-text-v1 produceert altijd 768 dimensies.
  • input_type="document" optimaliseert embeddings voor opslag. input_type="query" optimaliseert ze voor ophalen. Gebruik het juiste type in elke fase.
  • numCandidates bepaalt hoe groot het zoeknet is dat MongoDB uitwerpt voordat wordt teruggebracht tot de uiteindelijke limit-resultaten. Een hogere waarde verbetert de recall ten koste van de querytijd.
  • vectorSearchScore rangschikt resultaten op semantische overeenkomst. Resultaten hoeven geen enkele exacte querywoord te bevatten. Scoreranges variëren per model en dataset, maar de volgende grenzen zijn een bruikbaar startpunt voor nomic-embed-text-v1 met cosinesimilariteit:
    • 0,9 en hoger: Bijna identieke betekenis. Document en query zijn semantisch vrijwel gelijk.
    • 0,7 tot 0,9: Sterke relevantie. Het document sluit duidelijk aan bij de intentie van de query.
    • 0,5 tot 0,7: Gemiddelde relevantie. Het document is thematisch verwant maar gebruikt andere formulering of context.
    • Onder 0,5: Zwakke relevantie. Het verband is los en het resultaat is mogelijk niet nuttig.

Je vindt alle codevoorbeelden voor deze tutorial in de GitHub-repository.

Verder lezen

FAQ's

Heb ik een betaald Atlas-abonnement nodig om semantisch zoeken te implementeren?

Nee. Alle vier de stappen in deze tutorial draaien op een M0 Free tier-cluster, dat gratis is.

Wat gebeurt er als ik een ander embeddingmodel gebruik voor mijn query dan voor mijn opgeslagen documenten?

Je resultaten zijn zinloos. De vectors zijn niet vergelijkbaar omdat verschillende modellen tekst op verschillende numerieke ruimtes projecteren. Gebruik altijd hetzelfde model voor zowel indexeren als queryen.

Wat is het verschil tussen `numCandidates` en `limit`?

numCandidates is hoeveel vectors MongoDB tijdens de zoekopdracht bekijkt. limit is hoeveel van de beste resultaten je terugkrijgt. Een hogere numCandidates-waarde verbetert de kwaliteit van de resultaten ten koste van iets langzamere queries. Een veelgebruikt startpunt is om numCandidates op 10 tot 15 keer je limit te zetten.

Kan ik semantisch zoeken en tekstzoekopdrachten samen gebruiken?

Ja. MongoDB ondersteunt hybride zoeken, waarmee je $vectorSearch en $search in één pijplijn combineert.

Werkt semantisch zoeken ook voor andere talen dan Engels?

Dat hangt af van je embeddingmodel. nomic-embed-text-v1 is primair getraind op Engelstalige tekst. Kies voor meertalige use-cases een meertalig embeddingmodel dat is getraind op de talen in je data.


Damilola Oladele's photo
Author
Damilola Oladele
Onderwerpen