Son esos momentos en los que la búsqueda de texto tradicional se queda corta. En la práctica, suele verse así:
- Un portal de soporte no devuelve resultados cuando alguien escribe "won't turn on" en lugar de la frase exacta del artículo de ayuda.
- Una tienda online no encuentra nada cuando una persona busca "algo calentito para el invierno" porque ninguna descripción contiene esas palabras exactas.
- Una base de conocimiento pasa por alto el documento correcto porque la pregunta está formulada de forma distinta a como está redactado el contenido.
La búsqueda semántica resuelve esto: entiende el significado e intención detrás de la consulta en lugar de limitarse a las palabras exactas.
Este tutorial te muestra cómo implementar búsqueda semántica en MongoDB con Python y el modelo de embeddings gratuito nomic-embed-text-v1.
Nota: nomic-embed-text-v1 ocupa aproximadamente 0,27 GB en memoria. Asegúrate de tener al menos 1 GB de RAM disponible antes de ejecutar cualquier script de este tutorial. La primera ejecución descarga el modelo desde Hugging Face, así que puede tardar más según tu conexión. Las siguientes ejecuciones cargan el modelo desde la caché local. La inferencia por CPU es bastante más lenta que por GPU. En equipos sin GPU dedicada, el modelo se ejecuta en CPU y usa RAM en lugar de VRAM, por lo que la generación de embeddings puede tardar más.
En este tutorial aprenderás:
- Qué son los embeddings vectoriales y cómo representan significado
- Cómo generar y almacenar embeddings en MongoDB
- Cómo crear un índice de MongoDB Vector Search
- Cómo convertir la consulta de un usuario en un vector
- Cómo ejecutar una canalización de agregación
$vectorSearche interpretar los resultados
Puedes encontrar todo el código de este tutorial en el repositorio de GitHub.
Búsqueda de texto vs. búsqueda semántica
La búsqueda de texto encuentra documentos que contienen las palabras exactas de tu consulta. La búsqueda semántica encuentra documentos que comparten el mismo significado, aunque utilicen palabras distintas.
La siguiente tabla muestra lo que devuelve cada enfoque para la consulta "heart problems":
| Texto del documento | Búsqueda de texto | Búsqueda semántica | Por qué lo devuelve la búsqueda de texto |
|---|---|---|---|
| "...heart problems in adults..." | Sí | Sí | Contiene las palabras exactas "heart problems" |
| "...cardiac conditions and symptoms..." | No | Sí | No contiene las palabras exactas "heart problems" |
| "...risk factors for heart disease..." | No | Sí | No contiene las palabras exactas "heart problems" |
| "...chest pain and shortness of breath..." | No | Sí | No contiene las palabras exactas "heart problems" |
Conceptos de búsqueda semántica
Tres conceptos son clave para la búsqueda semántica en MongoDB. Te ayudarán a entender por qué cada paso del tutorial funciona como funciona.
Embeddings vectoriales
Un modelo de embeddings convierte texto en una lista de números de longitud fija llamada vector. Cada número del vector representa una dimensión de significado.
Dos textos con significados similares generan vectores numéricamente cercanos. "cardiac conditions" y "heart problems" quedan próximas en el espacio vectorial, aunque no compartan palabras. Esa cercanía es lo que hace posible la búsqueda semántica.
Este tutorial utiliza el modelo de embeddings nomic-embed-text-v1, que es gratuito, de código abierto y se ejecuta íntegramente en tu máquina. El modelo se descarga automáticamente desde Hugging Face la primera vez y queda guardado para usos posteriores. El modelo produce 768 números por entrada.
Índices de búsqueda vectorial
Un índice de búsqueda vectorial le indica a MongoDB qué campo contiene los embeddings, cuántas dimensiones esperar y qué función de similitud usar para compararlos. Debes crear este índice antes de poder ejecutar consultas $vectorSearch.
La etapa de agregación $vectorSearch
$vectorSearch es la etapa de agregación que ejecuta la búsqueda. Acepta un vector de consulta, busca en el campo indexado y devuelve documentos ordenados por similitud semántica. Puedes encadenarla con $project y otras etapas como en cualquier canalización de agregación.
Vamos allá.
Requisitos previos
Antes de empezar, asegúrate de tener lo siguiente:
- Python 3.8 o posterior instalado
- Una cuenta de MongoDB Atlas con un clúster M0 (nivel gratuito) configurado
pymongo4.7 o posterior, y los paquetes de Pythonsentence-transformersyeinopsinstalados- Conocimientos básicos de Python y colecciones de MongoDB
- Tu cadena de conexión de Atlas, disponible en la interfaz de Atlas en **Database > Connect > Drivers**
- Tu dirección IP permitida en Atlas antes de ejecutar los scripts. Ve a **Security > Network Access** en la interfaz de Atlas y añade tu IP actual.
Configura tu proyecto
Crea una carpeta para tu proyecto y accede a ella desde la terminal:
mkdir mongodb-semantic-search
cd mongodb-semantic-search
Crea y activa un entorno virtual para aislar las dependencias del proyecto:
python -m venv venv
source venv/bin/activate
En Windows, activa el entorno virtual con:
venv\Scripts\activate
Ahora instala los paquetes necesarios:
pip install pymongo sentence-transformers einops
Crearás un archivo de Python para cada paso del tutorial. Todos van en la carpeta mongodb-semantic-search.
Crea el archivo de utilidades de embeddings
Crea un archivo llamado embedding_utils.py. Aquí irán las dos funciones de embeddings usadas en el tutorial:
from sentence_transformers import SentenceTransformer
# Carga el modelo de embeddings gratuito y de código abierto.
# El modelo se descarga desde Hugging Face en la primera ejecución y se guarda localmente.
# trust_remote_code=True es necesario para este modelo:
model = SentenceTransformer("nomic-ai/nomic-embed-text-v1", trust_remote_code=True)
def get_embedding(text, precision="float32"):
# Usa esta función cuando generes embeddings para almacenarlos en MongoDB:
return model.encode(text, precision=precision).tolist()
def get_query_embedding(text, precision="float32"):
# Usa esta función cuando generes el embedding de la consulta de un usuario:
return model.encode(text, precision=precision).tolist()
Advertencia: trust_remote_code=True permite que el modelo ejecute en tu máquina código Python específico descargado desde Hugging Face. Si el código fuente del modelo en Hugging Face se ve comprometido o se actualiza con cambios maliciosos, ese código se ejecutará automáticamente en tu entorno. Ten mucha precaución antes de usar este parámetro en producción.
Genera y almacena embeddings vectoriales
Crea un archivo llamado generate_embeddings.py. Este archivo importa la función de embeddings de embedding_utils.py, genera un vector para el campo text de cada documento y almacena el documento completo, incluido el embedding, en MongoDB:
from embedding_utils import get_embedding
from pymongo import MongoClient
# Sustituye el marcador por tu cadena de conexión de Atlas:
mongodb_client = MongoClient(
"mongodb+srv://<USERNAME>:<PASSWORD>@<HOST>/",
appname="devrel-tutorial-python-semantic-search"
)
collection = mongodb_client["sample_db"]["documents"]
# Datos de ejemplo:
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"],
# El vector convive con tus datos originales en el mismo documento:
"embedding": embedding
})
# Elimina la colección antes de cada ejecución para evitar duplicados:
collection.drop()
result = collection.insert_many(docs_to_insert)
print(f"Inserted {len(result.inserted_ids)} documents with embeddings.")
mongodb_client.close()
En el código anterior, collection.drop() borra todos los documentos e índices de la colección antes de cada ejecución. Si vuelves a ejecutar generate_embeddings.py sin esta línea, insertarás duplicados y $vectorSearch puede devolver el mismo documento varias veces. Al eliminar la colección también borras el índice de búsqueda vectorial, por lo que debes volver a ejecutar create_vector_index.py cada vez que ejecutes de nuevo generate_embeddings.py.
Ejecuta el script:
python generate_embeddings.py
Verás una salida similar a esta en la terminal:
<All keys matched successfully>
Inserted 3 documents with embeddings.
Ahora cada documento en MongoDB contiene tanto el texto original como su vector de 768 dimensiones en el campo embedding. El vector convive con tus datos en el mismo documento, por lo que no necesitas almacenamiento o consultas adicionales en el momento de buscar.
Crea un índice de MongoDB Vector Search
Crea un archivo llamado create_vector_index.py. Este archivo define y crea un índice de búsqueda vectorial sobre el campo embedding para que MongoDB pueda ejecutar consultas $vectorSearch en tu colección.
La definición del índice requiere tres campos:
path: el campo que contiene los embeddings (embeddingen este tutorial)numDimensions: debe coincidir con el tamaño de salida del modelo (768paranomic-embed-text-v1)similarity: la función de comparación (cosinees la adecuada para este modelo)
El valor de numDimensions debe coincidir exactamente con tu modelo de embeddings. Si no, la creación del índice fallará:
from pymongo.mongo_client import MongoClient
from pymongo.operations import SearchIndexModel
import time
# Sustituye el marcador por tu cadena de conexión de Atlas:
mongodb_client = MongoClient(
"mongodb+srv://<USERNAME>:<PASSWORD>@<HOST>/",
appname="devrel-tutorial-python-semantic-search"
)
# Señala la misma base de datos y colección que usaste en el paso anterior:
database = mongodb_client["sample_db"]
collection = database["documents"]
# Define el índice de búsqueda vectorial.
# Los tres campos requeridos indican a MongoDB qué indexar y cómo comparar vectores:
search_index_model = SearchIndexModel(
definition={
"fields": [
{
"type": "vector",
"path": "embedding", # El nombre del campo de generate_embeddings.py
"numDimensions": 768, # nomic-embed-text-v1 siempre produce 768 dimensiones
"similarity": "cosine" # Función de similitud recomendada para este modelo
}
]
},
name="vector_index",
type="vectorSearch"
)
result = collection.create_search_index(model=search_index_model)
print("New search index named " + result + " is building.")
# Consulta cada cinco segundos hasta que el índice esté listo para aceptar consultas:
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()
Ejecuta el script:
python create_vector_index.py
Verás una salida similar a esta en la 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.
El índice puede tardar hasta un minuto en construirse. El bucle de sondeo comprueba cada 5 segundos y solo sale cuando MongoDB confirma que el índice está listo para consultar. No pases al siguiente paso hasta que veas "vector_index is ready for querying."
Convierte tu consulta de búsqueda en un vector
Crea un archivo llamado generate_query_vector.py. Este archivo importa la función get_query_embedding() de embedding_utils.py. Ejecuta generate_query_vector.py para comprobar que el modelo de embeddings carga correctamente y produce un vector de 768 dimensiones:
- Define la función
get_query_embedding()que se importará en el siguiente paso. - Puedes ejecutarlo por separado para verificar que el modelo funciona correctamente.
Debes usar el mismo modelo aquí que en el primer paso. Un modelo distinto produce vectores en un espacio numérico diferente, lo que hace que la comparación no tenga sentido:
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]}")
Ejecuta el script:
python generate_query_vector.py
Obtendrás una salida parecida a esta:
<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]En la salida anterior,
Vector dimensions: 768confirma que la salida del modelo coincide con tus embeddings almacenados.Ejecuta una consulta
$vectorSearchCrea un archivo llamado
run_vector_search.py. Este archivo importa la funciónget_query_embedding()deembedding_utils.pyy convierte el término de búsqueda de un usuario en un vector.run_vector_search.pyejecuta una canalización de agregación$vectorSearchy muestra los resultados ordenados por similitud semántica:from embedding_utils import get_query_embedding from pymongo import MongoClient # Sustituye el marcador por tu cadena de conexión de Atlas: mongodb_client = MongoClient( "mongodb+srv://<USERNAME>:<PASSWORD>@<HOST>/", appname="devrel-tutorial-python-semantic-search" ) collection = mongodb_client["sample_db"]["documents"] # Genera un vector de consulta a partir de la búsqueda del usuario: user_query = "I need an automated, scalable system for serious information storage" query_vector = get_query_embedding(user_query) # Define la canalización de agregación $vectorSearch: pipeline = [ { "$vectorSearch": { "index": "vector_index", # El índice creado en create_vector_index.py "path": "embedding", # El campo que contiene tus vectores almacenados "queryVector": query_vector, # El vector generado a partir de la consulta del usuario "numCandidates": 150, # Cuántos vecinos considera MongoDB "limit": 3 # Cuántos resultados devolver } }, { "$project": { "_id": 0, "title": 1, "text": 1, "score": { "$meta": "vectorSearchScore" # Puntuación de relevancia de cada resultado } } } ] 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()En el código anterior, dos parámetros controlan el comportamiento de la búsqueda:
numCandidatesestablece el tamaño del conjunto inicial que MongoDB examina antes de reducir a los resultados finales. Un valor mayor mejora el recall pero tarda algo más.limitdefine cuántos resultados recibes. En este tutorial,limites 3 ynumCandidateses 150. Un buen punto de partida es fijarnumCandidatesentre 10 y 15 veces tulimit.
Ahora ejecuta el script:
python run_vector_search.py
Verás una salida similar a esta:
<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" obtiene la puntuación más alta aunque la consulta "I need an automated, scalable system for serious information storage" no comparta palabras con "MongoDB Atlas is a fully managed cloud database." Se devuelve porque sus vectores son cercanos en significado: "...un sistema automatizado y escalable para almacenar información" se corresponde semánticamente con "...una base de datos en la nube totalmente gestionada". Una búsqueda de texto con la misma consulta devolvería cero resultados porque ninguna palabra exacta aparece en los documentos. Así es como debe funcionar la búsqueda semántica.
Puntos clave
- MongoDB Vector Search funciona en un clúster M0 de nivel gratuito. No necesitas un plan de pago.
- Debes usar el mismo modelo de embeddings para generar los embeddings almacenados y los vectores de consulta. Mezclar modelos produce resultados sin sentido.
- El valor
numDimensionsdel índice debe coincidir exactamente con el tamaño de salida de tu modelo.nomic-embed-text-v1siempre produce 768 dimensiones. input_type="document"optimiza los embeddings para almacenamiento.input_type="query"los optimiza para recuperación. Usa el tipo correcto en cada etapa.numCandidatescontrola el tamaño de la red de búsqueda que lanza MongoDB antes de reducir a los resultados finales definidos porlimit. Un valor mayor mejora el recall a costa del tiempo de consulta.vectorSearchScoreordena los resultados por similitud semántica. No hace falta que contengan palabras exactas de la consulta. Los rangos de puntuación varían según el modelo y el conjunto de datos, pero estos límites son un buen punto de partida paranomic-embed-text-v1con similitud coseno:- 0,9 o más: significado casi idéntico. Documento y consulta son prácticamente lo mismo.
- 0,7 a 0,9: alta relevancia. El documento se alinea claramente con la intención de la consulta.
- 0,5 a 0,7: relevancia moderada. El documento está relacionado, pero con otro enfoque o contexto.
- Por debajo de 0,5: baja relevancia. La relación es débil y el resultado puede no ser útil.
Puedes encontrar todo el código del tutorial en el repositorio de GitHub.
Para saber más
- Descripción general de MongoDB Vector Search cubre todas sus capacidades, incluido filtrado y cuantización.
- Cómo realizar búsquedas híbridas te muestra cómo combinar búsqueda vectorial y búsqueda de texto en una sola consulta.
- Cómo crear embeddings vectoriales explica cómo generar embeddings para datos de texto en tus colecciones usando modelos de Voyage AI, OpenAI y otros proveedores open source.
- Retrieval-Augmented Generation (RAG) con MongoDB te muestra cómo usar la búsqueda semántica como capa de recuperación en una aplicación de generación aumentada con recuperación.
Preguntas frecuentes
¿Necesito un plan de pago de Atlas para implementar búsqueda semántica?
No. Los cuatro pasos de este tutorial se ejecutan en un clúster M0 de nivel gratuito, que no tiene coste.
¿Qué pasa si uso un modelo de embeddings distinto para mi consulta que para mis documentos almacenados?
Tus resultados no tendrán sentido. Los vectores no serán comparables porque cada modelo mapea el texto a espacios numéricos distintos. Usa siempre el mismo modelo para indexar y para consultar.
¿Cuál es la diferencia entre `numCandidates` y `limit`?
numCandidates es la cantidad de vectores que MongoDB examina durante la búsqueda. limit es cuántos de los mejores resultados devuelve. Un numCandidates más alto mejora la calidad a costa de un ligero aumento en el tiempo de consulta. Como punto de partida, establece numCandidates entre 10 y 15 veces tu limit.
¿Puedo usar búsqueda semántica y búsqueda de texto a la vez?
Sí. MongoDB admite búsqueda híbrida, que combina $vectorSearch y $search en una única canalización.
¿La búsqueda semántica funciona en otros idiomas además del inglés?
Depende de tu modelo de embeddings. nomic-embed-text-v1 está entrenado principalmente en inglés. Para casos multilingües, elige un modelo multilingüe entrenado en los idiomas que contenga tu información.
