Это как раз те случаи, когда обычный полнотекстовый поиск не справляется. На практике это выглядит так:
- Портал поддержки не находит результатов, если пользователь вводит «не включается» вместо точной фразы из справочной статьи.
- Платформа e-commerce ничего не выдаёт по запросу «что-нибудь тёплое на зиму», потому что ни в одном описании товара нет этих точных слов.
- База знаний пропускает нужный документ, если пользователь сформулировал вопрос иначе, чем в тексте документа.
Семантический поиск решает эту проблему. Он понимает смысл и намерение запроса, а не просто сопоставляет точные слова.
В этом руководстве показано, как реализовать семантический поиск в MongoDB с помощью Python и бесплатной модели эмбеддингов nomic-embed-text-v1.
Примечание: nomic-embed-text-v1 занимает примерно 0,27 ГБ в оперативной памяти, поэтому перед запуском скриптов из этого руководства убедитесь, что на вашей машине доступно не менее 1 ГБ ОЗУ. При первом запуске модель загружается с Hugging Face, поэтому учтите дополнительное время в зависимости от скорости интернета. Все последующие запуски используют модель из локального кэша. Инференс на CPU значительно медленнее, чем на GPU. На машинах без выделенного GPU модель работает на CPU и использует оперативную память вместо видеопамяти, поэтому генерация эмбеддингов может занять больше времени.
В этом руководстве вы узнаете:
- Что такое векторные эмбеддинги и как они представляют смысл
- Как генерировать и хранить эмбеддинги в MongoDB
- Как создать индекс MongoDB Vector Search
- Как преобразовать пользовательский запрос в вектор
- Как запустить конвейер агрегирования
$vectorSearchи интерпретировать результаты
Все примеры кода к этому руководству доступны в репозитории GitHub.
Текстовый поиск и семантический поиск
Текстовый поиск находит документы, где встречаются точные слова из вашего запроса. Семантический поиск находит документы, которые передают тот же смысл, что и запрос, даже если используются совсем другие слова.
В таблице ниже показано, что вернёт каждый подход по запросу «heart problems»:
| Текст документа | Текстовый поиск | Семантический поиск | Почему текстовый поиск это возвращает |
|---|---|---|---|
| "...heart problems in adults..." | Да | Да | Содержит точные слова "heart problems" |
| "...cardiac conditions and symptoms..." | Нет | Да | Не содержит точные слова "heart problems" |
| "...risk factors for heart disease..." | Нет | Да | Не содержит точные слова "heart problems" |
| "...chest pain and shortness of breath..." | Нет | Да | Не содержит точные слова "heart problems" |
Ключевые понятия семантического поиска
Три понятия лежат в основе семантического поиска в MongoDB. Они помогут понять, почему каждый шаг в этом руководстве работает именно так.
Векторные эмбеддинги
Модель эмбеддингов преобразует текст в список чисел фиксированной длины — вектор. Каждое число в векторе отражает одно из измерений смысла.
Два текста с похожим смыслом дают векторы, которые численно близки друг к другу. «cardiac conditions» и «heart problems» оказываются рядом в векторном пространстве, хотя у них нет общих слов. Эта близость и делает возможным семантический поиск.
В этом руководстве используется модель эмбеддингов nomic-embed-text-v1, которая бесплатна, с открытым исходным кодом и полностью запускается локально на вашей машине. При первом запуске скрипта модель автоматически загружается с Hugging Face и сохраняется локально для последующих запусков. Модель выдаёт 768 чисел на каждый ввод.
Индексы векторного поиска
Индекс векторного поиска указывает MongoDB, в каком поле хранятся эмбеддинги, сколько ожидается измерений и какую функцию сходства использовать для сравнения. Этот индекс нужно создать до выполнения любых запросов $vectorSearch.
Этап агрегирования $vectorSearch
$vectorSearch — это этап агрегирования, который выполняет поиск. Он принимает вектор запроса, ищет по проиндексированному полю и возвращает документы, отсортированные по семантической близости. Вы можете объединять его с $project и другими этапами, как и любой другой конвейер агрегирования.
Приступим.
Предварительные требования
Перед началом убедитесь, что у вас есть следующее:
- Установлен Python 3.8 или новее
- Учётная запись MongoDB Atlas с кластером M0 (бесплатный тариф)
pymongoверсии 4.7 или новее, а также пакеты Pythonsentence-transformersиeinops, установленные в системе- Базовые знания Python и коллекций MongoDB
- Строка подключения Atlas, доступная в интерфейсе Atlas в разделе **Database > Connect > Drivers**
- Ваш IP-адрес добавлен в список разрешённых в Atlas до запуска скриптов. Перейдите в **Security > Network Access** в интерфейсе Atlas и добавьте текущий IP-адрес.
Подготовка проекта
Создайте папку для проекта и перейдите в неё в терминале:
mkdir mongodb-semantic-search
cd mongodb-semantic-search
Создайте и активируйте виртуальное окружение, чтобы изолировать зависимости проекта:
python -m venv venv
source venv/bin/activate
В Windows активируйте виртуальное окружение командой:
venv\Scripts\activate
Теперь установите необходимые пакеты:
pip install pymongo sentence-transformers einops
Для каждого шага этого руководства вы создадите отдельный файл Python. Все файлы помещаются в папку mongodb-semantic-search.
Создание файла вспомогательных функций для эмбеддингов
Создайте файл с именем embedding_utils.py. В этом файле находятся обе функции для работы с эмбеддингами, используемые в руководстве:
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()
Предупреждение: trust_remote_code=True позволяет модели выполнять специфичный для неё код Python, загружаемый с Hugging Face на вашу машину. Если исходный код модели на Hugging Face будет скомпрометирован или изменён с вредоносными целями, этот код автоматически выполнится в вашей среде. Будьте осторожны при использовании этого параметра в продакшене.
Генерация и сохранение векторных эмбеддингов
Создайте файл generate_embeddings.py. В нём импортируется функция эмбеддинга из embedding_utils.py, для поля text каждого документа генерируется вектор, и полный документ, включая эмбеддинг, сохраняется в 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()
В приведённом выше коде collection.drop() удаляет все документы и индексы коллекции перед каждым запуском. Если повторно запустить generate_embeddings.py без этой строки, будут вставлены дубликаты документов, из‑за чего $vectorSearch вернёт один и тот же документ несколько раз. Удаление коллекции также удаляет индекс векторного поиска, поэтому каждый раз после запуска generate_embeddings.py нужно заново запускать create_vector_index.py.
Запустите скрипт:
python generate_embeddings.py
В терминале вы увидите примерно такой вывод:
<All keys matched successfully>
Inserted 3 documents with embeddings.
Теперь каждый документ в MongoDB содержит исходный текст и его 768-мерный вектор в поле embedding. Вектор хранится рядом с вашими данными в том же документе, поэтому при выполнении запроса не требуется отдельное хранилище или дополнительный поиск.
Создайте индекс MongoDB Vector Search
Создайте файл create_vector_index.py. В нём определяется и создаётся индекс векторного поиска по полю embedding, чтобы MongoDB мог выполнять запросы $vectorSearch к вашей коллекции.
Определение индекса требует трёх полей:
path: поле, где хранятся эмбеддинги (в этом руководстве —embedding)numDimensions: должен совпадать с размерностью выхода модели (768дляnomic-embed-text-v1)similarity: функция сравнения (для этой модели подходитcosine)
Значение numDimensions должно в точности соответствовать вашей модели эмбеддингов. Несоответствие приведёт к ошибке создания индекса:
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()
Запустите скрипт:
python create_vector_index.py
В терминале вы увидите примерно такой вывод:
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.
Построение индекса может занять до минуты. Цикл опроса проверяет готовность каждые 5 секунд и завершает работу только после подтверждения MongoDB, что индекс доступен для запросов. Не переходите к следующему шагу, пока не увидите «vector_index is ready for querying.»
Преобразуйте поисковый запрос в вектор
Создайте файл generate_query_vector.py. Этот файл импортирует функцию get_query_embedding() из embedding_utils.py. Запустите generate_query_vector.py, чтобы убедиться, что модель эмбеддингов корректно загружается и выдаёт 768-мерный вектор:
- Он определяет функцию
get_query_embedding(), которую импортирует следующий шаг. - Его можно запустить отдельно, чтобы проверить работоспособность модели.
Здесь необходимо использовать ту же модель, что и на первом шаге. Другая модель создаёт векторы в ином числовом пространстве, поэтому сравнение теряет смысл:
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]}")
Запустите скрипт:
python generate_query_vector.py
Вы увидите примерно такой вывод:
<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]
В этом выводе строка Vector dimensions: 768 подтверждает, что размерность результата модели совпадает с вашими сохранёнными эмбеддингами.
Выполните запрос $vectorSearch
Создайте файл run_vector_search.py. В нём импортируется функция get_query_embedding() из embedding_utils.py и пользовательский поисковый запрос преобразуется в вектор. run_vector_search.py выполняет конвейер агрегирования $vectorSearch и выводит результаты, отсортированные по семантической близости:
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()
В приведённом коде два параметра управляют поведением поиска:
numCandidatesзадаёт размер начального пула, который MongoDB просматривает перед сужением до финальных результатов. Большее значение повышает полноту, но немного увеличивает время выполнения.limitопределяет, сколько результатов вы получите. В этом руководствеlimitустановлен равным 3, а numCandidates — 150. Часто рекомендуют начинать сnumCandidates, равного 10–15 значенияlimit.
Теперь запустите скрипт:
python run_vector_search.py
В терминале вы увидите примерно такой вывод:
<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» получает наивысший балл, хотя запрос «I need an automated, scalable system for serious information storage» не содержит ни одного общего слова с «MongoDB Atlas is a fully managed cloud database.» Результат возвращается, потому что их векторы близки по смыслу: «...автоматизированная, масштабируемая система для хранения информации» семантически соответствует «...полностью управляемой облачной базе данных». Текстовый поиск по тому же запросу вернул бы ноль результатов, так как ни одно точное слово из запроса не встречается ни в одном документе. Так и должен работать семантический поиск.
Итоги
- MongoDB Vector Search работает на кластере M0 (бесплатный тариф). Платный план не требуется.
- Нужно использовать одну и ту же модель эмбеддингов для генерации сохранённых эмбеддингов и векторов запросов. Смешение моделей приводит к бессмысленным результатам.
- Значение
numDimensionsв определении индекса должно точно соответствовать размерности выхода вашей модели эмбеддингов.nomic-embed-text-v1всегда выдаёт 768 измерений. input_type="document"оптимизирует эмбеддинги для хранения.input_type="query"оптимизирует их для поиска. Используйте корректный тип на каждом этапе.numCandidatesуправляет размером «сети» поиска, которую MongoDB забрасывает перед сужением до финального набора поlimit. Большее значение повышает полноту за счёт времени запроса.vectorSearchScoreранжирует результаты по семантической близости. Для совпадения не требуется наличие точных слов запроса. Диапазоны оценок зависят от модели и датасета, но следующие границы — полезная отправная точка дляnomic-embed-text-v1с косинусной схожестью:- 0,9 и выше: Почти идентичный смысл. Документ и запрос семантически почти совпадают.
- 0,7–0,9: Сильная релевантность. Документ явно соответствует намерению запроса.
- 0,5–0,7: Умеренная релевантность. Документ тематически связан, но использует иной контекст или формулировки.
- Ниже 0,5: Слабая релевантность. Связь неустойчивая, результат может быть бесполезным.
Все примеры кода к этому руководству доступны в репозитории GitHub.
Дополнительные материалы
- Обзор MongoDB Vector Search описывает все возможности MongoDB Vector Search, включая фильтрацию и квантизацию.
- Как выполнять гибридный поиск показывает, как объединить в одном запросе векторный поиск и полнотекстовый поиск.
- Как создавать векторные эмбеддингиобъясняет, как генерировать векторные эмбеддинги для текстовых данных в ваших коллекциях с помощью моделей от Voyage AI, OpenAI и других провайдеров open-source моделей.
- Retrieval-Augmented Generation (RAG) с MongoDB показывает, как использовать семантический поиск в качестве слоя извлечения в приложении с RAG.
Часто задаваемые вопросы
Нужен ли платный план Atlas, чтобы внедрить семантический поиск?
Нет. Все четыре шага из этого руководства выполняются на кластере M0 (бесплатный тариф), который бесплатен.
Что произойдёт, если для запроса использовать другую модель эмбеддингов, чем для сохранённых документов?
Результаты будут бессмысленными. Векторы окажутся несопоставимыми, потому что разные модели отображают текст в разные числовые пространства. Всегда используйте одну и ту же модель и для индексации, и для запросов.
В чём разница между `numCandidates` и `limit`?
numCandidates — это сколько векторов MongoDB рассматривает во время поиска. limit — это сколько лучших результатов возвращается. Более высокое значение numCandidates улучшает качество результатов ценой небольшого увеличения времени запроса. Часто рекомендуют устанавливать numCandidates в 10–15 раз больше, чем limit.
Можно ли использовать семантический поиск и текстовый поиск вместе?
Да. MongoDB поддерживает гибридный поиск, который сочетает $vectorSearch и $search в одном конвейере.
Работает ли семантический поиск для языков, кроме английского?
Зависит от модели эмбеддингов. nomic-embed-text-v1 обучена в основном на английском тексте. Для многоязычных сценариев выберите мультиязычную модель, обученную на языках, представленных в ваших данных.