За 8 дней частичной занятости я собрал RAG-систему на NestJS + PostgreSQL (pgvector), которая обрабатывает ~11 000 чанков документов.
Первая версия отвечала около 4 минут, после оптимизации - 40–60 секунд.
Главный вывод: RAG - это не «векторный поиск + LLM», а в первую очередь подготовка данных, фильтрация контекста и аккуратная работа с промптами.
Зачем я это делал
Главной целью проекта было создать RAG-систему, которая могла бы отвечать на вопросы на основе моих знаний и опыта, это позволило понять реальную работу с большим количеством документов.
RAG-система была интегрирована с моим сайтом-визиткой site15.ru. Там я показываю и описываю часть проектов за последние 10 лет: скачивания, звёзды, просмотры, npm-библиотеки, счётчики групп, посты и карму на Habr и dev.to.
Технически это реализовано так: фронтенд site15.ru → backend site15.ru → сервер rag-system. Backend передаёт специальный API-ключ, что хотя бы частично защищает сайт от лишних запросов.

Таким образом, site15.ru выступает как демо и интерфейс для взаимодействия с RAG.

Почему RAG оказался сложнее, чем казалось
На старте проект выглядел просто:
RAG = векторный поиск + LLM
На практике оказалось, что большая часть времени уходит на:
подготовку и сегментацию данных,
фильтрацию контекста,
формирование и согласование промптов.
Первая версия системы делала всё последовательно - и отвечала около 4 минут даже на простой вопрос.
Архитектура системы
Источники данных (Telegram переписки, статьи, портфолио, резюме)
↓
Backend (NestJS)
├─ LLM-модуль
├─ PostgreSQL + pgvector
├─ RxJS асинхронный pipeline
├─ Dialog Manager
└─ API контроллер для site15.ru
↓
RAG-компоненты
├─ Question Transformer
├─ Фильтрация документов и секций
├─ Векторный поиск
└─ Формирование промпта
↓
LLM-провайдеры
(OpenAI / Groq / Ollama)

Выбор стека
Backend - NestJS
Я постоянно пишу бэкенды на NestJS, поэтому выбор очевиден.

Frontend - React Admin
Нужна была админка, чтобы управлять данными и промптами.


PostgreSQL + pgvector
Одна система для обычных данных и векторов - проще и надежнее, чем раздельные хранилища.

Несколько LLM-провайдеров
Поддержка разных провайдеров позволяет использовать бесплатные лимиты и легко переключаться при необходимости.

Ключевое архитектурное решение: иерархическая фильтрация
Главная идея - не отправлять в LLM всё подряд.
Pipeline обработки запроса:
Запрос пользователя (frontend site15.ru)
↓
Backend site15.ru
↓
RAG-сервер: нормализация вопроса
↓
Фильтрация по метаданным (11 000 → ~500)
↓
Фильтрация по секциям/заголовкам (500 → ~200)
↓
Векторный поиск (200 → 5–10)
↓
Один оптимизированный запрос в LLM

Так удалось значительно сократить объём данных, отправляемых в LLM, и ускорить ответы.
Самая большая техническая проблема
Создание метаданных для 11 005 чанков документов.
Так как я хотел, чтобы всё работало, используя только бесплатные лимиты LLM, я не мог создавать метаданные в облачных LLM — там я быстро упирался в ограничения, поэтому пришлось запускать локально через LM Studio с моделью qwen2.5-7b-instruct и обработка заняла 2 дня на RTX 2060 SUPER.

Почему первая версия была медленной
Первая версия: 8 промежуточных промптов, последовательные вызовы LLM → ~4 минуты на ответ.
После оптимизации: 4 согласованных промпта, часть этапов параллельна, асинхронная очередь на RxJS → 40–60 секунд.
Промпты и ошибки
Промпты оказались самым сложным моментом. Примеры:
Утечка контекста
Пользователь: "Расскажи про NestJS"
LLM: "NestJS - отличный фреймворк. Кстати, у меня есть телеграм-бот для кофе..."
Конфликт инструкций
Промпт 1: отвечай кратко
Промпт 2: приводи подробные примеры
Вывод: меньше, но согласованных промптов - лучше.
Использование ИИ при разработке
Около 70% кода писалось с помощью ИИ-ассистентов, но без моей архитектурной правки и отладки он не работал бы.

Безопасность
проверка разрешённого IP,
проверка API-ключа для запросов с backend site15.ru.
Проект не production-ready, но это позволяет хоть как-то защитить основной сайт от лишних запросов.

Развёртывание
RAG-сервер на отдельном VPS,
docker-composeдля PostgreSQL и Ollama,backend через
pm2,интеграция с site15.ru.

Текущий статус
Экспериментальный проект, не MVP, не production-ready.
Site15.ru выступает как интерфейс для демонстрации работы RAG и статистики проектов.
Планы на будущее
Написание тестов пользовательских сценариев (проверка корректности ответов и pipeline).
Рефакторинг LLM-модуля в стиле NestJS.
Добавление аналитики: время обработки, количество вызовов LLM, расход токенов.
Автоматизация деплоя через Docker / Kubernetes.
Улучшение точности и скорости через оптимизацию фильтрации контекста.
Выводы
RAG - это сложный инженерный процесс: подготовка данных, фильтрация контекста и аккуратная работа с промптами важнее, чем сама LLM.
За 8 дней частичной занятости удалось собрать рабочую систему, интегрировать её с site15.ru и получить реальный опыт работы с RAG.
Ссылки
https://github.com/site15/rag-system - Репозиторий проекта
https://site15.ru - Мой сайт визитка с чатом поддержки
Комментарии (13)

