Слишком добрый ИИ-судья: измеряем ложные успехи компьютерного агента

Компьютерный агент кликает по интерфейсу, заполняет формы и закрывает вкладки, а потом какая-то модель решает, справился ли он. Если этот «судья» снисходителен, агент получает награду за незавершённую работу. Числа на дашборде растут, хотя на деле качество не улучшилось, а в обучающие данные попадают траектории, где задача не решена. В статье разберём, как измерить такую снисходительность и сравнить трёх оценщиков по частоте ложных успехов.
Зачем считать именно ложные успехи
Обычно в автоматической оценке смотрят на общую согласованность судьи с человеком. Но ошибки судьи не равноценны. Если модель-судья (LLM-as-a-judge или VLM-as-a-judge: модель, которая по скриншотам и логам выносит вердикт «успех/неуспех») отклоняет хорошую траекторию, вы потеряете один полезный пример. Если же она засчитывает плохую, будет хуже:
- Завышенная метрика. Success rate растёт, но реальный процент выполненных задач остаётся прежним.
- Отравленные данные для обучения. При фильтрации траекторий (rejection sampling) или обучении с подкреплением ложный успех превращается в положительный пример.
- Reward hacking. Агент может научиться делать то, что выглядит как успех: например, открывать нужную страницу и останавливаться, не нажав «Сохранить».
Поэтому главной метрикой здесь будет частота ложных положительных решений (false positive rate, FPR): какую долю на самом деле проваленных траекторий судья пометил как успешные.
Кого сравниваем
Методика рассчитана на три типа оценщиков. Конкретные модели выбирайте сами. В статье нет результатов замеров, только порядок их проведения.
- Универсальный VLM-судья. Мультимодальная модель общего назначения получает описание задачи, финальный скриншот (или несколько последних) и отвечает «SUCCESS/FAIL» с обоснованием.
- Специализированная reward-модель. Модель, обученная оценивать траектории агентов. Она возвращает скалярную оценку, которую вы превращаете в бинарный вердикт по порогу.
- Проверяемый ground truth. Программная проверка состояния среды после эпизода: запрос к базе данных, чтение файла, проверка DOM или API. Это эталон, с которым сравниваются первые два.
Важно: ground truth здесь тоже оценщик, только детерминированный. У него свои ограничения, о них ниже.
Шаг 1. Соберите задачи с проверяемым финальным состоянием
Подходят только задачи, итог которых можно проверить кодом. Хорошие примеры:
- «Создай файл
report.txtсо строкой X»: проверяется чтением файла. - «Измени e-mail в профиле на Y и сохрани»: проверяется запросом к тестовой базе.
- «Добавь товар Z в корзину»: проверяется через API тестового магазина.
Неудачные примеры: «найди интересную статью», «сделай письмо вежливее». У них нет однозначного финального состояния.
Для каждой задачи сохраните запись в JSONL. Вот пример формата, а не реальные данные:
{"task_id": "t001", "instruction": "Измени e-mail в профиле на test@example.org и сохрани", "checker": "profile_email_equals", "expected": "test@example.org"}
Шаг 2. Получите траектории, включая «почти успешные»
Если агент либо полностью справляется, либо падает в самом начале, ложные успехи почти не проявятся. Самые опасные случаи выглядят завершёнными, хотя таковыми не являются:
- форма заполнена, но не отправлена;
- появилось всплывающее «Сохранено», но сохранение упало с ошибкой валидации;
- агент написал «Готово!» в финальном сообщении, хотя последнее действие провалилось;
- изменение сделано не в той записи (другой пользователь, другой файл).
Помимо естественных прогонов агента, полезно намеренно собрать контрольный набор «ловушек»: возьмите успешные траектории и обрежьте их перед последним значимым действием. Такая траектория по построению провалена, но визуально очень похожа на успешную. Помечайте эти случаи отдельно ("source": "truncated"), чтобы потом посчитать метрики по ним отдельно.
Шаг 3. Зафиксируйте ground truth до запуска судей
Сразу после эпизода, пока среда не сброшена, запустите программную проверку и запишите результат. Порядок важен: если проверять позже, можно получить состояние следующего эпизода.
{"task_id": "t001", "run_id": "r17", "source": "natural", "gt_success": false, "gt_detail": "email не изменился: форма не отправлена"}
Судьи не должны видеть gt_success и gt_detail. Иначе это утечка ответа.
Шаг 4. Прогоните судей на одинаковых входах
Чтобы сравнение было честным, все судьи получают одно и то же: инструкцию, одинаковый набор скриншотов и одинаковый текстовый лог действий. Финальное сообщение агента («Задача выполнена») стоит прогнать в двух вариантах, с ним и без него. Так видно, насколько судья доверяет самоотчёту агента.
Шаблон промпта для VLM-судьи (пример):
Ты проверяешь работу компьютерного агента.
Задача: {instruction}
Ниже — последние скриншоты и список действий.
Оцени ТОЛЬКО по видимому состоянию интерфейса, а не по словам агента.
Если нет прямого визуального подтверждения, что изменение сохранено, ответь FAIL.
Ответ строго в формате: VERDICT: SUCCESS|FAIL; REASON: <одно предложение>
Для reward-модели запишите сырой скор. Порог подберите на отдельной калибровочной выборке, а не на тестовой. Иначе FPR получится заниженным.
Итоговая запись на одну траекторию (пример):
{"run_id": "r17", "source": "natural", "gt_success": false, "vlm_verdict": "SUCCESS", "rm_score": 0.41}
Шаг 5. Посчитайте FPR и сопутствующие метрики
Скрипт ниже использует только стандартную библиотеку Python, ничего не устанавливает и не обращается к сети. Он читает локальный файл verdicts.jsonl и печатает матрицу ошибок для каждого судьи.
python3 -m venv .venv
. .venv/bin/activate
python3 score_judges.py verdicts.jsonl --rm-threshold 0.5
# score_judges.py
import argparse, json
from collections import defaultdict
def load(path):
with open(path, encoding="utf-8") as f:
return [json.loads(line) for line in f if line.strip()]
def confusion(rows, pred):
c = defaultdict(int)
for r in rows:
gt, p = r["gt_success"], pred(r)
c[("TP" if p else "FN") if gt else ("FP" if p else "TN")] += 1
return c
def report(name, c):
neg = c["FP"] + c["TN"]
pos = c["TP"] + c["FN"]
fpr = c["FP"] / neg if neg else float("nan")
tpr = c["TP"] / pos if pos else float("nan")
judged = c["TP"] + c["FP"]
prec = c["TP"] / judged if judged else float("nan")
print(f"{name:12} FP={c['FP']:4} TN={c['TN']:4} TP={c['TP']:4} FN={c['FN']:4} "
f"FPR={fpr:.3f} TPR={tpr:.3f} precision={prec:.3f}")
ap = argparse.ArgumentParser()
ap.add_argument("path")
ap.add_argument("--rm-threshold", type=float, default=0.5)
a = ap.parse_args()
rows = load(a.path)
judges = {
"vlm": lambda r: r["vlm_verdict"] == "SUCCESS",
"reward_model": lambda r: r["rm_score"] >= a.rm_threshold,
}
for source in sorted({r.get("source", "all") for r in rows}) + ["ALL"]:
subset = rows if source == "ALL" else [r for r in rows if r.get("source") == source]
print(f"\n== source: {source} (n={len(subset)}) ==")
for name, pred in judges.items():
report(name, confusion(subset, pred))
Что смотреть в выводе:
- FPR — доля провалов, которые судья принял за успех. Это основная метрика статьи.
- Precision — доля реальных успехов среди всего, что судья засчитал. Если вы фильтруете траектории для обучения, именно она показывает «чистоту» датасета.
- TPR — чтобы не было соблазна снизить FPR до нуля судьёй, который всё отклоняет.
- Разбивка по
source: на обрезанных «ловушках» FPR обычно информативнее, чем на естественных прогонах.
Шаг 6. Проверьте устойчивость результата
- Доверительные интервалы. Если провалившихся траекторий мало, FPR сильно шумит. Посчитайте bootstrap-интервал: пересэмплируйте строки с возвращением 1000 раз и возьмите 2,5-й и 97,5-й перцентили. Различие между судьями, у которых интервалы сильно перекрываются, не стоит выдавать за вывод.
- Повторы VLM-судьи. При ненулевой температуре прогоните каждую траекторию несколько раз и посмотрите, как часто меняется вердикт.
- Абляция самоотчёта. Сравните FPR с финальной репликой агента и без неё. Если разница большая, судья верит словам агента вместо того, чтобы смотреть на экран.
- Порог reward-модели. Постройте кривую FPR/TPR по порогам на калибровочной выборке и выберите рабочую точку под задачу: для фильтрации обучающих данных обычно важнее низкий FPR.
Как проверить, что замер корректен
- Вручную просмотрите случайную выборку траекторий, где ground truth и судья не согласны. Иногда ошибается проверка: например, она читает не ту запись.
- Убедитесь, что все «ловушки» действительно получили
gt_success: false. Если какая-то обрезанная траектория прошла проверку, значит, последнее действие не было значимым и ловушка некорректна. - Сверьте число строк: у каждой траектории должны быть вердикты всех судей. Пропуски (таймаут API, неразобранный ответ) учитывайте отдельно и не превращайте молча в FAIL или SUCCESS.
- Проверьте, что калибровочная и тестовая выборки не пересекаются по
task_id.
Типовые ошибки
- Оценка только по последнему скриншоту. На нём может быть видно «Сохранено», хотя предыдущий шаг вернул ошибку. Давайте судье несколько последних кадров или лог.
- Утечка ответа в промпт. Ожидаемое значение в инструкции допустимо («смени e-mail на X»), а результат проверки — нет.
- Неразобранные ответы засчитываются как успех. Парсер, который по умолчанию возвращает SUCCESS, сам порождает ложные успехи.
- Подбор порога на тесте. Так метрики reward-модели оказываются завышены относительно VLM-судьи.
- Сравнение на разных входах. Если одному судье дали лог, а другому только картинку, вы сравниваете входные данные, а не судей.
- Только общая accuracy. Если провалов мало, высокая accuracy может скрывать очень высокий FPR.
Ограничения методики
- Охват. Программный ground truth есть только для задач с проверяемым состоянием. Выводы трудно переносить на открытые задачи, где судья нужнее всего.
- Ошибки самой проверки. Чекер может быть слишком строгим (не принимает эквивалентный результат) или слишком мягким. FPR судей считается относительно него, а не относительно истины.
- Побочные эффекты. Проверка «e-mail изменён» не заметит, что агент заодно удалил что-то лишнее. Если это важно, добавьте проверки инвариантов.
- Смещение набора ловушек. Обрезанные траектории — искусственный класс ошибок. Реальное распределение провалов агента может быть другим, поэтому всегда показывайте метрики по
naturalотдельно. - Нестабильность моделей. Результаты для VLM-судьи привязаны к версии модели, промпту и температуре. Фиксируйте их вместе с отчётом.
Что делать с результатом
Если у судьи высокий FPR, не обязательно сразу от него отказываться. Практичные варианты: сделать ground truth основным сигналом везде, где он доступен, а судью использовать только для непроверяемых задач; ужесточить промпт и запретить опираться на самоотчёт агента; для фильтрации обучающих данных требовать согласия двух независимых оценщиков. После любого такого изменения прогоните замер заново на тех же траекториях. Тогда сравнение «до/после» будет честным.
Что почитать дальше
Другие практические методики оценки и тестирования агентов собраны в разделе руководств. Термины из статьи (reward-модель, ground truth, FPR, reward hacking) объяснены в глоссарии.