У нашего AI‑агента поддержки было десять метрик и ноль ответов на главный вопрос: какую долю обращений он решает?

Судья, который читал только текст диалога, ставил 18/20, а проверка по реальным вызовам инструментов давала 8/20. Текстовые проверки считали правдоподобный ответ верным и не могли отличить его от подтвержденного. Самодельный «процент фантомных эскалаций» оказался измерением того, насколько слова бота согласованы с его же внутренними флагами. Каждая метрика что‑то измеряла, и ничего продуктового

Тогда мы решили: оценка качества станет отдельным сервисом с собственным источником истины. В этой статье два переписывания формулы вердикта, несколько пойманных слепот и один эталонный набор, который учил нас доверять неправильным ошибкам

Что из этого вышло

Версия ноль: один вопрос и шесть ответов

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

resolved — решено
unresolved — не решено
escalated — передано человеку
needs_info — агент ждет данные от пользователя
unknown — не смог классифицировать
excluded — диалог вне оценки

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

Первая же проверка на реальном трафике дала два числа: 34,7% решенных на одной выборке и 48,7% на другой. Сравнивать их было нельзя, разные кабинеты, интенты, полнота данных. Точный процент создавал ощущение, что метрика существует. На самом деле существовал только некалиброванный классификатор

Заодно мы получили первую техническую проверку: Judge совпал с системным флагом эскалации в 40 случаях из 41. Это подтверждало, что он видит сам факт передачи, но еще ничего не говорило о ее правильности. Механика работала. Теперь надо было проверить смысл

Первая калибровка: 70% и главный класс ошибок

Коллега разметила вслепую 50 диалогов. Мы сравнили ее метки с вердиктами Judge: согласие 70%, каппа Коэна 0,584

Каппа — это согласие двух оценщиков с поправкой на случайные совпадения. Для нашей выборки 0,584 означало умеренное согласие, но не готовую продуктовую метрику. Повторный прогон дал 68% и 0,561. Этот разброс заставил нас фиксировать версию промпта и входные данные, а каждую кандидатную версию запускать не меньше трех раз. Позже на таком зафиксированном состоянии три прогона дали одинаковый результат

Главный класс расхождений оказался продуктовым, а не лингвистическим. В 8 случаях из 50 Judge считал передачу оператору допустимым исходом, а человек ставил «не решено»: агент передал разговор слишком рано, не собрав обязательные данные

Мы сделали очевидное, добавили в промпт правило «перед передачей собери недостающее». Кандидат починил три диалога и сломал два. Каппа сдвинулась на 0,018. Такое изменение осталось глубоко внутри шума

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

Это был первый полезный результат Judge, и он был отрицательным: судья не стал лучше, но система не позволила принять перестановку ошибок за прогресс

Первая смена формулы: от мнения к вычислению

Holistic‑судья с инструкцией «Прочитай диалог и скажи исход» давал число, но не давал причины. Когда вердикт казался ошибочным, мы не могли понять, что именно сломалось. Поэтому мы переписали механизм: разложили «решено» на сигналы и стали вычислять его формулой

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

Первый прогон против человеческих меток: каппа −0,13 против 0,73 у holistic‑судьи. Отрицательное значение означало, что формула согласовалась с людьми хуже случайного угадывания

Выглядело как провал подхода. Разбор показал другое: формула судила не тот диалог. Судья не видел фактов

— вызовы инструментов лежали в одном канале данных, а читался другой;

— результаты инструментов не доходили до оценки;

— межходовой контекст терялся, и подтвержденный на втором ходе факт выглядел выдуманным на пятом;

— сырые условия сценария («если статус отменен») попадали в судью как свершившиеся факты

Самый показательный класс: Judge уверенно помечал «агент выдумал название компании». В трейсе находился вызов инструмента пользовательских данных, а название было настоящим, просто судья его не видел. Несколько таких случаев попали в разметку как галлюцинации и были откачены после проверки по трейсу: убедительнее всего выглядит ошибка, у которой есть правдоподобное объяснение

