Weiter zum Inhalt

So implementierst du Semantic Search in MongoDB

Lerne, wie du Semantic Search in MongoDB mit Python umsetzt. Erzeuge Vektorembeddings, erstelle einen Vector-Search-Index und führe $vectorSearch-Abfragen aus.
Aktualisiert 31. Juli 2026  · 9 Min. lesen

Mit KI erkunden

In ChatGPT öffnenIn Claude öffnenIn Perplexity öffnen

Genau in solchen Momenten stößt die normale Volltextsuche an ihre Grenzen. So sieht das in der Praxis aus:

  • Ein Support-Portal liefert keine Ergebnisse, wenn ein Nutzer "geht nicht an" tippt statt der exakten Formulierung aus dem Hilfeartikel.
  • Eine E‑Commerce-Plattform findet nichts bei der Suche nach "etwas Warmes für den Winter", weil keine Produktbeschreibung genau diese Wörter enthält.
  • Eine Wissensdatenbank verpasst das richtige Dokument, weil der Nutzer seine Frage anders formuliert als im Dokument.

Semantic Search löst dieses Problem. Sie versteht Bedeutung und Absicht hinter einer Anfrage, statt nur exakte Wörter zu matchen.

Dieses Tutorial zeigt dir, wie du Semantic Search in MongoDB mit Python und dem kostenlosen nomic-embed-text-v1‑Embedding-Modell umsetzt.

Hinweis: nomic-embed-text-v1 belegt im Arbeitsspeicher etwa 0,27 GB. Stelle also sicher, dass auf deinem Rechner mindestens 1 GB RAM frei ist, bevor du ein Skript aus diesem Tutorial ausführst. Beim ersten Lauf wird das Modell von Hugging Face heruntergeladen — plane je nach Internetverbindung zusätzliche Zeit ein. Alle weiteren Läufe laden das Modell aus dem lokalen Cache. Inferenz auf der CPU ist deutlich langsamer als auf der GPU. Auf Maschinen ohne dedizierte GPU läuft das Modell auf der CPU und nutzt RAM statt VRAM; das Erzeugen von Embeddings kann daher länger dauern.

In diesem Tutorial lernst du:

  • Was Vektorembeddings sind und wie sie Bedeutung abbilden
  • Wie du Embeddings in MongoDB erzeugst und speicherst
  • Wie du einen MongoDB-Vector-Search-Index erstellst
  • Wie du eine Nutzeranfrage in einen Vektor umwandelst
  • Wie du eine $vectorSearch-Aggregation ausführst und die Ergebnisse interpretierst

Den vollständigen Code zu diesem Tutorial findest du im GitHub-Repository.

Textsuche vs. Semantic Search

Die Textsuche findet Dokumente, die exakt die Wörter deiner Suchanfrage enthalten. Semantic Search findet Dokumente mit derselben Bedeutung – auch wenn sie völlig andere Wörter verwenden.

Die folgende Tabelle zeigt, was beide Ansätze für die Suchanfrage "heart problems" zurückliefern:

Dokumenttext Textsuche Semantic Search Warum Textsuche es zurückgibt
"...heart problems in adults..." Ja Ja Enthält die exakten Wörter "heart problems"
"...cardiac conditions and symptoms..." Nein Ja Enthält nicht die exakten Wörter "heart problems"
"...risk factors for heart disease..." Nein Ja Enthält nicht die exakten Wörter "heart problems"
"...chest pain and shortness of breath..." Nein Ja Enthält nicht die exakten Wörter "heart problems"

Kernkonzepte von Semantic Search

Drei Konzepte sind in MongoDB für Semantic Search zentral. Sie helfen dir zu verstehen, warum jeder Schritt in diesem Tutorial so funktioniert.

Vektorembeddings

Ein Embedding-Modell wandelt Text in eine Liste fester Länge aus Zahlen um – einen Vektor. Jede Zahl repräsentiert eine Bedeutungsdimension.

