RAG над внутренней документацией: ChromaDB + Llama 2 без передачи данных наружу
Строим RAG-систему над runbook клиента: ChromaDB как векторная БД, Llama 2 на локальном GPU - инженеры спрашивают через чат, данные не покидают периметр.
Retrieval-Augmented Generation (RAG) как паттерн обогащения LLM корпоративными данными без дообучения, 2023
В сентябре мы подняли локальный helpdesk-ассистент через Ollama и честно зафиксировали: треть вопросов из реальной очереди закрывается «из коробки», остальное - нет, потому что модель просто не знает корпоративной специфики. Туда же ушло резюме: следующий шаг - RAG поверх документов клиента.
Следующий шаг случился.
Что такое RAG и почему не дообучение
Retrieval-Augmented Generation - это паттерн, при котором модель не хранит корпоративные знания в весах, а получает их в момент запроса. Упрощённо: пользователь задаёт вопрос, система ищет в базе документов релевантные фрагменты, вставляет их в контекст и отдаёт всё это модели. Модель видит вопрос плюс релевантный кусок runbook - и отвечает опираясь на него, а не на веса.
Альтернатива - файн-тюнинг, то есть дообучение модели на документах клиента. Звучит логично, пока не смотришь на практику: дообучение дорого по GPU-часам, теряет общие знания модели, требует переобучения при каждом обновлении документации. RAG дешевле, гибче и, главное, не требует ML-экспертизы внутри команды - нужны только разработчик и понимание, как устроен поиск.
Задача конкретного клиента
Это тот же клиент, что и в сентябре. Документация - около 300 markdown-файлов: runbook по дежурству, описание сервисов, инструкции по реагированию на инциденты. Всё это лежит в корпоративном Confluence и периодически экспортируется в markdown для офлайн-хранения.
Требования жёсткие: данные не выходят за периметр. Значит, никакого OpenAI, никакого облачного векторного поиска. Всё на железе клиента.
Серверная часть - тот же физический сервер с RTX 4090 24 GB, Rocky Linux 9.
Стек
Выбор получился такой:
- Модель. Llama 2 13B в формате GGUF через llama-cpp-python. Мы уже работали с ней в августе, знаем характер - это не Ollama, тут больше контроля над параметрами инференса, что для RAG важно: надо управлять длиной контекста и поведением при его заполнении.
- Векторная БД. ChromaDB. Работает как Python-пакет, не требует отдельного сервера, данные хранит локально на диске. Для 300 документов - более чем достаточно.
- Эмбеддинги. sentence-transformers, модель
all-MiniLM-L6-v2. Маленькая, быстрая, для технической документации на английском и русском работает терпимо. Запускается на CPU - GPU оставляем для инференса. - Интерфейс. Простой FastAPI-эндпоинт плюс Telegram-бот. Инженеры дежурства уже сидят в корпоративном Telegram, добавить бота - минимум интеграционных усилий.
Как это работает
Вопрос пользователя
|
v
Эмбеддинг вопроса (sentence-transformers)
|
v
Поиск top-5 фрагментов в ChromaDB
|
v
Сборка промпта: system_prompt + фрагменты + вопрос
|
v
Инференс Llama 2 13B (llama-cpp-python, n_ctx=4096)
|
v
Ответ с указанием источника (имя файла + номер строки)
Фрагменты - это чанки по 512 токенов с перекрытием 64 токена. Разбивка по токенам, а не по словам или символам, потому что иначе смысловые единицы рвутся в случайных местах.
Индексация
Скрипт индексации прогоняет все markdown-файлы, режет на чанки, считает эмбеддинги и складывает в ChromaDB вместе с метаданными - имя файла, заголовок раздела, дата последнего изменения. Индексация 300 файлов на CPU заняла около 20 минут. Пересчёт при обновлении документации - только изменённые файлы, обнаруживаем по хешу.
Промпт и поведение модели
Это оказалось важнее, чем казалось. Первая версия промпта была наивной - просто «вот документы, ответь на вопрос». Модель охотно отвечала, но придумывала детали, которых в документах не было. Классическая галлюцинация, только теперь она ещё и маскировалась под ответ «из runbook».
Помогло жёсткое ограничение в системном промпте: отвечать только на основе переданных фрагментов, при недостаточной информации явно говорить об этом. Плюс мы добавили вывод источника - какой файл и какой раздел послужил основой. Инженер видит ссылку и может проверить. Это убивает иллюзию оракула и делает инструмент честным.
Что получилось на реальных запросах
Тестировали на выборке дежурных вопросов за последние три месяца. Сравнивали с сентябрьскими результатами без RAG.
Инфраструктурно-специфичные вопросы - «какой endpoint у метрик кластера prod-1», «кто on-call по сервису payments» - теперь закрываются, если ответ есть в документации. Точность заметно выросла именно в этой категории.
Процедурные вопросы - «как заэскалировать инцидент P1» - работают, если runbook написан внятно. Если документ содержит фразы вроде «действовать по ситуации» - модель честно воспроизводит эту бесполезность.
Вопросы не из документации - модель говорит, что информации нет. Иногда добавляет общий ответ из своих весов, хотя мы просили этого не делать. Нужно доработать промпт.
Русский язык всё ещё проседает - Llama 2 периодически переключается на английский в середине ответа, особенно при цитировании технических фрагментов. Раздражает, но дежурные привыкают.
Что не решили
Документация обновляется нерегулярно - часть runbook устарела, и модель честно воспроизводит устаревшие инструкции. Это проблема процесса, не технологии, но пользователи всё равно вопрос задают нам.
Оценка качества ответов - пока субъективная. Нет метрики, нет автотестов над набором «вопрос - правильный ответ». Работаем по ощущениям, что некомфортно.
Текущий статус
Бот запущен в дежурной группе около двух недель. Обратная связь от инженеров - осторожно положительная. Главная ценность оказалась не в том, что бот отвечает вместо человека, а в том, что даёт ссылку на нужный раздел runbook. Поиск по Confluence у клиента так себе - бот находит быстрее.
Интеграция с внутренними системами - отдельная работа: подключить корпоративный SSO для бота, настроить ротацию ключей API, разобраться с правами доступа к разным категориям документов. Это следующая итерация.
Общая оценка: паттерн работает, стек работает, данные периметр не покидают. Галлюцинации есть, но управляемые - пока явно говоришь модели «молчи если не знаешь» и показываешь источник, инструмент остаётся честным.