Привет, Хабр! Меня зовут Дмитрий Тимохин, я лидер команды ML CRM «Кластер CRM и клиентский опыт» в ВТБ. В этой статье расскажу, как мы разрабатывали сервис анализа и категоризации клиентских обращений для центра клиентской поддержки корпоративно-инвестиционного бизнеса (ЦКП КИБ). 

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

Как собрать из этого потока единое обращение, выделить необходимую информацию, чтобы корректно решить проблему клиента?

Разберемся в статье.

Как выглядел процесс до автоматизации

Как на момент старта проекта, так и сейчас коммуникации с клиентами поступают из разных источников, основные из которых — звонки, чаты и электронная почта. Все взаимодействия по вопросу объединяются в одно клиентское обращение, но бывают коммуникации, которые могут включать сразу несколько вопросов.

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

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

Атрибуты итоговой разметки:

  • тип обращения (консультация или поручение);

  • продукт банка (кредит, депозит, зарплатный проект и другое);

  • вопрос внутри продукта (с каким вопросом по продукту пришел клиент);

  • суть вопроса (детальная информация по вопросу продукта);

  • дополнительный тип обращения (жалоба или пожелание)

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

Анализ подходов к решению задачи

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

Сгенерировали более тысячи обращений – это позволило начать исследовать подходы и проверять гипотезы, которые используются на рынке для решения похожих задач.

1. Суммаризация 

Здесь мы тестировали как специализированные модели, так и LLM с промпт-инжинирингом. В промпты добавляли few-shot и one-shot примеры, чтобы задать нужную структуру и стиль итоговой суммаризации. Дополнительно выделяли из обращения ключевые предложения и передавали их также в LLM вместе с контекстом — это помогало улучшить качество суммаризации и ускорить обработку текста. 

2. Обезличивание информации

Сформированные суммаризации предполагалось использовать для аналитики широким кругом заинтересованных лиц. Поэтому отдельным требованием стало обязательное обезличивание текста: персональные и иные конфиденциальные данные не должны были попадать в итоговые материалы. 

3. Категоризация

Изначально у нас не было разметки итоговых классов и мы не знали, что будем работать с детерминированным справочником продуктов. Поэтому при поиске решений, мы смотрели в сторону инструментов, которые умеют работать с текстами без разметки и автоматически выделять тематики. Одним из первых таких инструментов стал BERTopic — это готовый многоэтапный инструмент для тематического моделирования. Исходный текст проходит через расчет эмбеддингов, снижение размерности, кластеризацию, выделению ключевых слов с помощью c-TF-IDF и генерацию названий для найденных кластеров. Именно на BERTopic мы собрали первое приближенное решение для бизнес-заказчика.

Параллельно мы тестировали и другие подходы, в том числе TopicNet — иерархическую модель тематического моделирования. Инструмент показал себя хорошо, но только при наличии качественной иерархической разметки. Без нее задача почти не решаема.

Отдельно также экспериментировали и с LLM: подавали обращения, просили модель разметить тексты, выделяя их суть и тематику. Контекст мы наполняли уже найденными категориями, чтобы LLM размышляла в рамках списка сгенерированных тематик и не выдумывала синонимичные в последующих итерациях, если то или иное обращение было близко по смыслу.

А потом мы получили данные. Много данных

Пока мы выстраивали решение на синтетических обращениях, от бизнес-заказчика поступили реальные данные из первых источников коммуникаций (чаты и звонки). 

На них мы начали тестировать разработанный нами алгоритм, похожий на BERTopic, но с более гибкой структурой настройки компонентов в общем пайплайне кластеризации и генерации тематик по обращению. 

Решение по классификации и кластеризации обращений:

  • Эмбеддинги — дообученные на банковском корпусе модели на базе deepvk/USER-bge-m3.

  • Снижение размерности — UMAP.

  • Кластеризация — HDBSCAN с подбором гиперпараметров под каждый уровень иерархии (кросс-валидация, оптимизация по Silhouette).

  • Генерация названий кластеров — Qwen2.5-32B-Instruct с передачей номера кластера, ключевых слов (c-TF-IDF) и релевантных документов (MMR).

Пайплайн не справляется с выборкой 

Хотя результаты работы инструмента выглядели убедительно, обратная связь показала, что заказчик использует накопленные справочники продуктов, позволяющие точнее классифицировать обращения и учитывать нюансы, недоступные алгоритму. Многие продукты и типы вопросов семантически слишком близки, поэтому даже дообученные эмбеддинги не могут надежно разделить их без предметного контекста.

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

После анализа, фильтрации, очистки данных от выбросов и шума в выборке осталось около 100 тыс. обращений со следующей структурой:

  • 2 класса — «тип обращения»;

  • 13 классов — «продукт»;

  • 40 классов — «вопрос»;

  • 93 класса — «суть вопроса».

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

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

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