KonstantinTokar
26.01.2026 17:0511000 чанков это сколько в байтах?

kaufmanendy Автор
26.01.2026 17:05вот эти файлы на куски разбиты https://github.com/site15/rag-system/tree/main/sources
файлов: 450, размер общий: 16.5 MiB (17,311,147 bytes)
куски разной длины

obir
26.01.2026 17:05
Хороший проект, но у меня думает, к сожалению, 2-3 минуты на вопрос. Я RAG собирал на векторах из PDF-ов ~50 Mb на llama 3.2 и он был очень быстр

kaufmanendy Автор
26.01.2026 17:05Тут я больше изучал как вообще можно строить эти RAG системы и какие компоненты там могут быть
Я добавил там на сайте возможность посмотреть какие именно промпты запускались и там теперь видно почему долго
Запускается ведь не один промпт, а много, для того чтобы из 11000 чанков сделать 5 и уже в 5 искать нужные данные, все зависит еще и от самого вопроса, чем он ближе к данным которые в базе тем быстрее отработают все логики



Так как я использую бесплатные лимиты, я стараюсь не делать параллельных запросов - у некоторых провайдеров на это тоже стоят ограничения. Но если делать production-ready RAG-систему и купить платный доступ к нейросетям или развернуть их на собственном железе, то многие задачи можно распараллелить и существенно ускорить поиск ответа.

Gigantazavr
26.01.2026 17:05Автор, рекомендую погрузиться в сложный мир обработки данных, и, вместо 8 промтов нейросетям, использовать собственно-написанные parser, chunker, и далее - embeddings и retrieval. Это позволит вам сократить скорость с 40 секунд на ответ до 2 секунд.
Разобраться в этом стоит, потому что тогда промт для вас станет вишенкой на торте, а не сложнейшим этапом.

kaufmanendy Автор
26.01.2026 17:05Там уже это все есть, в итоге после фильтрации из 11000 записей емебедниг чанков по входному запросу получаем 5 чанков с контекстом и есть входной запрос от пользователя
Запрос пользователя нужно привести к нужному нам формату - это делается через дополнительные запросы к нейронке
Потом этот "нормализованный" запрос + история переписки прогоняем для каждого чанка и используем специальный промпт который определяет есть ли ответ на заданный вопрос в текущем контексте
После прогона по всем чанкам и нахождения ответа мы формируем ответ пользователю тоже через специальный промпт который дополнительно трансформирует ответ используя историю переписки
На вход приходят разные виды документов: переписки из чата, посты из хабра, резюме с хедхантер, спарсенный сайт визитки
Документ можно разделить на разные категории, документ бьется на чанки и у каждого чанка своя категория, запрос от пользователя имеет свою категорию, история может иметь другую отличную от последнего запроса категорию
В зависимости от категорий мы используем разные промпты при трансформации входных, промежуточных и выходных данных
export enum Category { telegram = 'telegram', /** * рекламные, массовые или нерелевантные сообщения; */ spam = 'spam', /** * предложения о работе на полный рабочий день (штат, фултайм, постоянная занятость); */ job = 'job', /** * вопросы о найме, формировании или усилении команды; */ hiring = 'hiring', /** * вопросы о собеседованиях, подготовке к ним, оценке кандидатов; */ interview = 'interview', ... }

Gigantazavr
26.01.2026 17:05Рекомендую посмотреть, что такое cosine similarity - она решит вашу проблему. Вы можете заменить ваши промты одним надежным алгоритмом.

kaufmanendy Автор
26.01.2026 17:05Я глубоко не копал RAG, просто выделил неделю на изучение и не больше, и поделился тем что узнал и сделал с 0 знаниями
Сейчас есть база с векторными колонками, и я ищу совпадения вектора входного поискового запроса с векторами чанков а также с специальной графовой версий чанка, там под капотом тоже происходит поиск синусом


И после фильтрации я и получаю вместо 11000 чанков 5

Gigantazavr
26.01.2026 17:05Вы пытаетесь top 5 чанков отфильтровать через 5 запросов к нейронке, находя "один самый точный". - это не та стратегия для RAG.
Вы можете либо сразу закинуть эти 5 чанков (найденных путем ретривера) в основную модель, либо, если хотите, взять одну модель реранкера - она сравнит векторы и семантику, и отправит самый точный вариант.
LLM, даже локальная на 8B параметров, хорошо может из 5 вариантов выбрать 1 верный - не стоит недооценивать ее возможности

kaufmanendy Автор
26.01.2026 17:05Спасибо, в следующий раз когда когда вернусь к этой задаче попробую все чанки в один запрос положить
confident_action
Запрос до сих пор в обработке.
Толку от всех этапов нет, если одно слово все впечатление ломает, нехватает на входе. быстрого валидатора
kaufmanendy Автор
да, спасибо за комментарий, вижу что там все бесплатные запросы кончились у всех активных провайдеров, поэтому оно так и повисло, ща вот пишу заглушку чтобы оно хотя-бы уведомляло об этом в обратку
kaufmanendy Автор
Добавил различные промежуточные тексты, чтобы было понятнее что происходит и вывожу провайдера и модель которая используется