Все уроки · 44
Путь 3 · Дорожная карта AI-инженера·Урок 13 из 33

Векторные базы данных

Почему семантическому поиску нужна векторная база данных, как индексы ANN вроде HNSW быстро находят ближайших соседей, какую базу выбрать — и рабочий пример поиска по сходству.

УровеньСреднийВремя~15 мин

Сначала: что такое база данных?

База данных — это программа, вся работа которой — помнить данные и отвечать на вопросы о них. Вы один раз передаёте ей миллион записей, а потом спрашиваете: «какие записи подходят под это?» — и она отвечает за миллисекунды, не перечитывая всё с нуля.

Два слова вы услышите в разговоре о любой базе данных:

  • Запрос — вопрос, который вы ей задаёте. «Все заказы за март», «пользователь с этим адресом почты», «пять объектов, больше всего похожих на этот».
  • Индекс — вспомогательная структура, которую база заранее строит, чтобы отвечать, не проверяя каждую запись. Как предметный указатель в конце книги: вы сразу открываете нужную страницу, а не читаете все 400.

Обычная база данных отлично справляется с точными вопросами: это значение, эта дата, это имя пользователя. Для вопроса «найди пять документов, близких по смыслу к этому абзацу» она бесполезна: в нём нет ничего точного.

Векторная база данных создана именно для такого вопроса: её записи — векторы, а запрос звучит так: «что находится ближе всего к этому?»

Проблема: ближайшие соседи при больших объёмах

Эмбеддинги решают вопрос «что значит этот текст», а координаты определяет модель, которую вы выбрали для их построения (Модели эмбеддингов). Следующая проблема — скорость. Допустим, у вас миллион документов с эмбеддингами и один вектор запроса. Какие 5 документов ближе всего к нему?

Простыми словами: «ближе всего» значит «больше всего похоже по смыслу». В этом весь фокус из урока об эмбеддингах: близкие векторы — это похожие тексты. Остаётся лишь быстро их найти.

Очевидное решение — посчитать расстояние до каждого вектора и отсортировать — называется полным перебором (brute-force search), и для нескольких тысяч векторов он годится. На миллионе он уже тормозит, а на сотне миллионов безнадёжен.

Именно эту задачу решает векторная база данных: она хранит векторы и за миллисекунды отвечает на запрос «дай K ближайших соседей этого вектора» даже при огромных объёмах. Делает она это с помощью индекса — структуры данных, которая отдаёт немного точности в обмен на большую скорость.

Как работают индексы (версия в один абзац)

Основной подход — приближённый поиск ближайших соседей (Approximate Nearest Neighbor, ANN). На практике вам встретятся два семейства индексов:

  • HNSW (Hierarchical Navigable Small World) строит многослойный граф, где каждый вектор связан с несколькими соседними. Запрос начинается с верхнего слоя — там прыжки через всё пространство — и слой за слоем приближается к цели: так вы сначала едете по шоссе, потом по городским улицам, а в конце ищете точный адрес. Быстро и точно, но требует много памяти.
  • IVF (Inverted File) сначала раскладывает все векторы по корзинам: стандартный алгоритм k-means (k-средних) делит их на заданное число «районов». Затем запрос просматривает лишь несколько ближайших к нему корзин вместо всей коллекции (так в базах данных называют «один именованный набор записей»). Памяти нужно меньше, точность чуть ниже, настраивать легко.

Реализовывать это не нужно

Индексы ANN — результат десятилетий исследований, и они уже реализованы за вас. Ваша задача — знать, что они существуют, понимать компромисс «точность в обмен на скорость» и выбирать разумные параметры. Вот и всё.

Пайплайн

Вот полный жизненный цикл системы векторного поиска: каждый шаг — отдельный компонент, который вы построите:

Step through the pipeline
→→→→→
1. Загрузка документов

Загрузите свои тексты — обращения в поддержку, документацию продукта, статьи.

out› фрагменты
1/6

Какую выбрать: просто возьмите одну

Все варианты хороши — и поэтому выбрать сложнее, чем должно быть:

  • Chroma — открытый код, ориентирована на Python, настройки не требует. Вариант по умолчанию для обучения и прототипов.
  • FAISS — библиотека, а не база данных. Чистая скорость, проверена на практике в Meta, сервера нет, и готового способа сохранять данные между запусками тоже нет. Отлично подходит, когда эмбеддинги живут внутри вашего приложения.
  • LanceDB — встраиваемая база: данные хранятся в локальных файлах, отдельный сервер не нужен. Простая и современная.
  • Supabase pgvector — векторы прямо в Postgres. Если ваши данные уже лежат в Postgres, а масштаб умеренный, это путь наименьшего сопротивления.
  • Qdrant — открытый код, высокая скорость, развитая фильтрация. Популярный вариант для размещения на собственных серверах.
  • Weaviate — полнофункциональная база с открытым кодом и встроенным гибридным поиском: он сочетает старый добрый поиск по ключевым словам и векторный, поэтому одним запросом вы находите и точные артикулы товаров, и приблизительные по смыслу совпадения.
  • Pinecone — полностью управляемый сервис: обслуживать ничего не нужно. Подходит, если проще заплатить, чем самому держать инфраструктуру.

Короткий ответ

Начните с Chroma (или с pgvector, если вы уже используете Postgres). Она спокойно справляется с миллионами векторов — больше, чем увидит большинство побочных проектов. Переезд потом — это скрипт пересчёта эмбеддингов и полдня работы. Над вопросом «какую векторную базу выбрать» стоит думать минуты, а не недели.

Реализация: индексация и поиск

Суть укладывается примерно в 20 строк. Первый файл — математика, которая лежит в основе: косинусное сходство, полный перебор. Второй файл — то же самое с настоящей векторной базой (Chroma): ANN-индекс заменяет цикл.

Косинусное сходство в одном предложении: представьте две стрелки, выходящие из одной точки, — одна для вашего вопроса, другая для документа. Оценка показывает, насколько они указывают в одном направлении: 1 означает «одно и то же», 0 — «никак не связаны». Длина не важна, и это именно то, что нужно: вопрос из двух слов и длинный абзац всё равно могут быть об одном и том же, а значит, могут оказаться соседями.

vector-search
1import numpy as np
2 
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 и векторным базам размер не важен.

Два практических замечания для реальной эксплуатации:

  • Всегда храните исходный текст рядом с каждым вектором: он понадобится для ответа, а сам по себе вектор ничего не говорит.
  • Переиндексируйте при смене модели. Векторы разных моделей несравнимы, поэтому после смены модели придётся заново посчитать эмбеддинги и перестроить индекс.
Проверьте себя

Почему бы просто не пройти циклом по всем векторам и не посчитать расстояние?

Что показывает косинусное сходство?

Вы сменили модель эмбеддингов. Что ломается?

Что нужно хранить рядом с каждым вектором?