Why NextWhy Next
К списку
💻 Разработка11 мин чтения

Пайплайн, где код, исправленный одним ИИ, опровергает другой ИИ

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

Главное

ИИ, который сам судит свою работу, снисходителен к себе. Попроси другую модель «предположить, что это исправление ошибочно, и опровергнуть его», и всплывают дефекты, пропущенные само-ревью. Случай, когда из одного бага с логаутом три раунда подряд выходили всё более глубокие дефекты; устройство промптов, включая фрейминг опровержения и формат доказательств; и критерии, когда запускать эту петлю.

Содержание

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

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

Поэтому я вынес судейство наружу. Код, исправленный Claude, я передаю Codex и прошу ровно об одном. «Предположи, что это исправление ошибочно, и опровергни его.»

Три раунда подряд находился всё более глубокий баг

Ценность этой петли я впервые ощутил на баге с логаутом. Симптом был простой: нажимаешь кнопку выхода, а выход иногда не происходит. Claude нашёл причину и исправил, и если просто читать код, всё выглядело правдоподобно. Я чуть было не смержил.

Дал Codex задание на опровержение, и в первом раунде пришёл такой ответ. Само исправление верное, но редирект на странице аутентификации перехватывает поток раньше, чем выполнение доходит до этого участка кода, так что у исправления нет шанса сработать. Исправленный код был мёртвым кодом.

Починил и редирект, снова запустил опровержение. Во втором раунде вылезло ещё. На этот раз проблемой оказался флаг «в обработке», поставленный против повторного запуска: при определённом тайминге из-за этой защиты сам запрос на выход превращается в no-op и не делает ничего. Только в третьем раунде пришёл ответ «не нашёл, что опровергнуть», и лишь тогда я смержил.

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

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

Не ревью, а опровержение

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

К этому я добавляю три условия.

  1. Формат доказательств обязателен. Каждое замечание должно сопровождаться файлом и номером строки, а также сценарием воспроизведения, в котором этот дефект действительно срабатывает. «Это место выглядит опасным» не принимается. Не смог построить сценарий - значит, это подозрение не замечание, а отклонённая гипотеза. На практике опровергатель довольно часто сам опровергает собственное подозрение и снимает его, и именно это ключевой механизм отсева ложных срабатываний.
  2. Вердикт приходит в виде оценки. Пропустить / пропустить после мелких правок / блокировать: трёх ступеней достаточно. Когда есть ступени, состояние «замечания есть, но мержить можно» не смешивается с состоянием «мержить нельзя».
  3. Запуск в независимом контексте. Опровергателю даётся только diff и связанный код. Ни диалога из процесса работы, ни повествования «почему исправлено именно так» он не получает. Стоит поделиться повествованием, и опровергатель заражается тем же заблуждением.

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

Когда запускать

Не на каждое изменение я гоняю эту петлю. Звать опровергателя на исправление опечатки - расточительство. Безусловно запускаю на изменениях, которые трудно откатить. Сюда входят миграции DB, удаление данных, платёжные и аутентификационные пути. Изменения с заявлением «починил» тоже в списке. Исправлению бага положена пара доказательств: баг воспроизводился до правки и исчез после, и опровергатель хорошо находит бреши в этой паре. И последний случай - изменения, которые я не собираюсь читать целиком. Идея, что человек прочитает весь большой diff, сделанный ИИ, на практике разваливается. Раз уж читать не буду, пусть хотя бы другая модель прочитает враждебно.

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

Почему модель должна быть другой

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

У разных моделей внимание цепляется за разное. Одна чувствительна к таймингам в управлении состоянием, другая - к нарушениям контрактов и граничным значениям. Две сессии одной модели делят одни и те же слепые зоны, а у разных моделей слепые зоны не совпадают. Ценность перекрёстной проверки рождается ровно из этого несовпадения.

Эта конструкция не должна казаться незнакомой. По той же причине человеческие команды поручают код-ревью не автору. Работа с ИИ этот принцип не отменяет - она лишь позволила применять его дёшево и постоянно. Судья, который не является автором; взгляд, не заражённый повествованием; и одобрение, которое появляется только после неудавшегося опровержения. То, что человеческие организации делали изредка, потому что дорого, теперь можно поставить перед каждым мержем.

Похожие статьи

Комментариев: 0