Zwei Texte mit ähnlicher Bedeutung erzeugen Vektoren, die numerisch nahe beieinander liegen. "cardiac conditions" und "heart problems" landen im Vektorraum dicht nebeneinander, obwohl sie keine Wörter teilen. Genau diese Nähe macht Semantic Search möglich.

Dieses Tutorial verwendet das Embedding-Modell nomic-embed-text-v1. Es ist kostenlos, Open Source und läuft vollständig lokal. Beim ersten Ausführen lädt sich das Modell automatisch von Hugging Face und wird für künftige Läufe lokal gespeichert. Das Modell erzeugt pro Eingabe 768 Werte.

Vector-Search-Indizes

Ein Vector-Search-Index sagt MongoDB, in welchem Feld die Embeddings liegen, wie viele Dimensionen zu erwarten sind und welche Ähnlichkeitsfunktion zum Vergleich verwendet wird. Du musst diesen Index erstellen, bevor du $vectorSearch-Abfragen ausführen kannst.

Die $vectorSearch-Aggregationsstufe

$vectorSearch ist die Aggregationsstufe, die die Suche ausführt. Sie nimmt einen Anfragevektor, durchsucht das indizierte Feld und gibt Dokumente nach semantischer Ähnlichkeit sortiert zurück. Du verkettest sie wie gewohnt mit $project und anderen Stufen in einer Aggregationspipeline.

Dann lass uns starten.

Voraussetzungen

Bevor du loslegst, stelle Folgendes bereit:

  • Python 3.8 oder höher installiert
  • Ein MongoDB Atlas Account mit einem M0-Cluster (Free Tier)
  • pymongo ab Version 4.7, sentence-transformers und einops als Python-Pakete installiert
  • Grundkenntnisse in Python und MongoDB-Collections
  • Dein Atlas-Verbindungsstring, in der Atlas-UI unter **Database > Connect > Drivers**
  • Erlaube deine IP-Adresse in Atlas, bevor du Skripte ausführst. Gehe in der Atlas-UI zu **Security > Network Access** und füge deine aktuelle IP hinzu.

Projekt einrichten

Erstelle einen Ordner für dein Projekt und wechsle im Terminal hinein:

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

Erstelle und aktiviere eine virtuelle Umgebung, um Abhängigkeiten zu isolieren:

python -m venv venv
source venv/bin/activate

Unter Windows aktivierst du die virtuelle Umgebung mit:

venv\Scripts\activate

Installiere nun die benötigten Pakete:

pip install pymongo sentence-transformers einops

Du erstellst für jeden Schritt dieses Tutorials eine eigene Python-Datei. Alle Dateien liegen im Ordner mongodb-semantic-search.

Die Embedding-Hilfsfunktionen anlegen

Erstelle eine Datei namens embedding_utils.py. Diese Datei enthält beide Embedding-Funktionen, die im Tutorial verwendet werden:

from sentence_transformers import SentenceTransformer

# Lade das kostenlose Open-Source-Embedding-Modell.
# Das Modell wird beim ersten Lauf von Hugging Face heruntergeladen und lokal gespeichert.
# trust_remote_code=True ist für dieses Modell erforderlich:
model = SentenceTransformer("nomic-ai/nomic-embed-text-v1", trust_remote_code=True)


def get_embedding(text, precision="float32"):
    # Verwende diese Funktion, wenn du Text einbettest, den du in MongoDB speichern willst:
    return model.encode(text, precision=precision).tolist()


def get_query_embedding(text, precision="float32"):
    # Verwende diese Funktion, wenn du die Suchanfrage eines Nutzers einbettest:
    return model.encode(text, precision=precision).tolist()

Warnung: trust_remote_code=True erlaubt dem Modell, modellspezifischen Python-Code von Hugging Face auf deinem Rechner auszuführen. Wird der Quellcode des Modells auf Hugging Face kompromittiert oder mit bösartigen Änderungen aktualisiert, läuft dieser Code automatisch in deiner Umgebung. Gehe vorsichtig vor, bevor du diesen Parameter in produktiven Umgebungen einsetzt.

