У большой базы знаний есть неприятное свойство. Она хорошо отвечает на вопросы, которые в нее уже положили, и никак не сообщает о вопросах, которых в ней не хватает
Пользователь задает вопрос AI-агенту, RAG не находит подходящего материала, и диалог уходит специалисту. Оператор разбирается и пишет правильный ответ. Через несколько дней приходит другой пользователь с той же проблемой, но для Agent ничего не изменилось. Ответ уже существует внутри компании, просто он остался в истории поддержки
Чем больше обращений, тем хуже работает ручной контур. Продакт видит общую долю передач оператору, редактор получает отдельные просьбы обновить базу, а повторяющиеся вопросы растворяются среди тысяч диалогов. В итоге RAG пополняют по ощущениям, громким жалобам и случайно замеченным кейсам
Мы решили построить сервис, который находит такие пробелы автоматически. Он читает обращения, выделяет полезные ответы операторов, объединяет повторы, сравнивает результат с текущей базой знаний и готовит редактору рекомендации
Первая версия уверенно работала на небольших проверках. При переходе к реальному объему проявилось ограничение выбранной схемы: сервис долго обрабатывал данные, расходовал бюджет и не успевал сформировать результат. Дальше задача сместилась от промпта к архитектуре, нужно было определить единицу обработки и оставить LLM только ту работу, где действительно требуется понимание смысла
На первый взгляд решение очевидно. Берем диалог, просим LLM выделить вопрос и ответ, затем добавляем их в RAG
Но ответ оператора еще не является готовым материалом для базы знаний
Оператор отвечает конкретному человеку и учитывает его контекст. Он может написать «в вашем случае попробуйте еще раз», сослаться на уже присланный номер заказа или выполнить действие во внутренней системе. Если убрать исходный диалог, такая реплика становится непонятной. Если неосторожно обобщить ее, частный случай превращается в ложное правило для всех пользователей
Полезный ответ может быть разбросан по нескольким сообщениям чата. В одном обращении могут находиться два независимых вопроса. Похожая инструкция уже может существовать в базе, а десятки пользователей могут описывать одну проблему разными словами
Поэтому задача звучит не как «сгенерировать чанк из диалога». Нужно превратить большой и шумный поток обращений в небольшой набор проверяемых решений для редактора RAG
Мы сразу отказались от автоматической публикации. Сервис должен предложить изменение и показать, откуда оно взялось. Человек решает, создать новый материал, дополнить существующий или отклонить рекомендацию
Что должно попасть к редактору
Полезный кандидат состоит не только из вопроса и ответа. Редактору нужны исходные обращения, количество повторов, найденный материал из текущей базы, предлагаемое действие и объяснение совпадения
Сами кандидаты мы разделили на четыре группы
Новый материал. В RAG нет ответа на этот вопрос
Частичное покрытие. Похожий материал существует, но в нем не хватает важного условия или шага
Полное покрытие. База уже содержит нужную информацию, новый чанк не требуется
Связанная тема. Поиск нашел близкий материал, но он отвечает на другой вопрос и требует ручного сравнения
Если система не отличает новое знание от дополнения, она начинает раздувать RAG почти одинаковыми чанками. Retrieval получает несколько конкурирующих ответов, редакторы перестают понимать, какой из них актуален, а обновление одной инструкции не обновляет остальные копии
Первый рабочий pipeline
Сначала мы отбрасывали диалоги без содержательного ответа специалиста, системные сообщения, финальные благодарности и кейсы, где оператор совершил внутреннее действие, но не сформулировал полезную инструкцию
Затем LLM читала оставшиеся обращения. Она собирала ответ из нескольких реплик, разделяла независимые вопросы, убирала детали конкретного пользователя и формировала кандидаты для базы знаний
После этого каждый кандидат проходил проверку качества и сравнивался с текущими материалами RAG. В конце сервис предлагал создать новый чанк, обновить найденный или ничего не делать
На маленьком наборе все выглядело хорошо. В проверке из пяти обращений система выделила четыре полезных кандидата, собрала ответы из нескольких сообщений, разделила разные темы и отфильтровала пустые результаты
Затем мы перешли к реальным данным. После первичной фильтрации осталось 2316 обращений, в RAG уже находится 1191 чанков
Каскад вместо полного сравнения
На пилоте каждый кандидат можно было семантически сравнить со всей базой. При росте объема число LLM-проверок стало зависеть одновременно от количества обращений и размера RAG. Ускорять каждый вызов было недостаточно, нужно было уменьшить само пространство поиска
Перед LLM мы добавили быстрый лексический фильтр. Он сравнивает кандидата со всей базой и оставляет только 30 наиболее похожих материалов. Фильтр не принимает решение и не пытается заменить семантику, он только убирает заведомо далекие варианты
После этого вместо нескольких десятков обращений к LLM для одного кандидата требовалась одна проверка. В успешном прогоне сервис обработал 1899 обращений за 1 час 58 минут и сформировал 2904 кандидата без технических ошибок
Группировка до оценки
Следующим шагом мы перенесли объединение повторов в начало pipeline. Сначала сервис извлекает кандидатов и группирует их по смыслу, затем выбирает наиболее полный ответ в каждой группе. Только этот ответ проходит дорогую проверку качества и сравнение с RAG, а остальные обращения сохраняются как подтверждающие источники
После удаления повторов в выгрузке осталось 870 уникальных рекомендаций на создание или обновление материалов. Для редактора повторяемость стала не шумом, а сигналом приоритета. Если один пробел встретился в двадцати обращениях, он видит одну рекомендацию с двадцатью подтверждениями
Так стоимость обработки стала зависеть не от общего числа диалогов, а от числа уникальных проблем
Дубли уже внутри RAG
Во время разработки выяснилось, что повторы живут не только среди новых кандидатов. Они уже накопились в базе знаний
Один материал мог быть загружен несколько раз. Разные редакторы могли описать одну тему близкими словами. После миграций могли остаться частично пересекающиеся инструкции
Мы добавили отдельную проверку существующей базы. Сервис быстро оценивал все возможные пары, передавал LLM только наиболее похожие и собирал подтвержденные совпадения в группы
В тестовой базе было 134 чанка. Сравнение каждого с каждым потребовало бы почти 9000 проверок через LLM. Мы сначала отобрали 98 наиболее похожих пар дешевым текстовым фильтром, а уже затем передали их модели. В результате сервис нашел 14 групп возможных дублей
Автоматического удаления мы не добавили. Несколько материалов могут оказаться связанными цепочкой, хотя крайние элементы уже описывают разные случаи. Поэтому редактор видит причины объединения и отдельно проверяет самое слабое совпадение
Для очистки RAG ложное объединение опаснее пропущенного дубля. Пропуск означает лишнюю ручную работу. Ошибочное удаление может уничтожить правильную инструкцию
Разные модели для разных стадий
Извлечение и поиск дублей оказались разными задачами. В первом случае модель читает длинный диалог и не должна потерять второй вопрос. Во втором строго сравнивает два коротких материала. Более дешевая модель хорошо справилась с извлечением, но хуже различала типы покрытия, поэтому модели разделили по ролям
Это упростило и контроль качества. Если сервис не нашел полезный кандидат, мы проверяем извлечение. Если создает дубликаты, сравнение с RAG. Если объединяет разные инструкции, группировку. Один общий score скрыл бы эту разницу
От обращений до новых чанков
Отдельный пилот мы провели на 264 обращениях и RAG из 54 исходных чанков. После фильтрации сервис сформировал 191 кандидат
33 кандидата были классифицированы как полностью новые материалы, 93 как предложения обновить существующие, 35 уже покрывались базой, еще 30 требовали дополнительной проверки качества
Редактор вручную просмотрел 33 новых материала и принял их. После применения рекомендаций база выросла с 54 до 87 активных чанков
Так мы проверили полный цикл: обращение оператора превращается в кандидат, сравнивается с текущим RAG, проходит ручное решение и публикуется как новый материал. Сервис не изменяет базу самостоятельно, но доводит редактора до конкретного действия вместо списка необработанных диалогов
Примерная схема работы сервиса:
Диалоги, переданные оператору
↓
Отбор диалогов с содержательным ответом специалиста
↓
LLM извлекает вопрос и полезный ответ
↓
Похожие обращения объединяются в одну рекомендацию
↓
Кандидат сравнивается с текущей базой RAG
↓
Создать новый чанк / обновить существующий / пропустить
↓
Редактор проверяет и применяет рекомендацию
↓
Обновленный материал попадает в RAG
↓
Agent использует новое знание в следующих диалогах
Что оказалось главным
В начале задача казалась генеративной. Нужно было научить LLM читать обращения и писать новые материалы для RAG
На реальном объеме выяснилось, что главная сложность находится вокруг модели. Нужно определить, что считать знанием, сохранить источник, отличить новое от существующего, объединить повторы, ограничить стоимость и встроить решение человека
Самым важным техническим решением стала смена единицы работы. Стоимость больше не должна расти вместе с количеством обращений. Она должна расти вместе с количеством уникальных пробелов в знаниях
Для поддержки это меняет и продуктовый смысл повторов. Десять одинаковых вопросов не создают десять задач редактору. Они усиливают одну рекомендацию и показывают ее приоритет
В итоге получился не генератор чанков и не система автоматического самообучения. Получился сервис, который собирает рассеянный опыт поддержки, превращает его в проверяемые рекомендации и помогает человеку решить, чему действительно стоит научить AI-агента и его RAG

