Наивный векторный поиск своими руками
Самый честный способ понять векторный поиск — написать его самому, без библиотек.
Наивный (brute-force) поиск — сравнить запрос с каждым вектором базы и вернуть top-k самых близких. Медленно на больших данных, но идеально точно и понятно.
Что мы строим
Соберём крошечный индекс: список (текст чанка → его игрушечный вектор по осям [спорт, готовка, финансы]). Затем напишем функцию search, которая по вектору запроса вернёт top-k ближайших. Это ровно то, что делает векторная БД — только без оптимизаций.
import math
def cosine(a, b):
dot = sum(x * y for x, y in zip(a, b))
na = math.sqrt(sum(x * x for x in a)) or 1e-9
nb = math.sqrt(sum(x * x for x in b)) or 1e-9
return dot / (na * nb)
# мини-индекс: (текст чанка, вектор по осям [спорт, готовка, финансы])
index = [
("Как варить пасту аль денте", [0.0, 0.9, 0.1]),
("Тренировка ног для бегунов", [0.9, 0.0, 0.1]),
("Куда вложить деньги новичку", [0.1, 0.0, 0.9]),
("Рецепт томатного соуса", [0.0, 0.95, 0.0]),
("Разминка перед марафоном", [0.85, 0.0, 0.0]),
]
def search(query_vec, k=2):
scored = [(cosine(query_vec, vec), text) for text, vec in index]
scored.sort(reverse=True)
return scored[:k]
query = [0.0, 0.85, 0.15] # вопрос про готовку
print("Топ-2 ближайших чанка:")
for score, text in search(query, k=2):
print(f" {score:.3f} {text}")Вывод:
Топ-2 ближайших чанка: 0.998 Как варить пасту аль денте 0.985 Рецепт томатного соуса
Запрос «про готовку» вытащил два кулинарных чанка и проигнорировал спорт и финансы — хотя мы нигде не писали слово «готовка» в текстах. Сработала геометрия векторов. Это и есть retrieval-шаг RAG в чистом виде.
Почему это «наивно»
Мы сравниваем запрос со всеми элементами (for по всему индексу). На пяти чанках — мгновенно. На миллионах — медленно. Именно здесь в дело вступают ANN-индексы из прошлого урока: они дают тот же результат почти всегда, но без полного перебора.
Шаг к настоящей базе
Если заменить игрушечные векторы на выход настоящей модели эмбеддингов, а список — на HNSW-индекс, получится продакшн-RAG. Логика top-k поиска при этом не меняется — меняется лишь скорость и масштаб.
Итог
- Векторный поиск = посчитать близость со всеми и взять top-k.
- Наивный перебор прост и точен, но линеен по размеру базы.
- Тот же интерфейс top-k остаётся и в настоящих векторных БД.