Vektorembeddings erzeugen und speichern

Erstelle eine Datei namens generate_embeddings.py. Diese Datei importiert die Embedding-Funktion aus embedding_utils.py, erzeugt für jedes Dokument einen Vektor aus dem Textfeld und speichert das vollständige Dokument inklusive Embedding in MongoDB:

from embedding_utils import get_embedding
from pymongo import MongoClient

# Ersetze den Platzhalter durch deinen Atlas-Verbindungsstring:
mongodb_client = MongoClient(
    "mongodb+srv://<USERNAME>:<PASSWORD>@<HOST>/",
    appname="devrel-tutorial-python-semantic-search"
)

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

# Beispieldaten:
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"],
        # Der Vektor liegt zusammen mit den Originaldaten im selben Dokument:
        "embedding": embedding
    })

# Lösche die Collection vor jedem Lauf, um doppelte Einträge zu vermeiden:
collection.drop()

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

mongodb_client.close()

Im obigen Code löscht collection.drop() vor jedem Lauf alle Dokumente und Indizes der Collection. Führst du generate_embeddings.py ohne diese Zeile erneut aus, werden Duplikate eingefügt, wodurch $vectorSearch dasselbe Dokument mehrfach zurückgibt. Durch das Löschen der Collection wird auch der Vector-Search-Index entfernt, daher musst du create_vector_index.py jedes Mal erneut ausführen, wenn du generate_embeddings.py neu startest.

Führe das Skript aus:

python generate_embeddings.py

Du siehst etwa folgende Ausgabe im Terminal:

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

Jedes Dokument in MongoDB enthält nun sowohl den Originaltext als auch seinen 768‑dimensionalen Vektor im Feld embedding. Der Vektor liegt direkt im gleichen Dokument wie deine Daten – es ist also zur Abfragezeit kein separater Speicher oder Lookup nötig.

Einen MongoDB-Vector-Search-Index erstellen

Erstelle eine Datei namens create_vector_index.py. Diese Datei definiert und erstellt einen Vector-Search-Index auf dem Feld embedding, damit MongoDB $vectorSearch-Abfragen gegen deine Collection ausführen kann.

Die Indexdefinition benötigt drei Felder:

  • path: das Feld, das die Embeddings enthält (embedding in diesem Tutorial)
  • numDimensions: muss der Ausgabelänge des Modells entsprechen (768 bei nomic-embed-text-v1)
  • similarity: die Vergleichsfunktion (cosine ist für dieses Modell korrekt)

Der Wert numDimensions muss exakt zu deinem Embedding-Modell passen. Eine Abweichung führt dazu, dass die Indexerstellung fehlschlägt:

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

# Ersetze den Platzhalter durch deinen Atlas-Verbindungsstring:
mongodb_client = MongoClient(
    "mongodb+srv://<USERNAME>:<PASSWORD>@<HOST>/",
    appname="devrel-tutorial-python-semantic-search"
)

# Verweise auf dieselbe Datenbank und Collection wie im vorherigen Schritt:
database = mongodb_client["sample_db"]
collection = database["documents"]

# Definiere den Vector-Search-Index.
# Die drei Pflichtfelder sagen MongoDB, was indiziert wird und wie Vektoren verglichen werden:
search_index_model = SearchIndexModel(
    definition={
        "fields": [
            {
                "type": "vector",
                "path": "embedding",      # Feldname aus generate_embeddings.py
                "numDimensions": 768,     # nomic-embed-text-v1 liefert immer 768 Dimensionen
                "similarity": "cosine"    # Empfohlene Ähnlichkeitsfunktion für dieses Modell
            }
        ]
    },
    name="vector_index",
    type="vectorSearch"
)

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

# Alle fünf Sekunden pollen, bis der Index Abfragen annehmen kann:
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()

Führe das Skript aus:

python create_vector_index.py

Du siehst etwa folgende Ausgabe im 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.

