Przejdź do głównej treści

Jak zaimplementować wyszukiwanie semantyczne w MongoDB

Dowiedz się, jak zaimplementować wyszukiwanie semantyczne w MongoDB za pomocą Pythona. Generuj wektorowe osadzenia, utwórz indeks Vector Search i uruchamiaj zapytania $vectorSearch.
Zaktualizowano 31 lip 2026  · 9 min Czytać

Eksploruj z AI

Otwórz w ChatGPTOtwórz w ClaudeOtwórz w Perplexity

To są momenty, w których zwykłe wyszukiwanie tekstowe nie wystarcza. W praktyce wygląda to tak:

  • Portal wsparcia klienta nie zwraca wyników, gdy użytkownik wpisuje „nie włącza się” zamiast dokładnego sformułowania użytego w artykule pomocy.
  • Platforma e-commerce nic nie znajduje, gdy kupujący szuka „czegoś ciepłego na zimę”, ponieważ żaden opis produktu nie zawiera dokładnie tych słów.
  • Baza wiedzy pomija właściwy dokument, bo użytkownik sformułował pytanie inaczej, niż napisano dokument.

Wyszukiwanie semantyczne rozwiązuje ten problem. Rozumie znaczenie i intencję zapytania, a nie tylko dopasowuje dokładne słowa.

Ten samouczek pokazuje, jak zaimplementować wyszukiwanie semantyczne w MongoDB przy użyciu Pythona i darmowego modelu osadzeń nomic-embed-text-v1.

Uwaga: nomic-embed-text-v1 zajmuje w pamięci około 0,27 GB, więc przed uruchomieniem jakiegokolwiek skryptu z tego samouczka upewnij się, że masz co najmniej 1 GB wolnej pamięci RAM. Podczas pierwszego uruchomienia model jest pobierany z Hugging Face, więc w zależności od połączenia internetowego przewidź dodatkowy czas. Wszystkie kolejne uruchomienia ładują model z lokalnego cache’u. Wnioskowanie na CPU jest znacząco wolniejsze niż na GPU. Na maszynach bez dedykowanego GPU model działa na CPU i używa RAM zamiast VRAM, dlatego generowanie osadzeń może trwać dłużej.

W tym samouczku nauczysz się:

  • Czym są wektorowe osadzenia i jak reprezentują znaczenie
  • Jak generować i przechowywać osadzenia w MongoDB
  • Jak utworzyć indeks MongoDB Vector Search
  • Jak zamienić zapytanie użytkownika na wektor
  • Jak uruchomić potok agregacji $vectorSearch i zinterpretować wyniki

Wszystkie przykłady kodu do tego samouczka znajdziesz w repozytorium GitHub.

Wyszukiwanie tekstowe vs wyszukiwanie semantyczne

Wyszukiwanie tekstowe dopasowuje dokumenty zawierające dokładne słowa z twojego zapytania. Wyszukiwanie semantyczne dopasowuje dokumenty niosące to samo znaczenie co twoje zapytanie, nawet jeśli używają zupełnie innych słów.

Poniższa tabela pokazuje, co zwraca każde podejście dla zapytania „heart problems”:

Treść dokumentu Wyszukiwanie tekstowe Wyszukiwanie semantyczne Dlaczego wyszukiwanie tekstowe to zwraca
"...heart problems in adults..." Tak Tak Zawiera dokładne słowa „heart problems”
"...cardiac conditions and symptoms..." Nie Tak Nie zawiera dokładnych słów „heart problems”
"...risk factors for heart disease..." Nie Tak Nie zawiera dokładnych słów „heart problems”
"...chest pain and shortness of breath..." Nie Tak Nie zawiera dokładnych słów „heart problems”

Koncepcje wyszukiwania semantycznego

Trzy pojęcia są kluczowe dla wyszukiwania semantycznego w MongoDB. Pomogą ci zrozumieć, dlaczego każdy krok w tym samouczku działa tak, jak działa.

Wektorowe osadzenia

