AI Vibe Craft
← Назад к AI Vibe News

Редакция 14 июня 2026 г.

Разборы

Семантический поиск за ~150 строк: embeddings, pgvector и локальная модель без API-ключей

Семантический поиск за ~150 строк: embeddings, pgvector и локальная модель без API-ключей.

Семантический поиск на своих данных упирается в облачные embedding API: ключи, квоты, утечка текста наружу. Материал @dev48v на Dev.to описывает другой путь — компактный поисковик на embeddings и Postgres pgvector в Node без API-ключей.

Локальный inference: embeddings без облачного биллинга

Векторные представления текста — базовый кирпич для RAG, агентов с памятью и любого «поиска по смыслу». Здесь за эмбеддинги отвечает модель all-MiniLM-L6-v2 в варианте Xenova/all-MiniLM-L6-v2 через библиотеку Transformers.js (@xenova/transformers ^2.17.2). Каждый фрагмент превращается в вектор из 384 чисел; в схеме Postgres колонка объявлена как vector(384) — размерность должна совпадать с моделью.

Инференс идёт в Node 20+, без отправки данных в облако. При первом запуске модель скачивается (~25 MB) и кэшируется на машине. Вызов extractor настроен с pooling: "mean" и normalize: true, чтобы векторы были готовы к cosine distance.

Локальный стек снимает зависимость от биллинга embedding-провайдеров — удобная отправная точка для прототипа AI-фичи на своих документах.

Демо-проект лицензирован под MIT; исходники — в репозитории dev48v/pgvector-from-zero.

Индекс на pgvector: Wikipedia как демо-корпус

Postgres с расширением pgvector поднимается из Docker-образа pgvector/pgvector:pg16; по README автор отмечает готовность БД примерно за ~5 с после docker compose up -d. Корпус для демо набирается из публичного Wikipedia REST API — эндпоинт /page/random/summary, без аутентификации.

По умолчанию сидируется ~300 статей (SEED_LIMIT, дефолт 300). Для каждой эмбеддится строка title + summary — один вектор на статью, без чанкинга длинных текстов. Перед повторным сидом таблица articles очищается (TRUNCATE … RESTART IDENTITY). Фильтруются disambiguation-страницы и короткие extract (< 40 символов); дедупликация по title, верхняя граница попыток — count × 8.

Для ускорения поиска создаётся HNSW-индекс на колонке embedding с vector_cosine_ops — approximate nearest neighbour поверх векторов.

docker compose up -d
npm run seed
npm run dev

После npm run dev веб-интерфейс доступен на http://localhost:3000.

Cosine search вместо keyword match

Запрос пользователя тоже превращается в embedding той же моделью. В Postgres/pgvector близость считается оператором <=> (cosine distance); ранжирование — ORDER BY embedding <=> $query, similarity как 1 - distance.

На корпусе из 300 статей для запроса «animals that live in the ocean» автор приводит иллюстративные score:

Результат Similarity
Blue whale 92.1%
Coral reef 88.4%
Sea otter 85.0%

Другие примеры из материала — без чисел: «famous battles in history» возвращает наполеоновские сражения и древние осады; «how the brain works» — нейронауку с «neuron»/«cortex», хотя буквальных слов запроса в текстах нет. Формальных бенчмарков (latency, recall@k, сравнение с keyword baseline) в первоисточнике нет — это ограничение демо, а не продакшен-отчёт.

Смысл в том, что поиск ловит тему, а не совпадение токенов — ровно то, что отличает embedding-пайплайн от классического LIKE или полнотекстового индекса.

Два шага RAG, которые уже работают

Автор явно вписывает проект в цепочку Retrieval-Augmented Generation (RAG):

  1. эмбеддить документы и хранить векторы — реализовано;
  2. эмбеддить вопрос и искать ближайшие фрагменты — реализовано;
  3. передать найденное LLM с инструкцией отвечать только по контексту — следующий шаг, в репозитории не сделан.

Это рабочие первые два звена retrieval-слоя: без них агент или чат-бот не получит релевантные куски из вашей базы знаний. В README перечислены направления «going further»: answer-слой с LLM, чанкинг длинных документов, альтернативные операторы pgvector (<#>, <->).

Пост входит в серию TechFromZeroDay 45, формат «one new technology every day, built from scratch, every line explained». Полный каталог предыдущих дней в проверенных источниках не раскрыт; читать материал стоит как самостоятельный мини-проект по векторному поиску. Время чтения на Dev.to — около 3 минут; код рассчитан на разбор «с нуля».

Как повторить стек у себя

Минимальный набор зависимостей из package.json репозитория:

Компонент Версия
@xenova/transformers ^2.17.2
pg ^8.13.1
express ^4.21.2
TypeScript / tsx ^5.7.2 / ^4.19.2

Клонирование и запуск по инструкции из поста:

git clone https://github.com/dev48v/pgvector-from-zero.git
cd pgvector-from-zero
npm install
docker compose up -d
npm run seed
npm run dev

Для продакшена понадобятся чанкинг, обновление индекса и слой ответа LLM — но как лабораторный стенд для embeddings + pgvector без облачных ключей связка уже собирается за вечер. Если вы проектируете AI-фичу с поиском по своим данным, такой локальный контур быстрее отвечает на вопрос «как это ощущается на реальных векторах», чем абстрактное чтение про RAG.

Источники