Мой блог
Как проверяющий ИИ испортил правильные ответы: разбор архитектуры и багов LLM-as-a-judge
В процессе разработки учебных генераторов на базе больших языковых моделей можно столкнуться с крайне коварным классом багов. Модель, выступающая в роли арбитра или проверяющего, в поле финального ответа выдает ошибочную метку, хотя в своем же логическом объяснении приходит к абсолютно верному выводу. На одном из тестов генератора учебных материалов «Игротека» мы зафиксировали именно такую ситуацию. В задании на сортировку орфограмм слово «маши…ый» изначально было верно отнесено к корзине «Две НН». Однако каскад ИИ-проверок переложил его в категорию «Одна Н».
В сохраненном JSON-ответе модели-арбитра поле с вердиктом содержало ошибочный вариант «Одна Н», тогда как в поле с рассуждением нейросеть сама расписала, что на стыке корня «машин-» и суффикса «-н-» по академическим правилам пишется двойная буква. Поскольку логика программы считывала только значение целевого поля ответа, ошибочное решение перезаписало правильный авторский ключ.
В нашем конструкторе учебных игр учитель задает тему, после чего нейросеть формирует комплекты заданий для десяти различных игровых механик (викторины, сортировки, «Детектор лжи» и др.). В этой архитектуре правильный ключ ответа — ключевой элемент: если на интерактивной доске ученику за верный клик высветится ошибка, образовательный процесс будет сорван. Во время испытаний 7 сентября мы дали генератору сложнейшую тему по правописанию «Н и НН в различных частях речи» для 9 класса. В роли всех агентов (от генератора до арбитра) выступала модель claude-opus-5. Я задавал рамки тестирования, а написание кода системы и рефакторинг проводились с помощью Codex и Claude Code с перекрестной проверкой изменений.
Как была устроена схема автоматической проверки
На момент тестирования 7 сентября алгоритм прохождения материалов от генерации до публикации выглядел следующим образом:
- Генерация: по запросу учителя модель формирует единый JSON-пакет с заданиями для 10 игр.
- Базовый валидатор: программный код без применения нейросетей проверяет структуру, отсутствие дублей и запрещенные символы. Брак отправляется на ремонт или полную пересборку темы.
- Независимые решатели: два параллельных запроса к той же модели с одинаковыми параметрами, но разными системными ролями («строгий преподаватель» и «методист»). Они решают задания «вслепую», не зная правильных ответов автора и решений друг друга.
- Арбитраж: если хотя бы один решатель не согласился с авторским ключом, пункт отправляется арбитру. Модель-арбитр получает спорные варианты (без авторского ключа) и выносит вердикт. В прежней версии код записывал ответ арбитра прямо в ключ ответа.
- Финал: задания с текстовыми ошибками отправляются на ремонт. Если после этого остаются неисправленные блоки, тема отклоняется, иначе — попадает в каталог.
«Машинный»: когда у модели-арбитра было право записи
На первом круге проверки по слову «маши…ый» независимые решатели разошлись: один указал «Две НН», второй — «Одна Н». Спорный случай попал к арбитру, который вернул ошибочный вердикт «Одна Н», хотя в цепочке мыслей подтвердил нормативное написание «машинный». Согласно § 95 академического справочника под редакцией Лопатина, при образовании прилагательного от основы на -н («машин-») с помощью суффикса -н- всегда пишется «нн» (аналогично: «длинный» от «длина»).
За первый прогон арбитр переписал три авторских ключа. Другой характерный пример — сочетание «ране…ый в плечо боец». Автор исходно выбрал вариант «раненный», что полностью соответствует § 98 справочника (наличие зависимого слова «в плечо» требует написания двух «н»). Однако оба независимых решателя ответили «раненный» с ошибкой (написав «раненый»), и арбитр согласился с ними. Три запроса к одной и той же нейросети ошиблись коллективно.
Затем в игре «Детектор лжи» одно из правил было признано неисправным, игра ушла на пересборку, и проверка запустилась повторно. На втором круге уже испорченный арбитром ключ «Одна Н» для слова «машинный» воспринимался кодом как авторский! Оба решателя снова ответили «Одна Н», совпали с текущим ключом, и пункт перестал считаться спорным. Таким образом, ошибка, внесенная арбитром, была окончательно узаконена решателями.
Архитектурные изменения: отмена права перезаписи ключей
Мы пересмотрели роль арбитра. Главный вывод: давать нейросети-проверяющему право прямого редактирования правильных ответов нельзя. Теперь арбитр может лишь зафиксировать факт наличия проблемы («с этим пунктом что-то не так»), но не исправлять его.
Если арбитр расходится с автором, авторский ключ остаётся неизменным, а сам пункт направляется на полную пересборку с нейтральным промптом: «ключ не изменён, пересобери пример заново и самостоятельно проверь его». При этом ошибочное объяснение арбитра сохраняется только в технических логах и ни в коем случае не передается в промпт генератору ремонта, чтобы не отравлять его контекст неверными рассуждениями.
Регрессионное тестирование и живые прогоны
Для контроля изменений я написал регрессионный тест test_recorded_wrong_arbitration_never_changes_author_key. Он воспроизводит данный сбой на сохраненных данных: если решатели и арбитр выдают ошибочное «Одна Н» с противоречивым объяснением, тест проверяет, что авторский ключ не изменился, а пункт ушел в пересборку.
На последующих живых тестах новая логика сразу показала свою эффективность. В игре «Прожектор» со словом «ветре…ый» (исключение с одной Н, § 97) автор пометил вариант «пропустить». Арбитр снова выдал противоречивый ответ в JSON: в поле вердикта написал «ловить», а в пояснении указал, что «ветреный» пишется с одной Н и ловить его не следует. Новый код не стал перезаписывать ключ на «ловить», а штатно отправил карточку на пересборку.
Границы и слабости нового подхода
Описанное решение не является абсолютной панацеей. Если автор изначально ошибется, а два решателя и арбитр с ним единодушно согласятся (как в случае с «раненным в плечо»), система пропустит ошибку. Кроме того, отправка карточек на пересборку вместо точечного исправления увеличивает число обращений к API и может приносить новые мелкие дефекты при повторной генерации.
Пара, которую модель починила в уме
В другом тестовом прогоне генератор создал задание для игры «Соедини пары»:
- Слева:
«маши..а вязка» - Справа:
«машинная вязка»
Слева очевидная опечатка: пропущена буква «я» в окончании, и никакая подстановка в место точек .. не превратит «маши..а» в «машинная». Однако все четыре ответа решателей на двух кругах одобрили эту пару. При отправке контрольного запроса арбитру выяснилось, что в поле why нейросеть сама цитирует фразу как «маши..ая вязка». Модель просто «исправила в уме» опечатку и проверила корректное условие, аналогично тому, как человеческий мозг бегло проскакивает опечатки в знакомом контексте. Ученик же в игре получил бы испорченный текст.
Детерминированная проверка кодом вместо слепого доверия ИИ
Доверять логику проверки текстовых соответствий нейросети в таких задачах не требуется — её легко реализовать регулярными выражениями в коде Python. Теперь решатели обязаны указывать тип связи в паре: «раскрытие» (справа то же слово с заполненным пропуском) или «другая» (проверочное слово, синоним, правило).
Если оба решателя пометили пару как «раскрытие», детерминированная функция проверяет подстановку кодом:
def pair_expands(left, right):
pattern = re.escape(tidy_text(left)).replace(r'\.\.', '.*')
return re.fullmatch(pattern, tidy_text(right)) is not None
Строка «маши..а вязка» не совпадает с шаблоном «машинная вязка», и код мгновенно отправляет пару на пересборку без привлечения арбитра. Данное поведение зафиксировано тестом test_wrong_pair_cannot_be_accepted_by_boolean_alone.
Где механическая валидация остается слепой
Детерминированная проверка опирается на метку типа связи, которую устанавливает нейросеть. Если оба решателя ошибочно пометят поврежденную пару как «другая», регулярное выражение не запустится. Кроме того, код проверяет лишь совпадение символов вне пропуска, но не знает, существует ли вообще в русском языке подставляемое внутрь слово.
Колесо и оценки, съехавшие на соседний номер
В игровой механике «Колесо» (где ученик вращает рулетку и объясняет выпавшее правило) мы столкнулись с другим проявлением ненадёжности LLM. При тестировании решатель вернул ответы, в которых текстовые объяснения сдвинулись на один номер относительно анализируемых пунктов:
- Для элемента №5 (
«изране..ый») модель написала вердикт про отсутствие пропуска в слове«путница». - Для элемента №6 (
«путни..ца») выдала пояснение про слово-исключение«нежданный». - Для элемента №7 (
«нежда..ый») привела аргументацию по слову«изранённый».
Структурный валидатор пропустил ответ, так как общее количество пунктов и массив индексов совпадали. В итоге ошибочные оценки едва не попали в финальную сборку.
Обязательный дословный повтор проверяемого текста
Для устранения сдвига мы обязали решателей в обязательном поле w дословно переписывать исходный текст проверяемого пункта. Код Python производит строгую сверку:
if any(not isinstance(r.get('w'), str) or tidy_text(r['w']) != tidy_text(g['wheel']['data'][r['i']]) for r in wheel):
return False
При малейшем несовпадении строк ответ решателя забраковывается и запрашивается повторно (до трех попыток). Тест test_wheel_cannot_attach_verdict_to_another_item гарантирует, что оценка не сможет прикрепиться к чужому пункту.
Как повторить без новых запросов к модели
Чтобы не тратить бюджет и время на постоянные вызовы API при отладке архитектуры проверок, я применил подход с фиксацией API-ответов. Все сетевые ответы моделей сохраняются в JSON-файлы с привязкой к шагу и номеру вызова.
Во время регрессионного прогона реальный API-клиент заменяется заглушкой (mock), которая считывает данные из файлов и прогоняет их через обновленный код валидации. Это позволяет мгновенно тестировать изменения бизнес-логики и сравнивать поведение старой и новой версии алгоритма на совершенно идентичных вводных данных.
Что можно и чего нельзя доверять проверяющим нейросетям
Опыт тестирования и отладки «Игротеки» дает четкие практические ориентиры по использованию LLM в качестве судей (LLM-as-a-judge):
- Права арбитра должны быть ограничены: проверяющая модель может браковать пункты и отправлять их на пересборку, но не должна самостоятельно переписывать эталонные ключи или логику автора.
- Гибридная валидация необходима: детерминированные проверки (регулярные выражения, сверка длин, точные совпадения) должны предшествовать оценкам нейросети и подстраховывать их.
- Промпты требуют контекста правил: добавление прямых цитат из орфографических справочников в промпты решателей существенно снижает количество галлюцинаций.
Этот подход применим далеко за пределами школьной орфографии. В автоматизированном анализе юридических договоров, проверке программного кода или разборе финансовой документации нейросеть-судья эффективна до тех пор, пока её ответ поднимает сигнал тревоги для пересборки или ручного аудита. Как только ИИ-арбитр получает право самостоятельно вносить правки в финальные данные, в системе появляется второй бесконтрольный автор, работу которого придется проверять заново.
Источник: habr.com
