Модель предлагает, код исполняет: граница между рассуждением и действием

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

· 3 мин чтения

Пользователь просит AI-помощника оформить возврат платежа за заказ. Модель разбирает свободный текст, находит заказ, выбирает причину и вызывает API. Через 30 секунд оркестратор получает timeout. Что делать дальше: повторить запрос, спросить статус операции или передать случай человеку? Если вся логика спрятана в одном промпте и одном цикле агента, ответ приходится угадывать вместе с моделью. Первый вызов мог не дойти до API, мог завершиться успешно без ответа, а мог зависнуть после частичной записи. Слепой повтор способен создать второй возврат. Отказ от повтора оставит первый запрос в неопределённом состоянии.

Андрей Непряхин из AGIMA предлагает перенести границу между рассуждением и действием. Модель не вызывает API напрямую. Она возвращает proposal, ограниченную структуру данных. Внешний эффект исполняет детерминированный код, который заново проверяет схему, перезагружает факты из системы, проверяет права и решает, нужен ли человек.

Что такое proposal

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

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

Почему это работает

Контракт становится тестируемым по частям. Для proposal нужны датасет и оценка смысла, это eval LLM-компонента. Для проверки бизнес-правил достаточно обычных unit-тестов. Для исполнения нужны интеграционные тесты с обрывом соединения и повторной доставкой событий. Одна итоговая метрика не покажет, где именно возникла ошибка.

Retry становится контролируемым. RFC 9110 разрешает повторять запрос после сбоя соединения только если операция идемпотентна или клиент может доказать, что исходный запрос не применён. С proposal и явным operation_id, сохранённым до первого внешнего вызова, появляется шанс восстановить состояние и понять, что именно успело произойти.

Человек в контуре подключается точечно. Осмысленный триггер соединяет две величины: формальный критерий неполон и ошибка дорого стоит. Для низкорискового черновика ручное подтверждение каждого шага создаст очередь без пользы. Для необратимой операции даже высокая уверенность модели не заменит прав и явного одобрения.

Кому какой шаг

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

Как применить

  1. Перечислите все внешние записи, отправки и изменения прав в вашем пайплайне. Вынесите их из цикла агента в отдельные функции.
  2. Введите типизированный proposal и запретите модели передавать в исполнитель произвольные аргументы, только поля фиксированной схемы.
  3. Добавьте последовательность гейтов: schema, business и permission. Для каждого отказа задайте reason code и следующий маршрут.
  4. Сохраняйте operation_id до первого внешнего вызова, иначе рестарт между вызовом API и записью разорвёт связь с операцией.
  5. Разведите четыре контура: offline eval, release gate, runtime gate и monitoring.
  6. Заведите в журнале два отдельных поля: «что решила модель» и «что сделала система». В первой итерации у Непряхина одно общее поле скрыло источник проблемы: 90 из 99 блокировок оказались ложными и приходились на системные промпты кодинг-агентов.