На связи команда CVM (Customer Value Management) B2B из МТС — техлид Артём Каледин и ML-инженер Александр Швайко.
Менеджер должен быстро разбираться, как строить обсуждение с техническим директором компании. Но даже если информация о продукте есть, иногда бывает сложно быстро найти нужные материалы и выбрать плюсы для конкретного клиента. Раньше коллеги готовились к встречам с помощью Telegram-бота, но недавно от бизнеса поступила задача: сократить время на подготовку и повысить качество рекомендаций и контента.
В статье по мотивам доклада на Inside AI Meetup расскажем про архитектуру нашей системы и этапы работы, поделимся результатами и планами, а еще — как построили надежный RAG, который не галлюцинирует (ну, почти).
Что было до RAG
Ранее мы реализовали Telegram-бота, который помогал клиентским менеджерам готовиться к встречам. Его функционал включал в себя:
-
Разведка по ИНН, то есть сбор информации о компании из внутренних систем.
-
Подбор продуктов под клиента с помощью классических моделей.
-
LLM-комментарии — статические подсказки на основе шаблонов.
-
Готовые сценарии диалога под типовые задачи клиентского менеджера.

Интерфейс представлял собой привычный Telegram-бот с кнопками, рекомендациями и механизмом обратной связи через лайки и дизлайки.
Часть информации поступала из внутренних источников, другая — обогащалась внешними порталами и веб-поиском. Но в какой-то момент к нам пришли безопасники и сказали: «Ай-ай-ай, так делать нехорошо, нельзя брать данные извне». Пришлось полностью пересмотреть подход и перейти на работу исключительно с внутренними источниками знаний.
Какую задачу нам поставил бизнес
Коллеги из отдела продаж провели исследование, которое показало, что наибольшее количество времени уходит на подготовку к взаимодействию с клиентом.
Например, если необходимо предложить клиенту продукт MWS GPT, менеджер должен быстро разобраться в сути решения и решить, как строить обсуждение с техническим директором компании. Даже если информация о продукте есть, менеджеру бывает сложно быстро найти нужные материалы и выбрать плюсы, которые продадут инструмент именно этому клиенту.
Из этой точки были определены две цели:
-
Повысить эффективность продавцов через сокращение времени на подготовку ко встречам и на сопровождение сделок.
-
Повысить конверсии из первичного контакта в продажу за счёт качества рекомендаций и контента для продавцов.
Совместно с бизнесом мы собрали список метрик, которые и попытались улучшить с помощью нашего подхода:
-
сокращение времени на подготовку ко встрече;
-
конверсии в продажу;
-
оценка релевантности рекомендаций продавцами;
-
готовность клиентов рекомендовать продавцов (NPS);
-
общая удовлетворённость;
-
время ответа Co-Pilot.
Подробнее про задачу
Что нам предстояло сделать? Превратить Telegram-бота в ассистента клиентского менеджера, который отвечает на вопросы по 100+ B2B-продуктам со ссылками на источник в документации и тем самым ускоряет подготовку ко встречам.
У нас было три требования:
-
Все данные должны храниться в локальном стеке. Мы работаем с чувствительной документацией, поэтому внешние API под запретом.
-
Информация поступает только из базы. Данные отсутствуют на внутренних ресурсах — честно говорим «не нашел».
-
Каждый ответ подкрепляется ссылкой на так называемые хлебные крошки, то есть путь до фрагмента в структуре документа. Менеджер должен иметь возможность проверить данные или углубить свои знания.
Как выглядят данные и система знаний
Чтобы отвечать на вопросы, системе нужны данные. И вот с чем мы работаем.
Кажется, ничего сложного. Стандартная Wiki-страница с таблицами, схемами, PDF и вкладками. Но у некоторых продуктов вложенность до шести уровней, наш рекорд с такой структурой — 77 подстраниц. Как во всем этом хранилище найти нужный ответ с правильной ссылкой? Как понять отличие продукта А от продукта Б и узкую специфику продукта В?
Сначала мы нашли команду, которая отвечает за внутреннюю базу знаний. Оказалось, что у них уже есть собственный RAG, но нет исходных данных — обычного текста в том виде, в котором он хранится в базе знаний. Как быть? Парсить.
Наши методы парсинга и предобработки:
-
Playwright — мощный движок для автоматизации браузера. Позволяет работать с динамическим контентом (JS), эмулировать действия пользователя и обходить SPA-ограничения.
-
Crawl4AI — это Playwright на стероидах для RAG. Специализирован на быстрой выгрузке веб-страниц сразу в чистый Markdown, оптимизированный для контекста LLM.
-
Markdownify — конвертер HTML в Markdown. Используется для финальной очистки данных от мусора (скрипты, стили, навигация) и приведения их к компактному виду.
Сначала мы выбрали инструмент автоматизации браузера Selenium. Он справлялся со своей задачей, но в 2026 году хочется использовать более современные инструменты. Например, Crawl4AI, который позволяет загрузить структуру документа или предоставить несколько примеров, а затем самостоятельно обучается их разбирать и умеет обходить ограничения. Мы работали исключительно с внутренним корпоративным сайтом, поэтому никаких ограничений со стороны системы безопасности не возникало.
После извлечения данных встала задача преобразования HTML в удобный формат для дальнейшей обработки. Для этого применили библиотеку Markdownify, которая конвертирует HTML в Markdown. Этот формат можно использовать для векторизации без дополнительной подготовки.
Четыре стадии архитектуры: от сырого Markdown до оцененного ответа
Теперь перейдем к самой архитектуре. На схеме видно, что она включает большое количество внутренних сервисов и вспомогательных компонентов. В рамках статьи мы рассмотрим только ту часть, которая отвечает непосредственно за работу RAG. Она выделена красной рамкой на визуализации выше.
Архитектура состоит из четырех основных блоков:
-
Подготовка данных (Preprocessing).
-
Загрузка данных в векторную базу (Ingestion).
-
Поиск релевантных документов (Retrieval).
-
Генерация ответа (Generation & Evaluation).
На этапе подготовки мы собираем исходные данные и приводим их к единому виду. После этого каждый документ разбиваем на чанки и загружаем в векторную базу данных.
Когда пользователь отправляет запрос, мы ищем подходящие фрагменты сразу несколькими способами: с помощью векторного и полнотекстового поиска — то есть гибридный поиск. Затем объединяем найденные результаты, ранжируем их и определяем наиболее релевантные документы. Последним этапом модель генерирует готовый ответ на запрос пользователя.
Получение, предобработка и загрузка данных по совести
Как вы уже поняли, страница в системе управления данными — это огромный массив со сложной иерархией. Поразмыслив, мы пришли к структуре, где заголовок первого уровня в Markdown-формате соответствует исходному продукту, а последующие уровни отражают вложенность страниц и разделов.
Затем сырой Markdown-документ проходит дополнительную предобработку в два этапа.
Первый этап — добавление «хлебных крошек». Они позволяют не потерять информацию о том, откуда был взят конкретный фрагмент текста.
Второй этап — обработка таблиц. Очевидно, если граница чанка проходит посередине такой таблицы, нижнюю часть можно спокойно выбросить, без контекста она бесполезна. Поэтому перед загрузкой мы преобразуем таблицы в единый структурированный вид и удаляем ссылки, поскольку они являются мусорными для нашей задачи и занимают токены, не добавляя контекста.
После предобработки документы загружаются в векторную базу данных. Для этого разбиваем данные на чанки в два этапа.
Сначала формируются parent-чанки — крупные фрагменты документа, обычно соответствующие отдельным разделам или подстраницам.
Затем каждый такой фрагмент дополнительно разбивается на небольшие child-чанки, по которым впоследствии и выполняется поиск.
После этого child-чанки преобразуются в эмбеддинги с помощью модели BGE-M3 и сохраняются с расширением pgvector. Помимо самих векторов, в pgvector хранятся текст чанка и дополнительная метаинформация.
Весь процесс мы реализуем в Langflow! Пайплайн логически разделён на две части: верхняя отвечает за обработку parent-чанков, нижняя — за создание child-чанков и их векторизацию.
Langflow — очень удобен сам по себе, но для реализации нашей логики понадобилось написать несколько собственных компонентов. Так в базе данных появились две связанные таблицы с parent-чанками и child-чанками.
Поиск, Reranker и главный инсайт
Пайплайн Retrieval также состоит из четырех этапов. Простой поиск по векторам достает 60–70% нужного. Цепочка из четырех шагов повышает качество до 85–90%.
Первый этап — перегенерация запроса. Система получает пользовательский запрос и создаёт четыре переформулировки с помощью языковой модели. Такой подход позволяет увеличить полноту поиска (Recall), поскольку один и тот же вопрос может быть сформулирован разными способами.
Второй этап — гибридный поиск. Ищем параллельно по смыслу (Vector) и по словам (BM25), чтобы закрывать разные типы запросов.
Третий этап — объединение результатов. Результаты двух поисков объединяем в общий массив с помощью алгоритма Reciprocal Rank Fusion (RRF).
Четвёртый (и самый дорогой) этап — реранжирование. Массив уходит в Reranker, и на выходе мы получаем список наиболее релевантных кандидатов. Например, из 20 найденных чанков Reranker оставляет пять, которые гарантированно содержат ответ на запрос.
Зачем нужен Reranker
На третьем этапе, после объединения результатов с помощью RRF, мы получили ТОП-20 подходящих чанков. Однако Retrieval решил только первую часть задачи — из тысяч документов вытащил ТОП-20 кандидатов, которые с наибольшей вероятностью содержат ответ на запрос пользователя.
Задача Reranker — сузить поле поиска и расположить найденные фрагменты в порядке их реальной релевантности.
Смотрим на конкретном примере. После гибридного поиска формируется общий список чанков, и RRF объединяет результаты в ранжированный список. На первом месте оказывается чанк «Автосекретарь. Главное». После повторного ранжирования он смещается на третью позицию, а первое место занимает раздел «Сравнение продуктов», который содержит значительно более релевантную информацию.
Есть два подхода к реранжированию, которые мы сравниваем.
Первый подход — Cross-encoder (BGE), на котором был построен наш изначальный вариант разработки. Модель читает пару (вопрос, документ) целиком и оценивает, насколько они совпадают по смыслу. Для работы нужна нейросеть и терпение, потому что подход медленный, но точный.
Локально эта модель показывала очень хорошие результаты, и качество ранжирования нас полностью устраивало. Однако при переносе решения в инфраструктуру МТС возникли ограничения, которые мы впоследствии побороли. Библиотека FlagEmbedding не содержала необходимой реализации, поэтому пришлось изобретать костыли.
Второй подход — Metadata-эвристики. Здесь учитываются только теги документа (продукт, категория, тип секции). Нейросеть не требуется, Metadata выдает результат за миллисекунды, но ответ достаточно грубый.
В проде FlagEmbedding недоступен — используем metadata-реранкер. Что это меняет в рамках одного вопроса? FlagEmbedding вывела на первое место раздел «Сравнение продуктов», и модель сгенерировала ответ с почти идеальными метриками. В то же время наш метод Reranker выводил на первое место раздел «Автосекретарь. Контакты и общие вопросы». Этот чанк не содержал нужного контекста, метрики стремились к нулю.
Оцениваем метрики
Локальная версия с BGE CrossEncoder показала заметно лучшие результаты практически по всем показателям. В проде большинство метрик значительно упало, но достоверность ответа (Faithfulness), наоборот, выросла.
Чем мы это объясняем? Модель стала чаще отвечать: «Информация в документации не найдена». Если нужных сведений действительно нет в базе знаний, такой ответ считается технически корректным, при этом контекстуальные метрики падают.
Почему метрики ведут себя странно? Мы нашли четыре причины.
-
Значения RRF score схлопываются. Итоговые оценки оказываются слишком близкими друг к другу, буквально схлопываются в единицу и порядок решают только мелкие эвристические поправки ±0,05.
-
Intent штрафует правильное. Например, при наличии слова «тариф» система искусственно штрафует раздел «Главное», хотя именно он может содержать нужную информацию.
-
Fallback general бустит мусор. Секции без категории получают метку general, например, и страница «Контакты» (справочник телефонов) выходит в топ для вопросов «чем X отличается от Y».
-
Dedup-by-parent фиксирует ошибку. После сортировки берем первый чанк на каждый parent_id. Если Reranker ошибся в порядке — неправильный parent фиксируется, правильный выбывает навсегда.
Теперь посмотрим на итоговые версии, которые мы получили через BGE-модель. Для оценки было подготовлено 72 вопроса. Часть составлена вручную, часть — сгенерирована автоматически. Качество ответов также оценивалось с помощью LLM.
Разбор ошибок позволил выделить три проблемы.
Первая проблема — ошибка сравнения. Retrieval извлекал чанки, связанные со скидками и акциями, хотя вопрос касался различий между продуктами. Модель получала неподходящий контекст и галлюцинировала.
Вторая проблема — сама галлюцинация. Когда пользователь задает исходно-ложный запрос, модель должна ответить, что подобной информации в документации нет. Но наша модель отвечала утвердительно и генерировала ложную информацию. Эта проблема решается, если в системный промпт исходной модели добавить вопросы с отрицанием.
Третья проблема — ошибка автоматического оценщика. Сценарий оказался связан не с самим RAG, а с системой автоматической оценки. Например, на вопрос о ESS-маркировке в автосекретаре модель сформировала очень большой и содержательный ответ, полностью соответствующий найденному контексту. Однако оценщик ожидал короткий ответ, основанный на маленьком контексте, и занизил итоговую оценку.
Разбираем пайплайн на Langflow
На этапе загрузки данных используется уже рассмотренный ранее пайплайн. Часть элементов мы реализовали самостоятельно, поскольку их не было в библиотеке Langflow.
Далее при обработке пользовательского запроса система получает не только сам вопрос, но и дополнительную информацию. Также для каждого пользователя создается профиль и начинается поиск.
Исходный запрос автоматически переформулируется в альтернативные варианты → выполняется гибридный поиск → результаты передаются в Reranker → остается три наиболее релевантных child-чанка и по ним восстанавливаются соответствующие parent-чанки → выполняется дедупликация, чтобы не передавать модели дубляжи → итоговый промпт отправляется в LLM → генерируется финальный ответ.
Нужно объяснить, почему мы выбрали именно Langflow.
Во-первых, у нас уже есть продовый Langflow, куда мы просто загрузили подготовленную JSON-схему и гиперпараметры. После этого система сразу готова принимать пользовательские запросы и возвращать ответы в интегрированные системы.
Во-вторых, к интерфейсу можно предоставить доступ пользователям, которые протестируют систему, зададут вопросы и оставят обратную связь.
Спидран по итоговой архитектуре
Данные преобразуются в Markdown, таблицы приводятся к линейному виду, добавляются хлебные крошки, удаляются ссылки и выполняется дополнительная предобработка. После этого данные загружаются в векторную базу и разбиваются на parent- и child-чанки.
Если у вас есть ресурс, можно искать не по одному запросу, а по комбинации оригинального и модифицированных. Это позволяет находить больше релевантных документов. Например, один и тот же продукт могут называть по-разному: «Автосекретарь 2.0», «автосек», «секретарь» и так далее. Различные формулировки повышают полноту поиска.
Далее используем гибридный поиск для объединения и ранжирования кандидатов, чтобы из тысяч вариантов остались десятки наиболее релевантных. После этого умный Reranker выполняет точное ранжирование, оно добавляется в промпт, и LLM формирует итоговый ответ. Все результаты логируются для последующего анализа качества и расчета метрик.
Выводы и наши планы
В ближайших планах — масштабировать число продуктов, интегрировать полноценный Reranker в инфраструктуру вместо текущей реализации. А для этого нужно починить провалы, расширить покрытие и подготовиться к проду.
Отдельная задача на этом пути — проверить, как технические улучшения отражаются на бизнес-метриках, с которых мы начинали эту историю. Сейчас данных недостаточно, чтобы уверенно говорить о влиянии на конверсию, NPS или время подготовки менеджера к встрече: системе нужно накопить статистику в реальных сценариях. Поэтому после выхода в прод мы сосредоточимся не только на качестве Retrieval и ответов модели, но и на том, помогает ли Co-Pilot менеджерам быстрее находить нужную информацию и насколько полезны его рекомендации.
Главный вывод, который мы сделали по итогу работы над этим проектом: RAG ломается не на LLM и генерации ответа, а на поиске информации, то есть на этапе Retrieval.
Экспериментируйте с разными подходами и делитесь своим опытом исследования RAG в комментариях. Мы рады пообщаться.
Автор: avkaledin


