ADG Оставить заявку
Блог Автоматизация 5 мин чтения

RAG для корпоративной базы знаний: LangChain + Chroma + OpenAI в деле

Строим систему вопрос-ответ по внутренним регламентам: LangChain 0.0.x, векторная БД Chroma, OpenAI API. Архитектура, подводные камни и честные ограничения первого запуска.

Контекст момента

LangChain 0.0.x (февраль-март 2023) - фреймворк для построения LLM-приложений с RAG набирает популярность

После первого пилота с ChatGPT API один из вопросов повис в воздухе: что делать, когда база знаний не маленькая вики на 50 статей, а серьёзный корпоративный архив - регламенты, инструкции, приказы, технические стандарты - всё это в PDF и Word, несколько сотен документов? В промпт такое не вставишь, BM25 по ключевым словам работает плохо на юридически-бюрократическом тексте. Клиент поставил задачу конкретно: сотрудник задаёт вопрос на русском языке, система отвечает и показывает, из какого именно документа взят ответ.

Так мы дошли до RAG - Retrieval-Augmented Generation. Инструмент для сборки подобного - LangChain - как раз набирает обороты, версия 0.0.x, меняется быстро, документация местами отстаёт от кода. Но уже достаточно стабильна, чтобы собрать рабочий прототип.

Архитектура за три шага

Схема RAG в целом простая: документы превращаются в векторные представления (эмбеддинги), хранятся в векторной БД, при вопросе находятся ближайшие по смыслу фрагменты и вместе с вопросом отправляются в LLM. Модель отвечает не из своей «памяти», а опираясь на переданный контекст.

документы (PDF, DOCX)
  -> разбивка на чанки (RecursiveCharacterTextSplitter)
  -> эмбеддинги (OpenAI text-embedding-ada-002)
  -> Chroma (локальная векторная БД)

вопрос пользователя
  -> эмбеддинг вопроса
  -> поиск top-k чанков в Chroma
  -> сборка промпта (вопрос + найденные фрагменты + инструкция)
  -> GPT-3.5-turbo (chat completion)
  -> ответ + ссылка на источник

Chroma выбрали потому что разворачивается локально без внешних зависимостей, встраивается в Python-процесс и персистирует индекс на диск. Для первого прототипа это удобнее, чем поднимать отдельный сервис.

Что реально зацепило при сборке

Разбивка документов - не тривиальная задача. LangChain даёт RecursiveCharacterTextSplitter с настройкой chunk_size и chunk_overlap. Казалось бы, выставил 1000 символов с перекрытием 200 - и всё. Но у корпоративных регламентов специфика: пункт 3.4.2 без заголовка раздела выглядит как бессмыслица. Пришлось экспериментировать: для структурированных документов с чёткой нумерацией лучше работает разбивка по разделам, а не по фиксированному числу символов. Потратили на подбор стратегии больше времени, чем на всё остальное вместе взятое.

Качество PDF-выгрузки - слабое место. Сканированные документы (а в корпоративном архиве их немало) LangChain без OCR не читает. PyPDFLoader вытаскивает текст только из нормальных PDF с текстовым слоем. Для сканов нужен отдельный шаг - tesseract или что-то подобное. Часть документов клиента оказалась именно такими, и их пришлось обрабатывать вручную перед индексацией.

Метаданные - обязательно, не опционально. В Chroma к каждому чанку можно прикрепить метаданные: имя файла, номер страницы, дата документа. Без этого ответ система даёт, но без ссылки на источник - а это было ключевым требованием. Добавить метаданные постфактум нельзя, нужно закладывать при загрузке.

Стоимость индексации. text-embedding-ada-002 стоит $0.0001 за 1K токенов - звучит дёшево, но несколько сотен документов с суммарным объёмом в несколько миллионов символов - это уже ощутимо. Проиндексировали один раз, переиндексируем только при реальном обновлении документов. Инкрементальное обновление Chroma поддерживает, главное не потерять маппинг document_id.

Как работает в практике

На тестовой выборке вопросов - около тридцати штук, составленных HR-менеджером клиента по реальным регламентам - система отвечала правильно примерно в двух третях случаев. Это при том, что документы нормальные, текстовые, хорошо структурированные.

Провалы распались по нескольким категориям:

  • Вопрос охватывает несколько разных разделов. «Как оформить командировку за рубеж и какие нужны документы» - это три разных регламента, и top-3 чанков не покрывают всё. GPT честно отвечает на то, что получил, но ответ неполный.
  • Термины сформулированы иначе, чем в документе. Векторный поиск по смыслу лучше ключевых слов, но не идеален. Вопрос «как взять отгул» - в регламенте написано «предоставление дополнительного дня отдыха». Эмбеддинги это расстояние сокращают, но не всегда достаточно.
  • Документ устаревший. Система отвечает по тому, что есть в индексе. Если регламент 2019 года противоречит приказу 2022 года, а оба проиндексированы - GPT может смешать или выбрать старый. Актуальность документной базы полностью на совести клиента.

Ограничения, которые нельзя игнорировать

Главное: это не «умная система». Это очень удобный поиск плюс перефразирование найденного. Если нужного ответа нет в документах - GPT это скажет, но иногда попытается додумать из общих знаний. Промпт с явным «отвечай только на основе предоставленного контекста, если ответа нет - так и скажи» сильно снижает галлюцинации, но не устраняет их.

LangChain 0.0.x - это фреймворк в активной разработке. API меняется между минорными версиями, и то, что работало неделю назад, может сломаться после pip install --upgrade. Мы зафиксировали версии в requirements.txt и не обновляем без причины.


Прототип работает, клиент смотрит его с HR-командой. Следующий шаг - подключить инкрементальное обновление индекса при добавлении новых документов и разобраться со сканами через OCR. Если нужна интеграция подобного в ваши процессы - пишите.

Контакт

Нужна такая же инженерная работа?

Опишите задачу и контекст. Ответим в течение рабочего дня, при необходимости подпишем NDA.