Все уроки · 44
Векторные базы данных
Почему семантическому поиску нужна векторная база данных, как индексы ANN вроде HNSW быстро находят ближайших соседей, какую базу выбрать — и рабочий пример поиска по сходству.
Сначала: что такое база данных?
База данных — это программа, вся работа которой — помнить данные и отвечать на вопросы о них. Вы один раз передаёте ей миллион записей, а потом спрашиваете: «какие записи подходят под это?» — и она отвечает за миллисекунды, не перечитывая всё с нуля.
Два слова вы услышите в разговоре о любой базе данных:
- Запрос — вопрос, который вы ей задаёте. «Все заказы за март», «пользователь с этим адресом почты», «пять объектов, больше всего похожих на этот».
- Индекс — вспомогательная структура, которую база заранее строит, чтобы отвечать, не проверяя каждую запись. Как предметный указатель в конце книги: вы сразу открываете нужную страницу, а не читаете все 400.
Обычная база данных отлично справляется с точными вопросами: это значение, эта дата, это имя пользователя. Для вопроса «найди пять документов, близких по смыслу к этому абзацу» она бесполезна: в нём нет ничего точного.
Векторная база данных создана именно для такого вопроса: её записи — векторы, а запрос звучит так: «что находится ближе всего к этому?»
Проблема: ближайшие соседи при больших объёмах
Эмбеддинги решают вопрос «что значит этот текст», а координаты определяет модель, которую вы выбрали для их построения (Модели эмбеддингов). Следующая проблема — скорость. Допустим, у вас миллион документов с эмбеддингами и один вектор запроса. Какие 5 документов ближе всего к нему?
Простыми словами: «ближе всего» значит «больше всего похоже по смыслу». В этом весь фокус из урока об эмбеддингах: близкие векторы — это похожие тексты. Остаётся лишь быстро их найти.
Очевидное решение — посчитать расстояние до каждого вектора и отсортировать — называется полным перебором (brute-force search), и для нескольких тысяч векторов он годится. На миллионе он уже тормозит, а на сотне миллионов безнадёжен.
Именно эту задачу решает векторная база данных: она хранит векторы и за миллисекунды отвечает на запрос «дай K ближайших соседей этого вектора» даже при огромных объёмах. Делает она это с помощью индекса — структуры данных, которая отдаёт немного точности в обмен на большую скорость.
Как работают индексы (версия в один абзац)
Основной подход — приближённый поиск ближайших соседей (Approximate Nearest Neighbor, ANN). На практике вам встретятся два семейства индексов:
- HNSW (Hierarchical Navigable Small World) строит многослойный граф, где каждый вектор связан с несколькими соседними. Запрос начинается с верхнего слоя — там прыжки через всё пространство — и слой за слоем приближается к цели: так вы сначала едете по шоссе, потом по городским улицам, а в конце ищете точный адрес. Быстро и точно, но требует много памяти.
- IVF (Inverted File) сначала раскладывает все векторы по корзинам: стандартный алгоритм k-means (k-средних) делит их на заданное число «районов». Затем запрос просматривает лишь несколько ближайших к нему корзин вместо всей коллекции (так в базах данных называют «один именованный набор записей»). Памяти нужно меньше, точность чуть ниже, настраивать легко.
Реализовывать это не нужно
Индексы ANN — результат десятилетий исследований, и они уже реализованы за вас. Ваша задача — знать, что они существуют, понимать компромисс «точность в обмен на скорость» и выбирать разумные параметры. Вот и всё.
Пайплайн
Вот полный жизненный цикл системы векторного поиска: каждый шаг — отдельный компонент, который вы построите:
Загрузите свои тексты — обращения в поддержку, документацию продукта, статьи.
Какую выбрать: просто возьмите одну
Все варианты хороши — и поэтому выбрать сложнее, чем должно быть:
- Chroma — открытый код, ориентирована на Python, настройки не требует. Вариант по умолчанию для обучения и прототипов.
- FAISS — библиотека, а не база данных. Чистая скорость, проверена на практике в Meta, сервера нет, и готового способа сохранять данные между запусками тоже нет. Отлично подходит, когда эмбеддинги живут внутри вашего приложения.
- LanceDB — встраиваемая база: данные хранятся в локальных файлах, отдельный сервер не нужен. Простая и современная.
- Supabase pgvector — векторы прямо в Postgres. Если ваши данные уже лежат в Postgres, а масштаб умеренный, это путь наименьшего сопротивления.
- Qdrant — открытый код, высокая скорость, развитая фильтрация. Популярный вариант для размещения на собственных серверах.
- Weaviate — полнофункциональная база с открытым кодом и встроенным гибридным поиском: он сочетает старый добрый поиск по ключевым словам и векторный, поэтому одним запросом вы находите и точные артикулы товаров, и приблизительные по смыслу совпадения.
- Pinecone — полностью управляемый сервис: обслуживать ничего не нужно. Подходит, если проще заплатить, чем самому держать инфраструктуру.
Короткий ответ
Начните с Chroma (или с pgvector, если вы уже используете Postgres). Она спокойно справляется с миллионами векторов — больше, чем увидит большинство побочных проектов. Переезд потом — это скрипт пересчёта эмбеддингов и полдня работы. Над вопросом «какую векторную базу выбрать» стоит думать минуты, а не недели.
Реализация: индексация и поиск
Суть укладывается примерно в 20 строк. Первый файл — математика, которая лежит в основе: косинусное сходство, полный перебор. Второй файл — то же самое с настоящей векторной базой (Chroma): ANN-индекс заменяет цикл.
Косинусное сходство в одном предложении: представьте две стрелки, выходящие из одной точки, — одна для вашего вопроса, другая для документа. Оценка показывает, насколько они указывают в одном направлении: 1 означает «одно и то же», 0 — «никак не связаны». Длина не важна, и это именно то, что нужно: вопрос из двух слов и длинный абзац всё равно могут быть об одном и том же, а значит, могут оказаться соседями.
1import numpy as np2 3docs = {4 #98c379">"forgot password": [0.9, 0.1, 0.0],5 #98c379">"account locked": [0.85, 0.15, 0.0],6 #98c379">"refund policy": [0.0, 0.9, 0.2],7 #98c379">"double charge": [0.05, 0.85, 0.1],8 #98c379">"track my package": [0.1, 0.0, 0.9],9}10query = np.array([0.88, 0.12, 0.02])11 12for name, vec in docs.items():13 a = np.array(vec)14 score = np.dot(a, query) / (np.linalg.norm(a) * np.linalg.norm(query))15 print(name, round(score, 3))Запустите brute_force.py — и два документа об аккаунте получат самые высокие
оценки, точно как на карте смыслов из урока об эмбеддингах. with_chroma.py устроен
как код для рабочей системы: вы отдаёте базе сырые тексты (эмбеддинги она строит
сама), а запрос за миллисекунды возвращает два ближайших обращения — всю тяжёлую
работу делает индекс. Замените эти три документа своими — и у вас семантический поиск; передайте результаты LLM вместо того, чтобы
выводить их на экран, — и у вас RAG.
Настоящие эмбеддинги, игрушечный пример
В этом примере используются «игрушечные» трёхмерные векторы, чтобы математика была на виду. У настоящих эмбеддингов от 384 до 3072 измерений, но код остаётся тем же: numpy и векторным базам размер не важен.
Два практических замечания для реальной эксплуатации:
- Всегда храните исходный текст рядом с каждым вектором: он понадобится для ответа, а сам по себе вектор ничего не говорит.
- Переиндексируйте при смене модели. Векторы разных моделей несравнимы, поэтому после смены модели придётся заново посчитать эмбеддинги и перестроить индекс.
Почему бы просто не пройти циклом по всем векторам и не посчитать расстояние?
Что показывает косинусное сходство?
Вы сменили модель эмбеддингов. Что ломается?
Что нужно хранить рядом с каждым вектором?