Для поиска смены тематики внутри обращения мы использовали графовый подход на базе NetworkX. Перед анализом текст проходил предобработку и разбивался на смысловые фрагменты (чанки). Для каждого чанка вычислялись эмбеддинги, после чего оценивалась семантическая близость соседних фрагментов по косинусной мере. Если сходство опускалось ниже заданного порога, алгоритм фиксировал смену темы и разделял обращение на отдельные смысловые блоки. 

При построении графа мы логировали весь процесс, поэтому можно было пошагово отслеживать смену тематики внутри одного обращения и видеть, как алгоритм разделяет диалог.

Однако здесь возникло две основные проблемы:

Проблема №1. Выбор типа чанкирования

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

Мы перебрали несколько вариантов чанкирования: отдельные реплики, пары «клиент–оператор», объединенные блоки из нескольких сообщений и разбиение по токенам перекрестным пересечением (overlap). Параллельно запускали на разных порогах косинусной меры — от 0.1 до 0.9 — и смотрели, насколько адекватно алгоритм выделяет смену тематики внутри обращения.

Проблема №2. Отсутствие ручной разметки нескольких тематик в обращении

Для определения наилучших параметров в задаче смены тематик мы использовали LLM как судью (LLM-as-a-judge). В контекст мы передавали само обращение, результат работы NetworkX и набор few-shot-примеров с корректными и ошибочными разбиениями. После сравнивали ответы LLM и результаты алгоритма, подбирая параметры с наиболее точным совпадением смены тематики внутри обращения. 

Дополнительно для каждого канала вручную проверили по 100 обращений. В результате для каждого источника были определены оптимальная стратегия чанкирования и порог косинусной близости. Например, для чатов наилучшие результаты показало разбиение по схеме «вопрос–ответ» с порогом 0,3. 

Суммаризация и дополнительный тип обращения

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

Для этого мы использовали LLM 32B, доступную нам по API в промышленном контуре и контуре для разработки. На вход модель получала текст обращения, а через few-shot примеры мы задавали формат ответа: просили убрать чувствительные данные, сохранить банковскую терминологию и вернуть структурированную суммаризацию вместе с дополнительным типом обращения. Для каждого типа обращения использовали 3–4 примера, что повысило точность их определения. 

С обезличиванием всё оказалось проще: большинство чувствительных данных (номера счетов, даты, ФИО, суммы и др.), стабильно определялись регулярными выражениями совместно с библиотекой Natasha. Это позволило отказаться от трансформерных моделей, снизив требования к памяти и повысив скорость работы сервиса. 

Далее мы приступили к обучению классификаторов. Анализ распределения согласованных классов показал, что несколько доминирующих классов категории «продукт» перетягивали на себя более 90% всех наблюдений в выборке, а также их подклассы «вопрос» и «суть вопроса». Доля остальных продуктов была низкой, но достаточной, чтобы отделить их от других классов по смыслу. Поэтому для балансировки выборки мы сгенерировали дополнительные обращения для недостаточно представленных классов с помощью LLM 32B. 

При аугментации обращений в контекст модели мы передавали M случайных примеров из категории и веса этой категории для равномерного распределения итогового количества наблюдений класса. К примеру, категория «Торговый эквайринг», по которой всего 300 наблюдений, не самая маленькая, поэтому ее вес должен быть ниже, и на выходе мы получаем N * (вес категории) наблюдений. По каждому уровню классификации мы проводили от 15 до 30 итераций генерации, стараясь сбалансировать распределение.

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

Ниже пример генеральной совокупности после обогащения:

Обучение классификаторов и выбор класса

В качестве базовой модели эмбеддера мы выбрали deepvk/USER-bge-m3 — русскоязычную версию семейства BGE-M3. Поверх модели были добавлены классификационные головы для каждого уровня иерархии, после чего мы разморозили часть слоев (в среднем пять), увеличили dropout для борьбы с переобучением и быстрой сходимости в последних трех слоях и приступили к дообучению. 

Однако здесь нас поджидало несколько проблем:

  1. Архитектура задачи сложнее, чем обычная мультиклассовая классификация. Еще на этапе экспериментов с BERTopic мы заметили — многие категории оказались слишком близки семантически, а редкие классы модель путала с соседними или относила к шуму.

  2. Противоречия. Независимое обучение классификатора для уровней «продукт», «вопрос» и «суть вопроса» приводило к противоречивым результатам. Какому классификатору доверять? Можно ориентироваться на метрики — чем больше, тем лучше, однако с ростом числа классов модель только сильнее «сомневается» и хуже различает близкие категории.

  3. Похожие подкатегории. На уровне «суть вопроса» встречались практически одинаковые сущности, и тогда одного текста обращения уже было недостаточно — требовался контекст и дополнительные признаки.

  4. Авария. Сбои на стороне ЦКП могут ошибочно отнести большое число обращений к одной категории, зашумляя данные. Предотвратить такие ошибки заранее невозможно.

  5. Новички. Из-за недостаточной экспертизы отдельных клиентских менеджеров часть обращений ошибочно относилась к другим классам, что снижало доверие к качеству разметки. 

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