Model osadzeń konwertuje tekst na listę liczb o stałej długości, zwaną wektorem. Każda liczba wektora reprezentuje wymiar znaczenia.

Dwa teksty o podobnym znaczeniu dają wektory, które są do siebie numerycznie bliskie. „cardiac conditions” i „heart problems” lądują blisko siebie w przestrzeni wektorowej, mimo że nie mają wspólnych słów. Ta bliskość umożliwia wyszukiwanie semantyczne.

W tym samouczku używamy modelu osadzeń nomic-embed-text-v1, który jest darmowy, open-source i działa w całości na twojej lokalnej maszynie. Model automatycznie pobiera się z Hugging Face przy pierwszym uruchomieniu skryptu i zapisuje lokalnie na kolejne uruchomienia. Model produkuje 768 liczb na każde wejście.

Indeksy wyszukiwania wektorowego

Indeks wyszukiwania wektorowego mówi MongoDB, które pole zawiera osadzenia, ile wymiarów należy oczekiwać i której funkcji podobieństwa użyć do porównań. Musisz utworzyć ten indeks, zanim uruchomisz jakiekolwiek zapytania $vectorSearch.

Etap agregacji $vectorSearch

$vectorSearch to etap agregacji, który uruchamia wyszukiwanie. Przyjmuje wektor zapytania, przeszukuje zindeksowane pole i zwraca dokumenty uporządkowane według podobieństwa semantycznego. Łączysz go z $project i innymi etapami tak jak każdy inny potok agregacji.

Zaczynajmy.

Wymagania wstępne

Zanim zaczniesz, upewnij się, że masz przygotowane:

  • Zainstalowany Python 3.8 lub nowszy
  • Konto MongoDB Atlas z klastrem M0 (darmowa warstwa)
  • Zainstalowane pakiety Pythona: pymongo 4.7 lub nowszy, sentence-transformers i einops
  • Podstawowa znajomość Pythona i kolekcji MongoDB
  • Twój łańcuch połączenia Atlas, dostępny w interfejsie Atlas w **Database > Connect > Drivers**
  • Twój adres IP jest dozwolony w Atlas przed uruchomieniem skryptów. Przejdź w interfejsie Atlas do **Security > Network Access** i dodaj swój bieżący adres IP.

Skonfiguruj projekt

Utwórz folder dla projektu i przejdź do niego w terminalu:

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

Utwórz i aktywuj wirtualne środowisko, aby odizolować zależności projektu:

python -m venv venv
source venv/bin/activate

W systemie Windows aktywuj wirtualne środowisko poleceniem:

venv\Scripts\activate

Teraz zainstaluj wymagane pakiety:

pip install pymongo sentence-transformers einops

Utworzysz jeden plik Pythona dla każdego kroku tego samouczka. Wszystkie pliki umieść w folderze mongodb-semantic-search.

Utwórz plik narzędzi do osadzeń

Utwórz plik o nazwie embedding_utils.py. Ten plik zawiera obie funkcje osadzeń używane w tym samouczku:

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

Ostrzeżenie: trust_remote_code=True pozwala modelowi wykonywać specyficzny dla modelu kod Pythona pobrany z Hugging Face na twojej maszynie. Jeśli kod źródłowy modelu na Hugging Face zostanie skompromitowany lub zaktualizowany o złośliwe zmiany, ten kod uruchomi się automatycznie w twoim środowisku. Zachowaj ostrożność przed użyciem tego parametru w środowisku produkcyjnym.

Generuj i przechowuj wektorowe osadzenia

Utwórz plik o nazwie generate_embeddings.py. Ten plik importuje funkcję osadzeń z embedding_utils.py, generuje wektor dla pola tekstowego każdego dokumentu i zapisuje pełny dokument, łącznie z osadzeniem, w 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()

W powyższym kodzie collection.drop() usuwa wszystkie dokumenty i indeksy w kolekcji przed każdym uruchomieniem. Ponowne uruchomienie generate_embeddings.py bez tej linii wstawia zduplikowane dokumenty, przez co $vectorSearch może zwrócić ten sam dokument wielokrotnie. Usunięcie kolekcji kasuje też indeks wyszukiwania wektorowego, więc za każdym razem, gdy ponownie uruchamiasz generate_embeddings.py, musisz ponownie uruchomić create_vector_index.py.