Мы чинили не формулу, а канал данных по одному слою. Каппа шла так: −0,13 → +0,09 → 0,27 → 0,44 → 0,56. К концу первой итерации формула дала 0,529 ± 0,024 против 0,732 у holistic. После исправления оставшихся каналов инструментов и прокси корректности эскалации каппа стала 0,684 ± 0,023.

Формула осталась в системе, но не как главный вердикт, а как диагностика: она объясняла причины, а заголовок ставил holistic‑судья. Забегая вперед: через неделю формула догнала и обогнала его, но для этого ей нужно было наконец увидеть прод

Полный контекст и осторожная победа

Когда Judge начал получать текст, вызовы инструментов, их результаты и найденные фрагменты базы, качество структурных проверок заметно изменилось. Класс «не тот сценарий» сократился с 15 ложных срабатываний до 1, а «сценарий не определен» в объяснениях с 96% до 24%

Параллельно Judge отвязали от конкретного кабинета: он автоматически подхватывает определения сценариев и сопоставляет инструменты по их роли, а не по имени

После этих изменений формула с межходовым контекстом достигла каппы 0,75 на эталоне и обошла holistic‑судью с его 0,685. Holistic был выпилен полностью: минус без малого тысяча строк, одна версия истины вместо двух, единый вердикт

Заодно мы сократили число вызовов LLM с двенадцати до четырех на диалог: семантические судьи запускаются только на решающем ходе, и только если структурные проверки не дали ответа. Эквивалентность проверили на 103 диалогах: ни один вердикт не изменился

Здесь важно назвать ошибку, которой мы чуть не сделали: 0,75 была получена на том же наборе, по которому настраивались сигналы. Это калибровка, а не доказательство генерализации. Число приятно, но хвастаться им все равно что хвастаться оценкой за экзамен, который списали по шпаргалке

Вторая смена формулы: бизнесу нужен был другой вопрос

Самое большое переписывание началось не с бага. Продакт задал вопрос «Что именно должен измерять Judge?»

Вопрос «Решена ли проблема пользователя?» оказался плохим критерием для оценки агента, потому что у исхода слишком много причин вне его контроля. Пользователь может не прислать номер заказа. Бэкенд может вернуть ошибку. Сценарий может требовать живого специалиста. Корректным результатом шага может быть уточняющий вопрос и прочее

Решение: оценивать не исход, а поведение

Судья отвечает не на вопрос «решена ли проблема», а на вопрос «действовал ли агент корректно по инструкции и доступным данным»

Формула сжалась до двух главных проверок: подтверждены ли утверждения Agent источниками и дал ли он содержательный ответ. Правильность выбора сценария осталась диагностическим сигналом, но перестала определять главный вердикт: если пользователь получил корректный ответ, внутренний маршрут сам по себе не должен превращать его в «НеОК». И целый класс вердиктов перевернулся:

— корректный запрос недостающих данных — теперь положительный исход;

— оправданная передача человеку — положительный исход;

— незавершенность пользовательской проблемы больше не означает ошибку агента

Внешний словарь мы не трогали, API как возвращал «решено / не решено / вне оценки», так и возвращает. Поменялось наполнение. Это решение дало побочный эффект, о котором стоит предупреждать: если команда продолжает называть метрику «resolution rate», а измеряет уже корректность поведения, каждый в команде начинает читать один график по‑своему

Операторский фидбек как внешний сигнал

После смены формулы собственной ручной разметки стало недостаточно. Мы сопоставили вердикты Judge с 61 операторской оценкой Agent на реальных диалогах

Совпали 44% оценок. Но это число нельзя считать точностью Judge: оператор оценивал итог работы Agent, а Judge оценивал корректность поведения в рамках доступных инструкций и данных. Часть расхождений относилась к тону ответа, выбору сценария и работе внешних инструментов. Эти факторы находились за пределами тогдашней формулы

При этом сравнение дало полезный диагностический сигнал. В 28 случаях оператор оценил работу отрицательно, а Judge вернул положительный вердикт; обратных расхождений было шесть. Разбор трейсов выделил повторяющийся кластер из 13 диалогов: Agent сообщал, что действие выполнено, но соответствующего вызова инструмента в трейсе не было