В нашем же случае мы знаем цепочку категорий от начала до конца, что облегчает задачу. На первом и втором уровнях классификации берем топ-3 наиболее вероятных классов, перемножаем вероятности между собой и выбираем вариант с наибольшим значением. После извлекаем класс из первого уровня «продукт» и обучаем внутри него третий уровень «суть вопроса».

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

Определение класса «неизвестная категория»

Мы планировали постепенно автоматизировать процесс разметки и плавно перейти от детерминированного справочника к динамическому, в котором будут появляться новые категории с текущими предложениями, вопросами и проблемами ЦКП. 

Так мы решили ввести дополнительную, «неизвестную» категорию, — на случай, когда модель недостаточно уверена в своем решении. Например, если на верхнем уровне вероятности между «кредитным», «дебетовым» и «расчетным» счетом распределялись почти одинаково (по 31% на каждый класс) это означало, что алгоритм не может уверенно выбрать нужную категорию. Или же когда появляются новые типы обращений, которые ранее не встречались во время обучения — алгоритм также будет накапливать такие наблюдения в «неизвестной» категории и со временем преобразует их в новый класс.

Для подбора порога «уверенности» модели мы собрали отдельную выборку из категорий, не участвовавших в обучении (~10–15% от валидационной выборки). Дальше запускали алгоритм, который одновременно максимизировал F1-score и минимизировал долю «неизвестных» категорий. На пересечении этих двух условий мы находили порог уверенности модели: если вероятность предсказания оказывалась ниже порога, обращение отправлялось в «неизвестную» категорию на соответствующем уровне.

Результаты обучения классификаторов

Перейдем к результатам обучения классификаторов для каждого уровня иерархии. На рисунке ниже представлены средние значения F1-score по кросс-валидации, а также результаты на валидационной и отложенной выборках:

Как и ожидалось, качество классификации снижалось от уровня к уровню из-за роста количества классов. На отложенной выборке F1-score составил 77% для уровня «продукт», 60% для «вопроса» и 40% для «сути вопроса».

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

Позже, чтобы провести более объективную валидацию, бизнес-заказчик сформировал отдельную выборку обращений. Из нее были удалены шумы и ошибки, исключены категории, которые отсутствовали в справочнике на момент обучения модели. Далее сотрудники ЦКП вручную проверили результаты, сопоставляя фактические категории с предсказаниями модели. 

С учетом этих факторов итоговые метрики выросли до 90%+, что устроило и нас, и бизнес-заказчика.

Как выглядит финальная архитектура решения

На рисунке ниже показан полный пайплайн обработки клиентского обращения:

На вход система получает коммуникации из разных источников и объединяет их в единое обращение. Далее рассчитывается количество токенов и определяется маршрутизация для суммаризации всего обращения: либо обрабатывается весь контекст целиком, либо объединяются суммаризации отдельных коммуникаций. Для каждой коммуникации мы обращались в LLM 32B, чтобы извлечь атрибут «дополнительный тип» и «суммаризация», которая обезличивались и передавалась на вход последовательным классификаторам. 

Архитектура проекта получилась максимально декомпозированной: отдельные модули можно заменять, отключать или дорабатывать без влияния на весь пайплайн.

Для демонстрации бизнес-заказчикам был разработан UI на базе инструмента Streamlit. В интерфейс загружалось обращение, а на выходе отображались все результаты обработки: смена тематик, суммаризация, уровни классификации, дополнительный тип обращения и другие рассчитанные атрибуты. 

Итоги и планы

Разработку MVP мы начали в феврале 2025 года, а уже в мае–июне представили первую версию решения с согласованными продуктами и классами, которые покрывали большую часть обращений по двум каналам. 

После согласования MVP перешли к полноценной разработке: в сентябре 2025 года внедрили версию со всеми отфильтрованными классами, а в ноябре провели первое дообучение моделей на обновленном справочнике. 

Сейчас идет второй цикл обучения: заказчик предоставил новую версию справочника, а накопленный за счет логирования и автоматизации объем обращений позволил существенно повысить качество на отложенной выборке. 

Параллельно анализируем обращения, которые попадают в «неизвестную» категорию — сейчас их около 10%. Именно они помогают находить новые категории и постепенно двигаться от ручной разметки к более автоматизированному справочнику.

Над сервисом работали: 

Дмитрий Тимохин – лидер команды ML CRM – https://github.com/dmitrytimokhin

Денис Губриенко – разработчик команды ML клиентский опыт – https://github.com/Ziraelsik

Роман Гончар – разработчик команды ML клиентский опыт – https://github.com/gonchar-rp

Василий Сизов – лидер кластера CRM и клиентский опыт – https://github.com/Vasily-Sizov