RAG System

RAG-система на NestJS + PostgreSQL (pgvector) для ответов на вопросы на основе знаний и опыта.

TL;DR

За 8 дней частичной занятости я собрал RAG-систему на NestJS + PostgreSQL (pgvector), которая обрабатывает ~11 000 чанков документов.

Первая версия отвечала около 4 минут, после оптимизации — 40–60 секунд.

Главный вывод: RAG — это не «векторный поиск + LLM», а в первую очередь подготовка данных, фильтрация контекста и аккуратная работа с промптами.

С чего всё началось

Идея проекта возникла довольно прозаично. Я давно хотел сделать себе простой сайт-визитку, но всё время откладывал. В какой-то момент понял, что в эпоху нейросетей это уже не большая проблема — и за вечер собрал site15.ru.

Дальше возник логичный вопрос: почему бы не добавить на сайт чат, который мог бы отвечать на вопросы на основе моих знаний, опыта и проектов?

Так я и начал разбираться, как вообще делаются RAG-системы.

Проект изначально был для личного использования — без бизнес-целей и «стартап-мышления». Мне хотелось руками пройти путь от «ничего не знаю про RAG» до рабочей системы.

Зачем вообще RAG

Основная задача была учебная: научиться работать с RAG-системами, потому что сейчас это одна из самых востребованных тем — корпоративные базы знаний, ответы в стиле компании, поиск по внутренней документации.

По ходу стало понятно, что RAG — это не какая-то магия, а довольно жёсткий инженерный процесс, где ошибки в данных или промптах мгновенно вылезают наружу.

Как это встроено в site15.ru

RAG-система интегрирована с моим сайтом-визиткой. Site15.ru выступает как демо и интерфейс:

frontend site15.ru → backend site15.ru → отдельный сервер rag-system

Backend передаёт специальный API-ключ, что хотя бы минимально защищает систему от лишних запросов. Это не про серьёзную безопасность — просто базовая гигиена.

Таким образом сайт стал не просто визиткой, а живым примером работы RAG.

Почему стек именно такой

Backend — NestJS

Я постоянно пишу бэкенды на NestJS, поэтому тут даже не было выбора.

Frontend — React Admin

Вообще я работаю с Angular, но полноценного фронтенда тут не было нужно — требовалась только админка для управления документами, эмбеддингами и промптами.

В Angular-экосистеме готового аналога react-admin нет, поэтому решил взять его и заодно получить практический опыт.

PostgreSQL + pgvector

Очень удобно, когда не нужно поднимать отдельное векторное хранилище. Обычные данные и векторы живут в одной базе — меньше инфраструктуры, меньше точек отказа.

Несколько LLM-провайдеров

Изначально заложил поддержку разных провайдеров (OpenAI, Groq, Ollama и т.д.), чтобы можно было переключаться между ними и использовать бесплатные лимиты. Когда лимит одного заканчивается — активным становится следующий.

Архитектура и ключевая идея

В какой-то момент стало очевидно, что главная проблема — не LLM, а объём данных, который ты в неё пихаешь.

Я реализовал иерархическую фильтрацию:

Запрос пользователя
 → нормализация вопроса
 → фильтрация по метаданным       (11000 → ~500)
 → фильтрация по секциям          (500 → ~200)
 → векторный поиск                (200 → 5–10)
 → один оптимизированный запрос в LLM

Идея простая: не отправлять в LLM всё подряд.

Почему RAG оказался сложнее, чем казалось

На старте всё выглядело очень просто:

RAG = векторный поиск + LLM

На практике оказалось, что большая часть времени уходит на:

  • сегментацию документов,
  • генерацию метаданных,
  • согласование промптов между собой.

Первая версия системы делала всё последовательно и отвечала около 4 минут даже на простой вопрос.

Самая большая техническая боль

Самым тяжёлым этапом стало создание метаданных для 11 005 чанков документов.

В облаке это просто не взлетало — слишком долго и дорого. В итоге я запустил всё локально через LM Studio с моделью qwen2.5-7b-instruct и два дня гонял чанки на своей RTX 2060 SUPER.

Это был момент, когда стало особенно понятно, что RAG — это не игрушка.

Оптимизация и ускорение

После того как всё заработало, выяснилось, что система тормозит не из-за LLM, а из-за:

  • лишних промежуточных промптов,
  • последовательных вызовов, которые можно было запускать параллельно.

В первой версии было 8 промежуточных промптов, сейчас — 4. Часть этапов стала параллельной, появилась асинхронная очередь на RxJS.

Результат: 40–60 секунд вместо 4 минут.

Промпты — это ад

Самое долгое и сложное — формирование промптов. Они легко конфликтуют друг с другом или начинают «утекать»:

Промпт: отвечай только по контексту
LLM: ...и ещё немного от себя

Главный вывод: меньше промптов, но они должны быть согласованы.

Использование ИИ при разработке

Примерно 70% кода писалось с помощью ИИ-ассистентов, но без моей архитектурной логики, правок и отладки этот код просто не работал бы.

Ассистент — это инструмент, а не замена инженера.

Безопасность и деплой

Проект не production-ready, но я добавил:

  • проверку разрешённого IP,
  • проверку API-ключа.

Развёртывание максимально простое:

  • отдельный VPS,
  • docker-compose для PostgreSQL и Ollama,
  • backend через pm2.

CI/CD пока нет — в планах Docker + Kubernetes.

Текущий статус и планы

Сейчас это экспериментальный проект, не MVP и не продакшен. Скорее заготовка и рабочий пример того, как можно собирать RAG-системы руками.

В планах:

  • написать тесты пользовательских сценариев,
  • отрефакторить LLM-модуль в стиле NestJS,
  • добавить аналитику и статистику,
  • автоматизировать деплой,
  • возможно сделать публичную SaaS-версию.

Итог

RAG — это не «подключил LLM и поехали». Это про данные, фильтрацию и дисциплину в промптах.

За 8 дней частичной занятости мне удалось собрать рабочую систему, интегрировать её с site15.ru и получить реальное понимание того, как RAG работает на практике.