С поиском есть довольно очевидная проблема, которую долгое время приходилось решать разными обходными путями. Допустим, в базе лежит документ с названием «Правила возврата и отмены заказа», а пользователь пишет в поиске: «как вернуть деньги за покупку». Человеку понятно, что речь примерно об одном и том же. Для обычного поиска ситуация уже не настолько очевидная: слова «вернуть деньги» и «правила возврата» похожи для нас по смыслу, но буквально совпадают довольно слабо.
Можно улучшать токенизацию, добавлять синонимы, stemming, словари и другие механизмы, которыми полнотекстовый поиск занимается уже много лет. Всё это работает и никуда не исчезло. Но с появлением embedding-моделей появился другой подход: попробовать представить текст так, чтобы похожесть можно было считать математически.
Вместо того чтобы сравнивать сами слова, мы сначала превращаем текст в набор чисел:
"как вернуть деньги за покупку"
↓
[0.018, -0.342, 0.721, 0.114, ...]
Такой набор чисел называется embedding, а поиск по похожим embeddings — vector search.
На первый взгляд выглядит почти как фокус. Мы взяли предложение, превратили его в несколько сотен или тысяч чисел, после чего каким-то образом можем определить, что оно похоже на другое предложение. На практике никакой особой магии здесь нет, хотя за самим обучением embedding-моделей, конечно, стоит довольно много математики.
Для использования vector search достаточно понять более простую вещь: embedding model обучена размещать объекты в многомерном пространстве так, чтобы интересующий нас тип сходства отражался их взаимным расположением. Похожие объекты оказываются ближе, непохожие — дальше. Pinecone именно так описывает основную практическую ценность embeddings: представление позволяет свести сходство сложных объектов к расстоянию между точками в vector space.
Дальше задача поиска становится неожиданно привычной.
Как текст превращается в vector
Для разработчика vector проще всего представить как обычный массив чисел:
[
0.018,
-0.342,
0.721,
0.114,
-0.027,
...
]
Количество чисел называют dimensionality, или размерностью embedding. У разных моделей она может отличаться: это могут быть сотни или тысячи dimensions.
Здесь легко представить неправильную картину, будто каждая dimension обозначает какое-то понятное человеку свойство. Например первая отвечает за «животное», вторая за «цвет», третья за «положительную эмоцию». Для современных dense embeddings это обычно не так. Представление распределено сразу между большим количеством dimensions, и отдельное число само по себе практически ничего полезного человеку не говорит.
Смысл появляется в отношениях между vectors.
Допустим, embedding model получила три предложения:
A: Как вернуть деньги за подписку?
B: Хочу отменить платный тариф и получить возврат.
C: Как приготовить пасту карбонара?
После преобразования получим три vectors:
A → [ ... ]
B → [ ... ]
C → [ ... ]
Если модель хорошо подходит для нашей задачи, vectors A и B будут находиться ближе друг к другу, чем A и C. Причём в исходном тексте A и B могут почти не иметь одинаковых слов.
Именно это делает embeddings интересными для поиска.
Важно только не делать отсюда слишком сильный вывод вроде «embedding хранит смысл текста». Это удобное упрощение, но не совсем точное. Embedding model строит representation в соответствии с тем, чему она была обучена. Близость vectors показывает сходство в пространстве, которое сформировала конкретная модель, а не какую-то универсальную математическую меру смысла.
Поэтому выбор embedding model тоже влияет на поиск. Модель, обученная работать с общими текстами, может хорошо понимать обычный язык, но хуже различать специфические термины внутри узкого медицинского или юридического домена. С другим обучением распределение объектов в vector space тоже может получиться другим.
Vector space и расстояние между объектами
Чтобы было проще представить происходящее, можно сначала забыть о тысяче dimensions и взять обычную плоскость.
Допустим, наши embeddings состояли бы всего из двух чисел:
A = [1.2, 3.1]
B = [1.4, 3.0]
C = [8.7, 1.1]
Их можно буквально нарисовать точками.
A и B находятся рядом, C далеко. Значит, по принятой нами метрике A и B похожи сильнее.
В реальной embedding model измерений гораздо больше и визуализировать такое пространство напрямую уже невозможно, но принцип остаётся тем же. Мы берём query vector и ищем точки, которые находятся к нему ближе остальных.
Для определения близости существует несколько популярных способов. Чаще всего встречаются:
- cosine similarity;
- dot product;
- Euclidean distance.
Google, например, поддерживает все три как стандартные варианты vector similarity и отдельно рекомендует учитывать то, каким способом сама embedding model была обучена сравнивать representations.
Самый известный вариант — cosine similarity. Формула выглядит так:
cosine_similarity(A, B) =
(A · B) / (||A|| × ||B||)
Если не хочется вспоминать линейную алгебру, для понимания vector search формулу можно почти сразу забыть. Нас интересует угол между vectors: чем ближе их направления, тем выше similarity.
Euclidean distance работает более интуитивно и считает обычное расстояние между точками в пространстве. Dot product использует скалярное произведение и особенно удобен для нормализованных embeddings.
Какую метрику использовать, обычно определяет сама модель или документация к ней. Здесь лучше не выбирать cosine просто потому, что он чаще встречается в статьях.
Как работает vector search
Теперь можно собрать весь процесс целиком.
Представим, что у нас есть база из десяти тысяч документов. Сначала каждый документ нужно пропустить через embedding model:
Документ 1
↓
Embedding model
↓
Vector 1
Документ 2
↓
Embedding model
↓
Vector 2
...
Документ 10000
↓
Embedding model
↓
Vector 10000
Vectors сохраняются вместе с идентификаторами исходных документов. Обычно рядом хранится и metadata: категория, дата, пользователь, язык, права доступа или любые другие поля, по которым потом может понадобиться фильтрация.
После этого пользователь вводит запрос:
как вернуть деньги за подписку
Его нужно пропустить через ту же embedding model:
Query
↓
Embedding model
↓
Query vector
Теперь у нас есть один новый vector и тысячи vectors документов. Остаётся найти несколько ближайших.
Query vector
↓
similarity search
↓
Vector 182
Vector 914
Vector 72
↓
исходные документы
Вот, собственно, весь vector search на концептуальном уровне. Модель занимается representation, а поисковая система занимается поиском ближайших representations.
Это разделение мне кажется важным, потому что vector database сама по себе ничего не «понимает». Она не знает, что документ про возврат денег похож на вопрос пользователя. Эту связь заранее закодировала embedding model. База получает числа и очень быстро отвечает на гораздо более прозаичный вопрос: какие из сохранённых массивов чисел ближе всего к этому массиву?
Почему нельзя просто сравнить vector со всей базой
Для десяти тысяч записей можно действительно взять query vector, посчитать расстояние до каждого документа, отсортировать результат и выбрать десять ближайших.
Такой поиск называется exact nearest neighbor search, или обычный KNN.
Проблема появляется с ростом базы.
Если vectors миллион, для каждого запроса нужно сравнить query примерно с миллионом точек. Если десять миллионов — уже с десятью миллионами. Сами операции достаточно простые, но размерность embeddings тоже может быть большой, поэтому стоимость полного scan постепенно становится неприятной.
Это очень похоже на обычную базу данных без индекса. SQL-запрос:
SELECT *
FROM users
WHERE email = 'user@example.com';
можно выполнить полным перебором таблицы. Результат будет правильным. Просто на достаточно большой таблице хотелось бы не читать каждую строку.
С vectors появляется похожая потребность в индексе.
Google прямо разделяет exact KNN и Approximate Nearest Neighbor search: точный вариант сравнивает кандидатов напрямую и сохраняет максимальную точность, а ANN специально жертвует небольшой частью recall ради гораздо более быстрого поиска на больших наборах vectors.
Получается типичный инженерный компромисс:
Exact search
медленнее
точнее
Approximate search
быстрее
может пропустить часть идеальных соседей
Для небольшой базы ANN вообще может оказаться не нужен. Google даже отдельно рекомендует использовать обычный KNN там, где после фильтрации данных остаётся достаточно мало и latency устраивает: индекс тоже имеет свою стоимость создания, хранения и обслуживания.
Что такое ANN и при чём здесь HNSW
Approximate Nearest Neighbor — это не один конкретный алгоритм, а целый класс подходов, которые позволяют не сравнивать query со всеми vectors.
Один из самых распространённых сегодня индексов называется HNSW — Hierarchical Navigable Small World. Его используют, например, Qdrant и многие другие vector search системы.
Если очень грубо, HNSW строит поверх vectors граф. Каждая точка связана с несколькими другими точками, а поиск двигается по этим связям, постепенно приближаясь к нужной области vector space. В верхних слоях графа можно быстро перемещаться большими шагами, а ниже поиск становится более точным.
Мне нравится сравнивать это с поиском адреса в городе. Можно проверять каждый дом по очереди, начиная с первого. Результат получится абсолютно точный, но способ довольно странный. Гораздо проще сначала попасть в нужный район, потом на нужную улицу и только после этого искать конкретный дом.
HNSW делает что-то похожее с vector space.
Конечно, реальный алгоритм сложнее этой аналогии. У него есть параметры, влияющие на количество связей, качество индекса, скорость построения и recall. Qdrant, например, позволяет отдельно настраивать HNSW и даже переключаться между full scan и индексированным поиском в зависимости от размера выборки.
Для вводной статьи подробности алгоритма, на мой взгляд, уже лишние. Важнее понять сам принцип: ANN index пытается быстро привести нас в перспективную часть vector space, не проверяя каждую точку базы.
Что тогда делает vector database
После знакомства с embeddings иногда возникает впечатление, будто vector database — какая-то совершенно новая разновидность хранилища, которая понимает искусственный интеллект.
На практике её задача довольно знакомая.
Она должна хранить:
id
vector
payload / metadata
и эффективно выполнять запрос вроде:
Найди десять vectors, которые ближе всего к этому query vector, но только среди документов пользователя 42 и только за последний год.
То есть кроме vector similarity быстро появляются обычные задачи базы данных: filtering, updates, deletes, distributed storage, replication, persistence и всё остальное.
Vector index нужен для ускорения поиска соседей, payload — чтобы после поиска понимать, чему эти vectors вообще соответствуют, а filters позволяют не искать по всему пространству, если нас интересует только определённая часть данных.
И здесь становится понятно, почему vector support постепенно появился не только в специализированных решениях вроде Qdrant, Pinecone или Weaviate, но и в PostgreSQL, Elasticsearch, MongoDB, Spanner и других привычных системах. Сам по себе vector — просто ещё один тип данных. Специфика начинается в индексации и similarity search.
Поэтому вопрос «нужна ли мне отдельная vector database?» я бы никогда не решал автоматически. Если vectors являются небольшой частью существующей PostgreSQL-системы, отдельное хранилище может только усложнить архитектуру. Если же similarity search становится центральной частью продукта, объём данных большой, нужны сложные filters и высокая скорость ANN, специализированная система уже может иметь смысл.
Vector search и обычный поиск решают разные задачи
На фоне AI иногда создаётся ощущение, что vector search — просто новая улучшенная версия Elasticsearch или полнотекстового поиска. Это довольно опасное упрощение.
Возьмём запрос:
ERR_CONNECTION_RESET
Пользователь почти наверняка хочет документ, где буквально встречается ERR_CONNECTION_RESET.
Или:
SKU-89431
Здесь нам нужен конкретный артикул, а не товар, который семантически похож на него.
Или:
PHP 8.2.17
Точная версия имеет значение. Поиск документа про PHP 8.3, который embedding model посчитала очень похожим по смыслу, может оказаться вообще нежелательным.
Для таких задач lexical search отлично подходит.
Теперь другой запрос:
как вернуть деньги за подписку
а документ называется:
Cancellation and refund policy
Точных совпадений мало, но смысл практически тот же. Здесь vector search становится гораздо интереснее.
Elastic приводит аналогичный пример с vacation rules и документом annual leave policy: keyword search может пропустить документ из-за отсутствия тех же слов, а semantic/vector retrieval способен поднять его по similarity embeddings.
Поэтому я бы разделял:
Keyword search
→ что написано
и:
Vector search
→ на что похоже
Разумеется, реальный full-text search сложнее буквального совпадения строк. BM25 учитывает частоты терминов, анализаторы нормализуют текст, можно использовать synonyms и другие механизмы. Но фундаментальная разница всё равно остаётся: lexical retrieval работает с текстовыми признаками, vector retrieval — с learned representation.
Почему всё чаще используют Hybrid Search
Когда один подход хорошо решает одни запросы, а второй — другие, довольно естественно попробовать использовать оба.
Так появляется hybrid search.
┌→ lexical search ─┐
Query ────────┤ ├→ merge → ranking
└→ vector search ─┘
Например пользователь ищет:
ошибки авторизации OAuth refresh token
Lexical search хорошо поднимет документы, где встречаются конкретные OAuth и refresh token. Vector search может найти материалы про renewal access credentials, даже если точная терминология немного отличается.
Дальше результаты двух поисков нужно объединить в один ranking. Для этого существуют разные способы fusion. Elastic, например, рекомендует Reciprocal Rank Fusion: отдельно строятся rankings lexical и vector search, после чего позиции документов объединяются в один итоговый список.
В production такой подход выглядит мне намного здравее лозунга «теперь ищем только по смыслу». Exact terms никуда не исчезли. Люди по-прежнему ищут номера заказов, названия классов, ошибки, артикулы, версии и имена. Semantic retrieval просто добавляет ещё один сильный signal.
Google показывает ту же идею на поиске товаров: full-text retrieval точно находит конкретный model number, а vector search помогает подобрать похожие по описанию товары. Вместе два сигнала закрывают разные типы запроса.
При чём здесь RAG
Vector search особенно часто всплывает сейчас рядом с RAG, хотя сам подход появился не из-за LLM.
В простом RAG pipeline происходит примерно следующее:
Документы
↓
chunks
↓
embeddings
↓
vector database
Когда пользователь задаёт вопрос:
Question
↓
embedding
↓
vector search
↓
relevant chunks
↓
LLM context
↓
answer
То есть vector search используется как retrieval layer. Он пытается найти куски документов, которые семантически ближе всего к вопросу, после чего найденный текст передаётся LLM.
Здесь хорошо видно ещё одно ограничение: качество RAG зависит не только от самой языковой модели. Если retrieval нашёл неправильные документы, LLM получает неправильный контекст. Можно сколько угодно улучшать prompt, но ответ будет строиться из того, что система смогла найти.
И наоборот, хороший vector search ещё не гарантирует хороший RAG. Нужно правильно разбивать документы на chunks, выбирать embedding model, учитывать filters, иногда добавлять hybrid search и reranking, контролировать количество найденных фрагментов и уже потом думать о generation.
Получается ровно та же мысль, к которой я пришёл в статье про Context Engineering: модель видит только ту часть мира, которую наше приложение успело перед ней собрать.
Vector search — один из способов выбрать эту часть.
Embeddings используются далеко не только для текста
Мы всё время говорили про документы, потому что это сейчас самый заметный сценарий из-за RAG. Но сама идея embeddings гораздо шире.
В vector можно представить изображение. Тогда по фотографии кроссовок можно искать визуально похожие товары. Можно представить аудио и искать похожие звуки. Можно построить embeddings пользователей или товаров для recommendation system, после чего искать объекты с близкими representations.
Pinecone приводит среди типичных применений embeddings similarity search, recommendations, clustering, classification, anomaly detection, deduplication и reverse image search.
Особенно интересны multimodal embedding models, которые умеют помещать разные типы данных в общее пространство. Например текстовый запрос:
красная машина на заснеженной дороге
может быть превращён в vector, который затем сравнивается с embeddings изображений.
В этот момент vector search вообще перестаёт быть «поиском по тексту». Он становится достаточно универсальным способом искать похожие объекты, если мы умеем получить для них подходящее representation.
Что vector search не умеет
После первого знакомства embeddings действительно производят сильное впечатление. Берёшь запрос, который вообще не содержит слов из документа, и поиск всё равно его находит. Очень легко после этого решить, что классический search больше особо не нужен.
Потом начинается реальная работа.
Embedding model может плохо понимать конкретный домен. Similarity ещё не означает relevance для бизнес-задачи. Два текста могут быть очень похожи тематически, но один из них отвечать на совершенно другой вопрос. ANN index может иногда пропустить настоящий ближайший vector. Filters могут сильно менять поведение индекса. Большие документы приходится резать на chunks, а выбор размера chunk сам по себе влияет на retrieval.
Есть ещё проблема свежести. Embedding — это representation текста в момент индексации. Если документ изменился, vector тоже нужно пересчитать. Если мы сменили embedding model, смешивать старые и новые embeddings в одном пространстве обычно нельзя: новая модель создаёт другое representation, и данные приходится переиндексировать.
Отдельно стоит помнить о distance score. Число вроде:
0.83
само по себе не означает «83% вероятности, что документ правильный». Это similarity или distance в конкретном embedding space. Его интерпретация зависит от модели, метрики и данных, поэтому универсального порога «всё выше 0.8 релевантно» обычно не существует.
В общем, vector search не отменяет обычную инженерную работу с качеством поиска. Он просто добавляет новый очень сильный сигнал.
Как я теперь представляю поиск по смыслу
Когда впервые сталкиваешься с vector databases, всю систему довольно легко воспринимать как одну большую AI-технологию. Embeddings, semantic search, ANN, HNSW, RAG — терминов много, и они быстро слипаются.
Мне проще разделить всё на три независимые задачи.
Первая — representation. Embedding model должна превратить документ и запрос в пространство, где полезный для нашей задачи тип сходства выражается близостью vectors.
Вторая — retrieval. Search engine или vector database должна быстро найти ближайшие точки. Если данных мало, можно использовать exact KNN. Если много — появляется ANN и индексы вроде HNSW.
Третья — ranking. Нужно решить, действительно ли ближайшие vectors являются лучшими результатами для пользователя. Здесь появляются filters, lexical search, hybrid retrieval, reranking и бизнес-правила.
Representation
Embedding model
↓
Vector space
↓
Retrieval
KNN / ANN
↓
Ranking
Hybrid / reranking / rules
↓
Result
Мне кажется, это разделение снимает большую часть магии вокруг vector search.
Vector database не понимает текст. HNSW не знает, что такое возврат денег. Cosine similarity ничего не знает о намерении пользователя.
Embedding model сначала строит пространство, в котором нужный нам тип сходства выражается числами. После этого database делает то, что базы данных обычно умеют делать лучше всего: быстро ищет нужные записи.
Наверное, поэтому vector search и оказался настолько полезным рядом с LLM. Языковые модели дали нам хороший интерфейс для работы с неструктурированными данными, а embeddings — достаточно удобный способ находить среди этих данных нужную часть.
И в итоге «поиск по смыслу» оказывается не какой-то отдельной магической способностью AI, а вполне понятным pipeline из модели, vectors, индекса и хорошего старого ranking.