Назад к блогу

Как МТС собирала RAG-ассистента для клиентских менеджеров: локальный стек, хлебные крошки и четыре стадии поиска

Как МТС собирала RAG-ассистента для клиентских менеджеров: локальный стек, хлебные крошки и четыре стадии поиска

МТС построила полностью локального RAG-ассистента для клиентских менеджеров, который отвечает по 100+ B2B-продуктам и всегда подкрепляет ответ ссылкой на источник в документации. Разбираем, как команда обошла запрет на внешние API, справилась с шестиуровневой Wiki и собрала четырёхстадийный поиск с «хлебными крошками» — без единого обращения к облачным сервисам.

Команда CVM B2B из МТС превратила Telegram-бота для подготовки клиентских менеджеров в ассистента, который отвечает на вопросы по более чем 100 B2B-продуктам и подкрепляет каждый ответ ссылкой на источник в документации. Весь стек — локальный, внешние API под запретом.

Разработка шла под тремя жёсткими требованиями: данные хранятся только в локальном стеке, информация поступает только из базы, а каждый ответ сопровождается «хлебными крошками» — путём до фрагмента в структуре документа. Если нужных сведений нет, ассистент должен честно ответить «не нашел».

Что было до RAG

Ранее команда реализовала Telegram-бота, помогавшего клиентским менеджерам готовиться к встречам. Его функционал включал разведку по ИНН (сбор информации о компании из внутренних систем), подбор продуктов под клиента с помощью классических моделей, LLM-комментарии — статические подсказки на основе шаблонов, то есть заранее заготовленные тексты, которые бот показывал менеджеру без обращения к языковой модели в момент ответа, — и готовые сценарии диалога под типовые задачи. Интерфейс был привычным Telegram-ботом с кнопками, рекомендациями и обратной связью через лайки и дизлайки.

Часть информации поступала из внутренних источников, другая обогащалась внешними порталами и веб-поиском. В какой-то момент безопасники запретили брать данные извне, и команде пришлось полностью пересмотреть подход, перейдя на работу исключительно с внутренними источниками знаний.

Масштаб задачи

Ассистент должен отвечать на вопросы по 100+ B2B-продуктам со ссылками на источник в документации. Внутренняя Wiki оказалась глубоко вложенной: у некоторых продуктов — до шести уровней иерархии, а рекорд по числу подстраниц на один продукт при такой структуре — 77. Такую структуру нельзя просто передать языковой модели.

Предобработка: от Wiki к Markdown

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

Сначала команда выбрала Selenium, который справлялся с задачей, но затем перешла на более современные инструменты:

  • Playwright — движок для автоматизации браузера, работающий с динамическим контентом (JS), эмулирующий действия пользователя и обходящий SPA-ограничения.
  • Crawl4AI — специализирован на быстрой выгрузке веб-страниц сразу в чистый Markdown, оптимизированный для контекста LLM. Позволяет загрузить структуру документа или предоставить несколько примеров, а затем самостоятельно обучается их разбирать и умеет обходить ограничения.
  • Markdownify — конвертер HTML в Markdown, используется для финальной очистки данных от мусора (скрипты, стили, навигация) и приведения их к компактному виду. Полученный формат можно использовать для векторизации без дополнительной подготовки.

Заголовок первого уровня в Markdown соответствует исходному продукту, а последующие уровни отражают вложенность страниц и разделов.

Подготовка данных: чанки, таблицы, эмбеддинги

Предобработка сырого Markdown-документа включает два этапа. Первый — добавление «хлебных крошек», которые позволяют не потерять информацию о том, откуда был взят конкретный фрагмент текста. Второй — обработка таблиц: если граница чанка проходит посередине таблицы, нижняя часть теряет контекст и становится бесполезной. Поэтому перед загрузкой таблицы преобразуют в единый структурированный (линейный) вид, а ссылки удаляют как мусорные и занимающие токены без добавления контекста.

Схема разбиения двухуровневая. Сначала формируются parent-чанки — крупные фрагменты документа, обычно соответствующие отдельным разделам или подстраницам. Затем каждый такой фрагмент дополнительно разбивается на небольшие child-чанки. Child-чанки преобразуются в эмбеддинги с помощью модели BGE-M3 и сохраняются в pgvector. Помимо самих векторов, там хранятся текст чанка и дополнительная метаинформация. В базе данных появились две связанные таблицы с parent-чанками и child-чанками.

Пайплайн на Langflow

Архитектура состоит из четырёх основных блоков. Подготовка данных (Preprocessing) превращает исходные документы в пригодный для поиска вид. Загрузка данных в векторную базу (Ingestion) помещает эти документы в хранилище, где они разбиваются на parent- и child-чанки в два этапа: сначала документы загружаются в векторную базу, а затем делятся на parent- и child-чанки. Поиск релевантных документов (Retrieval) находит в базе фрагменты, подходящие под запрос пользователя. Генерация ответа (Generation & Evaluation) формирует итоговый ответ и оценивает его качество. Часть элементов пайплайна команда реализовала самостоятельно, поскольку их не было в библиотеке Langflow.

Retrieval и реранжирование

Простой поиск по векторам достаёт 60–70% нужного, а цепочка из четырёх шагов повышает качество до 85–90%. Пайплайн Retrieval состоит из четырёх этапов:

  1. Перегенерация запроса. Система получает пользовательский запрос и создаёт четыре переформулировки с помощью языковой модели. Это увеличивает полноту поиска (Recall), поскольку один и тот же вопрос может быть сформулирован разными способами.
  2. Гибридный поиск. Параллельно ищут по смыслу (Vector) и по словам (BM25), чтобы закрывать разные типы запросов.
  3. Объединение результатов. Результаты двух поисков объединяются в общий массив с помощью алгоритма Reciprocal Rank Fusion (RRF).
  4. Реранжирование — самый дорогой этап. Массив уходит в Reranker. Например, из 20 найденных чанков Reranker оставляет пять, которые гарантированно содержат ответ.

После реранжирования остаётся три наиболее релевантных child-чанка, по ним восстанавливаются соответствующие parent-чанки, затем выполняется дедупликация, чтобы не передавать модели дубляжи.

Метрики и выводы

Команда сравнила две версии реранкера — локальную с BGE CrossEncoder и продовую с metadata-эвристиками (реранкер, который упорядочивает найденные фрагменты по формальным признакам их метаданных, а не по смысловой близости к запросу). Локальная версия показала заметно лучшие результаты практически по всем показателям. В проде большинство метрик значительно упало, но достоверность ответа (Faithfulness) выросла.

Падение контекстуальных метрик объясняется тем, что модель стала чаще отвечать «Информация в документации не найдена». Если нужных сведений действительно нет в базе знаний, такой ответ считается технически корректным.

На конкретном примере: когда FlagEmbedding вывела на первое место раздел «Сравнение продуктов», модель сгенерировала ответ с почти идеальными метриками. Когда метод Reranker выводил на первое место раздел «Автосекретарь. Контакты и общие вопросы», чанк не содержал нужного контекста и метрики стремились к нулю.

Ограничения и открытые вопросы

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

Источники

Похожее