Der Index benötigt bis zu einer Minute zum Aufbau. Die Polling-Schleife prüft alle 5 Sekunden und beendet sich erst, wenn MongoDB meldet, dass der Index abfragbar ist. Fahre erst fort, wenn "vector_index is ready for querying." erscheint.

Deine Suchanfrage in einen Vektor umwandeln

Erstelle eine Datei namens generate_query_vector.py. Diese Datei importiert die Funktion get_query_embedding() aus embedding_utils.py. Führe generate_query_vector.py aus, um zu prüfen, ob das Embedding-Modell korrekt lädt und einen 768‑dimensionalen Vektor erzeugt:

  • Sie definiert die Funktion get_query_embedding(), die im nächsten Schritt importiert wird.
  • Du kannst sie eigenständig ausführen, um zu verifizieren, dass das Modell funktioniert.

Du musst hier dasselbe Modell verwenden wie im ersten Schritt. Ein anderes Modell erzeugt Vektoren in einem anderen numerischen Raum – ein Vergleich wäre sinnlos:

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

Führe das Skript aus:

python generate_query_vector.py

Du erhältst eine Ausgabe ähnlich dieser:

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

Die Zeile Vector dimensions: 768 bestätigt, dass die Modell-Ausgabe zu deinen gespeicherten Embeddings passt.

Eine $vectorSearch-Abfrage ausführen

Erstelle eine Datei namens run_vector_search.py. Diese Datei importiert die Funktion get_query_embedding() aus embedding_utils.py und wandelt den Suchbegriff eines Nutzers in einen Vektor um. run_vector_search.py führt anschließend eine $vectorSearch-Aggregationspipeline aus und gibt die Ergebnisse nach semantischer Relevanz sortiert aus:

from embedding_utils import get_query_embedding
from pymongo import MongoClient

# Ersetze den Platzhalter durch deinen Atlas-Verbindungsstring:
mongodb_client = MongoClient(
    "mongodb+srv://<USERNAME>:<PASSWORD>@<HOST>/",
    appname="devrel-tutorial-python-semantic-search"
)

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

# Erzeuge einen Anfragevektor aus der Nutzereingabe:
user_query = "I need an automated, scalable system for serious information storage"
query_vector = get_query_embedding(user_query)

# Definiere die $vectorSearch-Aggregationspipeline:
pipeline = [
    {
        "$vectorSearch": {
            "index": "vector_index",      # Der in create_vector_index.py erstellte Index
            "path": "embedding",          # Das Feld mit den gespeicherten Vektoren
            "queryVector": query_vector,  # Vektor, erzeugt aus der Nutzeranfrage
            "numCandidates": 150,         # Anzahl betrachteter Nachbarn
            "limit": 3                    # Anzahl zurückgegebener Ergebnisse
        }
    },
    {
        "$project": {
            "_id": 0,
            "title": 1,
            "text": 1,
            "score": {
                "$meta": "vectorSearchScore"  # Relevanzscore je Ergebnis
            }
        }
    }
]

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

Im obigen Code steuern zwei Parameter das Suchverhalten:

  • numCandidates legt die Größe des anfänglichen Suchpools fest, den MongoDB vor der Auswahl der finalen Ergebnisse betrachtet. Ein höherer Wert verbessert die Recall, dauert aber etwas länger.
  • limit bestimmt die Anzahl der Rückgabewerte. In diesem Tutorial ist limit auf 3 gesetzt und numCandidates auf 150. Als Faustregel kannst du numCandidates mit dem 10‑ bis 15‑Fachen deines Limits starten.

Führe nun das Skript aus:

python run_vector_search.py

Du siehst etwa folgende Ausgabe:

<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" erzielt die höchste Punktzahl, obwohl die Anfrage "I need an automated, scalable system for serious information storage" keine Wörter mit "MongoDB Atlas is a fully managed cloud database." teilt. Das Ergebnis erscheint, weil die Vektoren inhaltlich nah beieinanderliegen: "...ein automatisiertes, skalierbares System für Informationsspeicherung" entspricht semantisch "...eine vollständig verwaltete Cloud-Datenbank". Eine Textsuche nach derselben Anfrage würde null Treffer liefern, weil keine exakten Wörter vorkommen. Genau so soll Semantic Search arbeiten.

Wichtigste Erkenntnisse

  • MongoDB Vector Search läuft auf einem M0-Free-Tier-Cluster. Kein kostenpflichtiger Plan nötig.
  • Du musst dasselbe Embedding-Modell sowohl zum Erzeugen der gespeicherten Embeddings als auch für Anfragevektoren verwenden. Unterschiedliche Modelle liefern bedeutungslose Ergebnisse.
  • Der Wert numDimensions in deiner Indexdefinition muss exakt der Ausgabelänge deines Embedding-Modells entsprechen. nomic-embed-text-v1 liefert immer 768 Dimensionen.
  • input_type="document" optimiert Embeddings für die Speicherung. input_type="query" optimiert sie für das Retrieval. Verwende in jeder Phase den passenden Typ.
  • numCandidates steuert, wie weit MongoDB das Suchnetz spannt, bevor auf die finalen limit-Ergebnisse eingegrenzt wird. Ein höherer Wert verbessert die Recall, kostet aber Laufzeit.
  • vectorSearchScore sortiert Ergebnisse nach semantischer Ähnlichkeit. Treffer müssen keine exakten Wörter der Anfrage enthalten. Scores variieren je nach Modell und Datensatz, doch folgende Grenzen sind ein guter Startpunkt für nomic-embed-text-v1 mit Kosinus-Ähnlichkeit:
    • 0,9 und höher: Nahezu identische Bedeutung. Dokument und Anfrage sind semantisch fast gleich.
    • 0,7 bis 0,9: Hohe Relevanz. Das Dokument passt klar zur Suchintention.
    • 0,5 bis 0,7: Mittlere Relevanz. Das Dokument ist thematisch verwandt, nutzt aber anderes Framing oder Kontext.
    • Unter 0,5: Geringe Relevanz. Der Bezug ist schwach; das Ergebnis ist evtl. nicht hilfreich.

Den gesamten Code zu diesem Tutorial findest du im GitHub-Repository.

Weiterführende Ressourcen

FAQs

Brauche ich einen kostenpflichtigen Atlas-Plan, um Semantic Search zu implementieren?

Nein. Alle vier Schritte in diesem Tutorial laufen auf einem M0-Free-Tier-Cluster und sind kostenlos.

Was passiert, wenn ich für die Anfrage ein anderes Embedding-Modell nutze als für die gespeicherten Dokumente?

Die Ergebnisse sind bedeutungslos. Die Vektoren sind nicht vergleichbar, weil unterschiedliche Modelle Text in unterschiedliche Zahlenräume abbilden. Verwende immer dasselbe Modell für Indexierung und Abfrage.

Was ist der Unterschied zwischen `numCandidates` und `limit`?

numCandidates ist die Anzahl der Vektoren, die MongoDB während der Suche betrachtet. limit ist die Anzahl der Top-Ergebnisse, die du zurückbekommst. Ein höherer numCandidates-Wert verbessert die Ergebnisqualität bei leicht längerer Laufzeit. Als Startpunkt nimm 10 bis 15 mal dein limit.

Kann ich Semantic Search und Textsuche zusammen verwenden?

Ja. MongoDB unterstützt Hybrid Search, die $vectorSearch und $search in einer Pipeline kombiniert.

Funktioniert Semantic Search auch für andere Sprachen als Englisch?

Das hängt von deinem Embedding-Modell ab. nomic-embed-text-v1 ist vor allem auf englische Texte trainiert. Für mehrsprachige Anwendungsfälle wähle ein mehrsprachiges Embedding-Modell, das auf deinen Sprachen trainiert wurde.


Damilola Oladele's photo
Author
Damilola Oladele
Themen