Sono questi i momenti in cui la normale ricerca testuale non basta. Ecco cosa succede in pratica:
- Un portale di assistenza non restituisce risultati quando un utente digita "non si accende" invece dell'esatta frase usata nell'articolo di supporto.
- Una piattaforma e-commerce non trova nulla quando un cliente cerca "qualcosa di caldo per l'inverno" perché nessuna descrizione prodotto contiene esattamente quelle parole.
- Una knowledge base perde il documento giusto perché l'utente ha formulato la domanda in modo diverso rispetto a come è scritto il documento.
La ricerca semantica risolve il problema. Capisce il significato e l'intento dietro una query invece di cercare la corrispondenza esatta delle parole.
Questo tutorial ti mostra come implementare la ricerca semantica in MongoDB usando Python e il modello di embedding gratuito nomic-embed-text-v1.
Nota: nomic-embed-text-v1 occupa circa 0,27 GB quando è caricato in memoria, quindi assicurati che la tua macchina abbia almeno 1 GB di RAM disponibile prima di eseguire qualsiasi script di questo tutorial. Al primo avvio il modello viene scaricato da Hugging Face, quindi prevedi qualche minuto in più a seconda della tua connessione. Le esecuzioni successive caricano il modello dalla cache locale. L'inferenza su CPU è significativamente più lenta rispetto alla GPU. Sulle macchine senza GPU dedicata, il modello gira su CPU e usa la RAM invece della VRAM, quindi la generazione degli embedding può richiedere più tempo.
In questo tutorial imparerai:
- Cosa sono gli embedding vettoriali e come rappresentano il significato
- Come generare e archiviare embedding in MongoDB
- Come creare un indice MongoDB Vector Search
- Come convertire una query utente in un vettore
- Come eseguire una pipeline di aggregazione
$vectorSearche interpretarne i risultati
Puoi trovare tutti gli esempi di codice di questo tutorial nella repository GitHub.
Ricerca testuale vs ricerca semantica
La ricerca testuale trova i documenti che contengono esattamente le parole della tua query. La ricerca semantica trova i documenti che hanno lo stesso significato della tua query, anche se usano parole completamente diverse.
La tabella seguente mostra cosa restituisce ciascun approccio per la query di ricerca "heart problems":
| Testo del documento | Ricerca testuale | Ricerca semantica | Perché la ricerca testuale lo restituisce |
|---|---|---|---|
| "...heart problems in adults..." | Sì | Sì | Contiene le parole esatte "heart problems" |
| "...cardiac conditions and symptoms..." | No | Sì | Non contiene le parole esatte "heart problems" |
| "...risk factors for heart disease..." | No | Sì | Non contiene le parole esatte "heart problems" |
| "...chest pain and shortness of breath..." | No | Sì | Non contiene le parole esatte "heart problems" |
Concetti della ricerca semantica
Tre concetti sono centrali per la ricerca semantica in MongoDB. Ti aiuteranno a capire perché ogni passaggio di questo tutorial funziona così com'è.
Embedding vettoriali
Un modello di embedding converte il testo in un elenco a lunghezza fissa di numeri chiamato vettore. Ogni numero del vettore rappresenta una dimensione di significato.
Due testi con significati simili producono vettori numericamente vicini. "cardiac conditions" e "heart problems" cadono vicini nello spazio vettoriale, anche se non condividono parole. Questa vicinanza rende possibile la ricerca semantica.
Questo tutorial usa il modello di embedding nomic-embed-text-v1, gratuito, open source e interamente eseguibile in locale. Il modello viene scaricato automaticamente da Hugging Face al primo avvio dello script e salvato in locale per le esecuzioni successive. Il modello produce 768 numeri per input.
Indici di ricerca vettoriale
Un indice di ricerca vettoriale indica a MongoDB quale campo contiene gli embedding, quante dimensioni aspettarsi e quale funzione di similarità usare per il confronto. Devi creare questo indice prima di poter eseguire qualsiasi query $vectorSearch.
Lo stage di aggregazione $vectorSearch
$vectorSearch è lo stage di aggregazione che esegue la ricerca. Accetta un vettore di query, cerca nel campo indicizzato e restituisce i documenti ordinati per similarità semantica. Puoi concatenarlo con $project e altri stage come in qualsiasi pipeline di aggregazione.
Iniziamo.
Prerequisiti
Prima di iniziare, assicurati di avere quanto segue:
- Python 3.8 o versione successiva installato
- Un account MongoDB Atlas con un cluster M0 (Free tier) configurato
pymongo4.7 o successivo, pacchetti Pythonsentence-transformersedeinopsinstallati- Familiarità di base con Python e le collection MongoDB
- La tua stringa di connessione Atlas, disponibile nell'interfaccia Atlas sotto **Database > Connect > Drivers**
- Il tuo indirizzo IP autorizzato in Atlas prima di eseguire gli script. Vai su **Security > Network Access** nell'interfaccia Atlas e aggiungi il tuo IP corrente.
Configura il progetto
Crea una cartella per il progetto e naviga al suo interno dal terminale:
mkdir mongodb-semantic-search
cd mongodb-semantic-search
Crea e attiva un ambiente virtuale per isolare le dipendenze del progetto:
python -m venv venv
source venv/bin/activate
Su Windows, attiva l'ambiente virtuale con:
venv\Scripts\activate
Ora installa i pacchetti richiesti:
pip install pymongo sentence-transformers einops
Creerai un file Python per ciascun passaggio di questo tutorial. Tutti i file vanno nella cartella mongodb-semantic-search.
Crea il file di utility per gli embedding
Crea un file chiamato embedding_utils.py. Questo file contiene entrambe le funzioni di embedding usate nel tutorial:
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()
Attenzione: trust_remote_code=True consente al modello di eseguire sul tuo computer codice Python specifico del modello scaricato da Hugging Face. Se il codice sorgente del modello su Hugging Face viene compromesso o aggiornato con modifiche malevole, quel codice viene eseguito automaticamente nel tuo ambiente. Valuta con cautela l'uso di questo parametro in produzione.
Genera e archivia gli embedding vettoriali
Crea un file chiamato generate_embeddings.py. Questo file importa la funzione di embedding da embedding_utils.py, genera un vettore per il campo di testo di ciascun documento e archivia il documento completo, incluso l'embedding, 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()
Nel codice precedente, collection.drop() elimina tutti i documenti e gli indici nella collection prima di ogni esecuzione. Rieseguire generate_embeddings.py senza questa riga inserisce documenti duplicati, causando che $vectorSearch restituisca più volte lo stesso documento. L'eliminazione della collection rimuove anche l'indice di ricerca vettoriale, quindi devi rieseguire create_vector_index.py ogni volta che riesegui generate_embeddings.py.
Esegui lo script:
python generate_embeddings.py
Vedrai il seguente output nel terminale:
<All keys matched successfully>
Inserted 3 documents with embeddings.
Ora ogni documento in MongoDB contiene sia il testo originale sia il suo vettore a 768 dimensioni nel campo embedding. Il vettore vive accanto ai tuoi dati nello stesso documento, quindi non serve uno storage separato o un lookup aggiuntivo in fase di query.
Crea un indice MongoDB Vector Search
Crea un file chiamato create_vector_index.py. Questo file definisce e crea un indice di ricerca vettoriale sul campo embedding così che MongoDB possa eseguire query $vectorSearch sulla tua collection.
La definizione dell'indice richiede tre campi:
path: il campo che contiene gli embedding (embeddingin questo tutorial)numDimensions: deve corrispondere alla dimensione dell'output del modello (768pernomic-embed-text-v1)similarity: la funzione di confronto (cosineè corretta per questo modello)
Il valore numDimensions deve corrispondere esattamente al tuo modello di embedding. Un mismatch fa fallire la creazione dell'indice:
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()
Esegui lo script:
python create_vector_index.py
Vedrai il seguente output nel terminale:
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 creazione dell'indice può richiedere fino a un minuto. Il loop di polling controlla ogni 5 secondi ed esce solo quando MongoDB conferma che l'indice è interrogabile. Non passare allo step successivo finché non vedi "vector_index is ready for querying."
Converti la tua query di ricerca in un vettore
Crea un file chiamato generate_query_vector.py. Questo file importa la funzione get_query_embedding() dal file embedding_utils.py. Esegui il file generate_query_vector.py per verificare che il modello di embedding si carichi correttamente e produca un vettore a 768 dimensioni:
- Definisce la funzione
get_query_embedding()che verrà importata nel passaggio successivo. - Puoi eseguirlo da solo per verificare che il modello funzioni correttamente.
Devi usare lo stesso modello qui come nel primo passaggio. Un modello diverso produce vettori che occupano uno spazio numerico diverso, rendendo il confronto privo di senso:
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]}")
Esegui lo script:
python generate_query_vector.py
Otterrai un output simile a questo:
<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]
Nell'output precedente, Vector dimensions: 768 conferma che l'output del modello corrisponde ai tuoi embedding archiviati.
Esegui una query $vectorSearch
Crea un file chiamato run_vector_search.py. Questo file importa la funzione get_query_embedding() dal file embedding_utils.py e converte il termine di ricerca di un utente in un vettore. run_vector_search.py esegue una pipeline di aggregazione $vectorSearch e stampa i risultati ordinati per similarità semantica:
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()
Nel codice precedente, due parametri controllano il comportamento della ricerca:
numCandidatesimposta la dimensione del pool di ricerca iniziale che MongoDB esamina prima di restringere ai risultati finali. Un valore più alto migliora il recall ma richiede leggermente più tempo.limitimposta quanti risultati ricevi. In questo tutorial,limitè impostato a 3 e numCandidates a 150. Un punto di partenza comune è impostarenumCandidatesa 10-15 volte il tuo limit.
Ora esegui lo script:
python run_vector_search.py
Otterrai un output simile a questo:
<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" ottiene il punteggio più alto anche se la query "I need an automated, scalable system for serious information storage" non condivide parole con "MongoDB Atlas is a fully managed cloud database." La ricerca lo restituisce perché i loro vettori sono vicini nel significato: "...un sistema automatizzato e scalabile per l'archiviazione delle informazioni" mappa semanticamente a "...un database cloud completamente gestito". Una ricerca testuale per la stessa query restituirebbe zero risultati, perché nessuna delle parole esatte della query appare in alcun documento. Questa è la ricerca semantica che funziona come previsto.
Punti chiave
- MongoDB Vector Search funziona su un cluster M0 Free tier. Non serve un piano a pagamento.
- Devi usare lo stesso modello di embedding sia per generare gli embedding archiviati sia per generare i vettori di query. Mescolare modelli produce risultati privi di significato.
- Il valore
numDimensionsnella definizione dell'indice deve corrispondere esattamente alla dimensione dell'output del tuo modello di embedding.nomic-embed-text-v1produce sempre 768 dimensioni. input_type="document"ottimizza gli embedding per l'archiviazione.input_type="query"li ottimizza per il recupero. Usa il tipo corretto in ogni fase.numCandidatescontrolla l'ampiezza della ricerca che MongoDB esegue prima di restringere ai risultati finali definiti dalimit. Un valore più alto migliora il recall a scapito del tempo di query.vectorSearchScoreordina i risultati per similarità semantica. I risultati non devono contenere nessuna delle parole esatte della query. Gli intervalli di punteggio variano per modello e dataset, ma i seguenti limiti sono un buon punto di partenza pernomic-embed-text-v1con similarità coseno:- 0,9 e oltre: Significato quasi identico. Documento e query sono semanticamente quasi uguali.
- 0,7–0,9: Forte rilevanza. Il documento è chiaramente legato all'intento della query.
- 0,5–0,7: Rilevanza moderata. Il documento è correlato all'argomento ma usa un inquadramento o un contesto diverso.
- Sotto 0,5: Rilevanza debole. Il collegamento è labile e il risultato potrebbe non essere utile.
Puoi trovare tutti gli esempi di codice di questo tutorial nella repository GitHub.
Approfondimenti
- Panoramica di MongoDB Vector Search copre tutte le funzionalità di MongoDB Vector Search, inclusi filtraggio e quantizzazione.
- Come eseguire una ricerca ibrida mostra come combinare ricerca vettoriale e full-text in un'unica query.
- Come creare embedding vettorialispiega come generare embedding vettoriali per i dati testuali nelle tue collection usando modelli di embedding di Voyage AI, OpenAI e altri provider open source.
- Retrieval-Augmented Generation (RAG) con MongoDB mostra come usare la ricerca semantica come livello di retrieval in un'applicazione di retrieval-augmented generation.
FAQ
Ho bisogno di un piano Atlas a pagamento per implementare la ricerca semantica?
No. Tutti e quattro i passaggi di questo tutorial funzionano su un cluster M0 Free tier, che è gratuito.
Cosa succede se uso un modello di embedding diverso per la mia query rispetto ai miei documenti archiviati?
I risultati saranno privi di significato. I vettori non saranno confrontabili perché modelli diversi mappano il testo in spazi numerici differenti. Usa sempre lo stesso modello sia per l'indicizzazione sia per le query.
Qual è la differenza tra `numCandidates` e `limit`?
numCandidates indica quanti vettori MongoDB esamina durante la ricerca. limit indica quanti dei migliori risultati ti restituisce. Un valore numCandidates più alto migliora la qualità dei risultati a fronte di query leggermente più lente. Un punto di partenza comune è impostare numCandidates a 10-15 volte il tuo limit.
Posso usare insieme ricerca semantica e ricerca testuale?
Sì. MongoDB supporta la ricerca ibrida, che combina $vectorSearch e $search in un'unica pipeline.
La ricerca semantica funziona per lingue diverse dall'inglese?
Dipende dal tuo modello di embedding. nomic-embed-text-v1 è addestrato principalmente su testo in inglese. Per casi d'uso multilingue, scegli un modello multilingue addestrato sulle lingue contenute nei tuoi dati.