Здесь обнаружились две независимые задачи: разобраться, почему Agent не выполнил действие, и научить Judge замечать этот факт. Первая относилась к движку и внешним инструментам и расследовалась отдельно. Вторая была на стороне Judge: структурная проверка читала не все каналы вызовов инструментов

В Judge мы объединили эти каналы и добавили детерминированный сигнал «действие заявлено, но не исполнено». На той же выборке он нашел все 13 известных случаев; точность положительных срабатываний (precision) составила 0,93, а совпадение с операторскими оценками выросло с 44% до 69%. Это не универсальная оценка качества Judge, а результат для одного проверенного класса ошибок

Главный вывод был не в выборе модели. Если факт уже записан в состоянии системы, LLM не должна угадывать его по тексту

Для технических сбоев Judge мы ввели отдельный статус: такая оценка не влияет на метрику качества Agent и помечается для повторного запуска

К какой формуле мы пришли

Главный результат Judge для каждого диалога принимает одно из трех состояний: ОК, НеОК или вне оценки. На уровне API им соответствуют resolved, unresolved и out_of_scope.

Judge анализирует весь диалог: запросы пользователя, предыдущие ответы Agent, найденные фрагменты базы и историю вызовов инструментов. Промежуточные ответы остаются в диагностике, а структурные события учитываются по всему трейсу. Главный семантический вердикт относится к последнему ответу Agent по существу. Именно он показывает, чем закончился разговор. Ранняя ошибка не должна перевешивать последующее исправление, а хороший промежуточный ответ не должен маскировать плохой финал

Для этого решающего хода действует формула:

ОК = grounded AND complete

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

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

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

Главным агрегированным показателем стала доля «ОК» среди оцененных диалогов:

OK rate = ОК / (ОК + НеОК)

Диалоги «вне оценки» в знаменатель не входят, но их доля контролируется отдельно

Judge сегодня: что именно доказано

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

70%, κ 0,584. Первая калибровка holistic Judge по человеческой разметке. В выборке было 50 диалогов, использовался критерий «решен / не решен»

κ 0,75. Результат формулы после калибровки. Получен на том же наборе, поэтому это не независимая контрольная выборка

44% → 69%. Так изменилось совпадение с 61 операторской оценкой Agent. Это внешний диагностический сигнал, а не прямая разметка Judge

Precision 0,93 и 13 из 13 найденных провалов. Результат детектора невыполненных действий на проверенном классе ошибок, а не на всем трафике

0% → 100%. Так изменилось покрытие структурного типа эскалации на одном движке в проверенном окне продакшена

12 → 4 LLM-вызова на диалог. После оптимизации вердикт совпал на всех 103 контрольных диалогах

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

Что мы проверяем до любого изменения судьи

1. Какой вопрос задает метрика. Вопросы «решена ли проблема», «корректно ли действовал агент» и «удалось ли избежать оператора» описывают три разные метрики. Смена вопроса ломает сравнимость графиков оценки

2. Каждый диалог получает один исход. Дроби с плавающим знаменателем становятся источником ложных трендов

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

4. Факты состояния считаем кодом, семантику отдаем LLM. Вызов инструмента и эскалация не должны угадываться по тексту

5. Эталон не создается по объяснениям судьи. Нужны слепые метки от людей, которые видят реальный процесс

6. Любая правка идет через контрольный барьер. Минимум три прогона, блокировка при регрессии

Вывод
Judge начинался как «отправим диалог модели и спросим: решено?». Он прошел две смены формулы. Сначала сменился механизм, и мнение LLM заменила формула над фактами, потому что у мнения не было объяснимости. Потом сменился смысл, и вопрос «решено ли» заменили на «действовал ли корректно», потому что бизнесу нужен был измеримый агент, а не измеримый пользователь

Обе смены выглядели как шаг назад в моменте: первая начиналась с каппы −0,13, вторая обнулила сравнимость всех предыдущих графиков. Но каждая следующая версия ломалась о более честные источники истины. Ими стали человеческие метки, операторский фидбек и факты из трейса.

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

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