Uruchom skrypt:

python generate_embeddings.py

W terminalu zobaczysz taki wynik:

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

Każdy dokument w MongoDB zawiera teraz zarówno oryginalny tekst, jak i jego 768-wymiarowy wektor w polu embedding. Wektor żyje obok twoich danych w tym samym dokumencie, więc przy zapytaniu nie jest potrzebne osobne przechowywanie ani dodatkowe wyszukiwanie.

Utwórz indeks MongoDB Vector Search

Utwórz plik o nazwie create_vector_index.py. Ten plik definiuje i tworzy indeks wyszukiwania wektorowego na polu embedding, aby MongoDB mogło uruchamiać zapytania $vectorSearch na twojej kolekcji.

Definicja indeksu wymaga trzech pól:

  • path: pole, które przechowuje osadzenia (w tym samouczku embedding)
  • numDimensions: musi odpowiadać rozmiarowi wyjścia modelu (768 dla nomic-embed-text-v1)
  • similarity: funkcja porównawcza (dla tego modelu poprawna jest cosine)

Wartość numDimensions musi dokładnie odpowiadać twojemu modelowi osadzeń. Niezgodność spowoduje błąd tworzenia indeksu:

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

Uruchom skrypt:

python create_vector_index.py

W terminalu zobaczysz taki wynik:

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.

Zbudowanie indeksu trwa do minuty. Pętla odpytywania sprawdza co 5 sekund i kończy się dopiero, gdy MongoDB potwierdzi, że indeks jest gotowy do zapytań. Nie przechodź do następnego kroku, dopóki nie zobaczysz „vector_index is ready for querying.”

Zamień zapytanie wyszukiwania na wektor

Utwórz plik o nazwie generate_query_vector.py. Ten plik importuje funkcję get_query_embedding() z pliku embedding_utils.py. Uruchom plik generate_query_vector.py, aby sprawdzić, czy model osadzeń ładuje się poprawnie i tworzy 768-wymiarowy wektor:

  • Definiuje funkcję get_query_embedding(), którą importuje następny krok.
  • Możesz uruchomić go samodzielnie, aby zweryfikować, czy model działa poprawnie.

Musisz użyć tego samego modelu co w pierwszym kroku. Inny model tworzy wektory zajmujące inną przestrzeń liczbową, co uniemożliwia sensowne porównanie:

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

Uruchom skrypt:

python generate_query_vector.py

Otrzymasz wynik podobny do tego:

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

W powyższym wyniku Vector dimensions: 768 potwierdza, że wynik modelu pasuje do twoich przechowywanych osadzeń.

Uruchom zapytanie $vectorSearch

Utwórz plik o nazwie run_vector_search.py. Ten plik importuje funkcję get_query_embedding() z pliku embedding_utils.py i konwertuje termin wyszukiwania użytkownika na wektor. run_vector_search.py uruchamia potok agregacji $vectorSearch i wypisuje wyniki uporządkowane według podobieństwa semantycznego:

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

W powyższym kodzie dwa parametry kontrolują zachowanie wyszukiwania:

  • numCandidates ustawia rozmiar początkowej puli wyszukiwania, którą MongoDB analizuje, zanim zawęzi do wyników końcowych. Większa wartość poprawia recall kosztem nieco dłuższego czasu.
  • limit określa liczbę zwracanych wyników. W tym samouczku limit ustawiono na 3, a numCandidates na 150. Dobrym punktem wyjścia jest ustawienie numCandidates na 10–15 razy więcej niż twój limit.

Teraz uruchom skrypt:

python run_vector_search.py

W terminalu zobaczysz wynik podobny do tego:

<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” ma najwyższy wynik, mimo że zapytanie „I need an automated, scalable system for serious information storage” nie dzieli żadnych słów z „MongoDB Atlas is a fully managed cloud database.” Wyszukiwanie zwraca go, ponieważ ich wektory są bliskie znaczeniowo: „...zautomatyzowany, skalowalny system do przechowywania informacji” semantycznie mapuje się na „...w pełni zarządzana baza danych w chmurze”. Wyszukiwanie tekstowe dla tego samego zapytania zwróciłoby zero wyników, ponieważ żadne z dokładnych słów zapytania nie pojawia się w żadnym dokumencie. Tak właśnie działa wyszukiwanie semantyczne.

Najważniejsze wnioski

  • MongoDB Vector Search działa na klastrze M0 (darmowa warstwa). Nie jest wymagana płatna subskrypcja.
  • Musisz używać tego samego modelu osadzeń zarówno do generowania przechowywanych osadzeń, jak i do generowania wektorów zapytań. Mieszanie modeli daje bezsensowne wyniki.
  • Wartość numDimensions w definicji indeksu musi dokładnie odpowiadać rozmiarowi wyjściowemu twojego modelu osadzeń. nomic-embed-text-v1 zawsze zwraca 768 wymiarów.
  • input_type="document" optymalizuje osadzenia do przechowywania. input_type="query" optymalizuje je pod wyszukiwanie. Używaj właściwego typu na każdym etapie.
  • numCandidates kontroluje szerokość sieci wyszukiwania, jaką MongoDB zarzuca, zanim zawęzi do końcowych wyników limit. Większa wartość poprawia recall kosztem czasu zapytania.
  • vectorSearchScore porządkuje wyniki według podobieństwa semantycznego. Wyniki nie muszą zawierać żadnych dokładnych słów z zapytania. Zakresy punktacji różnią się w zależności od modelu i zbioru danych, ale poniższe progi to użyteczny punkt wyjścia dla nomic-embed-text-v1 z kosinusowym podobieństwem:
    • 0,9 i więcej: Prawie identyczne znaczenie. Dokument i zapytanie są semantycznie niemal takie same.
    • 0,7–0,9: Silna trafność. Dokument wyraźnie odnosi się do intencji zapytania.
    • 0,5–0,7: Umiarkowana trafność. Dokument jest tematycznie powiązany, ale używa innej ramy lub kontekstu.
    • Poniżej 0,5: Słaba trafność. Powiązanie jest luźne i wynik może być mało użyteczny.

Wszystkie przykłady kodu do tego samouczka znajdziesz w repozytorium GitHub.

Polecane materiały

FAQ

Czy potrzebuję płatnego planu Atlas, żeby wdrożyć wyszukiwanie semantyczne?

Nie. Wszystkie cztery kroki w tym samouczku działają na klastrze M0 (darmowa warstwa), który jest bezpłatny.

Co się stanie, jeśli użyję innego modelu osadzeń dla mojego zapytania niż dla przechowywanych dokumentów?

Twoje wyniki będą bezsensowne. Wektorów nie da się porównać, ponieważ różne modele mapują tekst na różne przestrzenie liczbowe. Zawsze używaj tego samego modelu do indeksowania i zapytań.

Jaka jest różnica między `numCandidates` a `limit`?

numCandidates to liczba wektorów, które MongoDB analizuje podczas wyszukiwania. limit to liczba najlepszych wyników, które do ciebie wracają. Wyższa wartość numCandidates poprawia jakość wyników kosztem nieco wolniejszych zapytań. Dobrym punktem wyjścia jest ustawienie numCandidates na 10–15 razy więcej niż limit.

Czy mogę używać wyszukiwania semantycznego i tekstowego razem?

Tak. MongoDB obsługuje wyszukiwanie hybrydowe, które łączy $vectorSearch i $search w jednym potoku.

Czy wyszukiwanie semantyczne działa w językach innych niż angielski?

To zależy od twojego modelu osadzeń. nomic-embed-text-v1 jest trenowany głównie na angielskich tekstach. Do zastosowań wielojęzycznych wybierz wielojęzyczny model osadzeń wytrenowany na językach, które zawierają twoje dane.

Tematy