Почему агент работает на тестах и ломается на перефразировках
Агент стабильно проходит тестовый набор и разваливается на перефразировках не потому, что модель стала хуже. Причина в том, что граница между кодом и моделью не спроектирована: решения, которые должны быть жёсткими, отданы генерации.
Картина типичная. Двадцать тестовых вопросов, девятнадцать пройдено, форма ответа всегда одна. Потом тот же по смыслу вопрос задают иначе: «покажи продажи за квартал», «сколько мы продали за последние три месяца», «динамика выручки за Q3». В одном случае агент возвращает таблицу, в другом строит график, в третьем просит уточнить период. Семантика одна, поведение разное.
Добавление правил в промпт не лечит проблему, а маскирует её. Каждое новое правило вступает в конфликт с уже написанными, и модель начинает разрешать противоречия сама. Нестабильность переезжает из поведения агента в порядок применения противоречащих инструкций: на тестовых вопросах конфликт разрешается в нужную сторону, на перефразировках - в любую.
Чем ошибка агента отличается от ошибки чат-бота
Чат-бот оставляет человеку роль главного оператора процесса. Ошибка чат-бота чаще всего остаётся ошибкой в промежуточном результате: сотрудник видит неточность, правит формулировку, отправляет ответ сам. Плохой ответ стоит времени, но не выходит за корпоративный контур.
Агент меняет схему. Человек ставит общую цель, например обработать обращение клиента, а последовательность действий система выстраивает сама: находит информацию во внутренних системах, проверяет историю взаимодействия с заказчиком, готовит ответ, вносит изменения в CRM, передаёт задачу следующему сотруднику. Ошибка агента может сразу превратиться в действие. Если система отправила письмо клиенту, изменила данные в CRM, выдала пользователю права доступа или инициировала платёж, исправить ответ в диалоговом окне уже недостаточно.
Практический смысл границы код/модель в том, что она ограничивает зону необратимых действий. Всё, что нельзя откатить одной правкой в интерфейсе, должно быть результатом решения кода, а не вероятностного выбора модели.
Почему промпт не удерживает архитектурное решение
Промпт конфигурирует модель. Архитектурный контракт он не задаёт. Разница простая: контракт описывает, какие решения система принимает, кто их принимает и что происходит при отказе. Промпт описывает пожелания на естественном языке, которые модель выполняет с высокой вероятностью, но не с гарантией.
Аналогия с кодом прямая. Если бизнес-логику вынести в комментарии, она не станет исполняемой: комментарий прочитает человек, на выполнение он не влияет. Правило в промпте ведёт себя так же, только «читателем» выступает модель, которая взвешивает инструкцию вместе с сотней других.
Масштаб эффекта зависит от объёма инструкций: чем больше правил в системном промпте, тем выше шанс, что часть из них применится в неожиданном порядке. Отдельно разбирали, почему большие LLM хуже следуют инструкциям и теряют контекст, вместе с протоколом проверки регресса.
Из этого не следует, что промпты бесполезны. Тон, стиль, формат изложения, выбор формулировки уточняющего вопроса - законная зона модели. Проблема начинается там, где в промпт уезжает то, что должно быть решением кода: определение категории запроса, право на необратимое действие, причина отказа.
Что такое граница код/модель и почему это архитектурный объект
Граница код/модель - это явное разделение того, что делает код, и того, что делает модель. Архитектурным объектом она становится, когда её фиксируют в проекте: перечень решений агента, зона ответственности каждой стороны, формат передачи данных между ними.
Пока граница не нарисована, она всё равно существует, просто складывается случайно: из того, что оказалось проще написать в промпте, что осталось от прошлых итераций, что не успели вынести в код. Случайная граница не воспроизводится. При смене модели, версии, температуры или формулировки запроса решения распределяются иначе, а поведение системы меняется без единой правки в репозитории.
Проектировать границу явно означает ответить на три вопроса по каждому решению агента: кто его принимает, на основании чего и что произойдёт, если ответ окажется неверным. Такой перечень удобно вести рядом с архитектурной схемой, а не внутри системного промпта. О том, как превращать архитектурные решения в контракты, которые читают и человек, и агент, есть отдельный разбор: проектирование вместо кода.
Критерий: перечислимые структурные признаки против интерпретации
Практический критерий короткий. Если решение сводится к перечислимым структурным признакам, оно принадлежит коду. Если решение зависит от смысла и неоднозначности, это зона модели.
Разберём на примерах. Есть ли в запросе явно указанный период или поле «дата»? Код: это разбор дат по формату. Относится ли запрос к категории «отчёт» по наличию метрики, периода и разреза? Код: набор сущностей перечислим. Как сформулировать уточняющий вопрос, если период не указан? Модель: нужен естественный язык. Как объяснить пользователю, почему данных нет? Модель, но причина приходит из кода.
Ловушка в том, что такие задачи кажутся слишком простыми для кода, и их отдают модели. Простота задачи не связана с уверенностью модели. Классификация по трём ключевым словам выглядит тривиальной, и именно она даёт разное поведение на перефразировках, потому что модель решает её заново на каждом запросе, а не применяет фиксированное правило.
Почему разные логические вопросы нельзя сворачивать в одну реакцию
Если у одного действия несколько логически разных причин, сводить их к одной общей реакции нельзя. Пример: агент отказывается выполнять запрос. Причины разные. Данных за запрошенный период нет. У пользователя нет прав на этот разрез. Запрос допускает два взаимоисключающих прочтения. Пользователь во всех трёх случаях видит «не могу выполнить запрос», а код не различает ситуацию и не может выбрать правильный следующий шаг: предложить другой период, запросить доступ, задать уточняющий вопрос.
Смешение причин ломает проверку. Нельзя написать тест на корректный отказ, если отказ один и тот же для трёх разных случаев: любое поведение проходит проверку и ни одно не гарантировано.
Три точки, где граница код/модель теряется чаще всего
Ниже три места, где граница проваливается чаще всего. Список не исчерпывающий, но эти точки дают основную долю нестабильности в агентах для запросов к данным.
Правило асимметрии при повышении категории запроса
Категория запроса не всегда очевидна, и агент может поднять её: с чтения на запись, с локального изменения на массовое, с обычного режима на чувствительный. Решение о повышении категории должно быть асимметричным. Код явно определяет, когда повышение допустимо, когда нет и что делать в каждом случае.
Пример. Запрос на изменение данных в CRM. Признаки массового изменения перечислимы: число затронутых записей по фильтру, отсутствие ограничения по периоду, повторное выполнение похожего запроса в течение короткого времени. Если признаки сработали, код поднимает категорию и требует подтверждения. Если решение оставить модели, она будет оценивать массовость заново на каждой перефразировке: «обнови статусы у клиентов из Москвы» и «приведи в порядок статусы московских клиентов» получат разную интерпретацию при одинаковом фактическом результате.
Асимметрия означает, что цена ошибки в разные стороны разная. Пропустить массовое изменение дороже, чем лишний раз запросить подтверждение. Код настраивается на более дорогой сценарий, вместо того чтобы искать баланс по каждому запросу.
Проблема необнаружимой ошибки в результате
Результат агента может быть синтаксически корректным и логически неверным. Ответ содержит валидный JSON, заполненные поля, связный текст. Ошибка внутри: не тот период, агрегат посчитан по подмножеству данных, перепутана валюта, не схлопнуты дубликаты.
Такая ошибка не обнаруживается ни простыми проверками, ни моделью. LLM-as-judge её тоже не ловит, потому что оценивает правдоподобность, а не истинность. Правдоподобный ответ выглядит ровно так же, как правильный, и отличается от него только фактическим содержанием. Похожее ограничение известно в чувствительных доменах: safety-обвязка защищает периметр, но не контролирует содержание ответа. Здесь работает тот же принцип.
Что помогает: структурные проверки там, где они возможны. Сверка суммы частей с целым, проверка инвариантов: число строк не превышает число записей в источнике, даты лежат внутри запрошенного периода, итог не отрицательный для величин, которые не могут быть отрицательными, контрольные суммы совпадают с эталоном. Это работа кода, потому что проверки формулируются как условия над данными, а не как суждения о смысле.
Если структурная проверка невозможна, зону ответственности модели ограничивают явно: модель отвечает за формулировку, а числа и сущности приходят из детерминированного слоя без пересказа. Как только модель получает право пересказывать числа своими словами, необнаружимая ошибка становится штатным режимом работы.
Отказ как решение кода с несколькими логически разными причинами
Отказ - не одна реакция, а несколько разных решений кода. Минимальный набор причин: нет данных, нет прав, запрос неоднозначен, инструмент недоступен, запрос выходит за объявленные границы. У каждой причины свой следующий шаг и свой текст для пользователя.
Ошибка - свернуть их в одну реакцию вида «не удалось выполнить». Тогда код не отличает отсутствие данных от отсутствия прав, не может предложить корректное действие, а логи не дают понять, что именно произошло. Пользователь получает одинаковый ответ на три разные ситуации и не знает, что менять в следующий раз.
Разделение простое. Тип отказа определяет код, потому что причины - это результат проверок над данными и правами. Текст отказа формулирует модель, если нужен естественный язык. Модель не должна определять причину, иначе она будет выбирать ту, что правдоподобнее звучит в конкретной перефразировке.
Уверенность модели не равна простоте задачи
Модель отвечает уверенно и быстро, и кажется, что задача простая и её можно оставить модели. Уверенность - свойство генерации, а не мера корректности. Она зависит от того, насколько типична формулировка, и падает на непривычных перефразировках, хотя тон ответа остаётся ровным.
Пример. Агент уверенно выбирает форму ответа, таблицу вместо уточняющего вопроса, потому что на тестовых вопросах такой выбор совпадал с ожидаемым. На перефразировке выбор остаётся таким же уверенным, но становится неверным. Сигнала о проблеме система не подаёт: нет ни ошибки, ни предупреждения, ни понижения тона.
Поэтому решение о том, что принадлежит коду, а что модели, принимают на основе структуры задачи: перечислимы ли признаки, можно ли проверить результат структурно. Скорость и тон ответа в этом решении не участвуют.
Почему LLM-as-judge не решает проблему проверки корректности
LLM-as-judge оценивает ответ по критериям, сформулированным на естественном языке. Он сравнивает правдоподобность и соответствие описанию, а не совпадение с фактами. Логическая ошибка, которая выглядит убедительно, проходит такую проверку с высокой оценкой.
Второе ограничение: судья наследует нестабильность оцениваемой системы. Он сам является LLM, чувствителен к формулировке критериев и может дать разные оценки на перефразировках одного и того же ответа. Добавляя судью поверх нестабильного агента, получают второй слой нестабильности вместо измеримой метрики.
Разумное применение: вспомогательный фильтр грубых нарушений (не тот язык, не тот формат, упоминание запрещённых тем), сигнал для ручного разбора, разметка для последующей проверки человеком. Как единственный механизм проверки корректности он не работает. Структурные проверки остаются за кодом. Критерии оценки агентных моделей и влияние оркестрации на надёжность разбирали отдельно: граница между возможностями и практическим риском.
Как спроектировать границу код/модель на практике
Работа идёт по шагам, и первый шаг почти всегда неприятный: нужно выписать то, что раньше держалось в голове.
- Выписать все решения, которые агент принимает при обработке запроса: определение категории, выбор инструмента, проверку прав, решение о повышении категории, формирование результата, отказ.
- Для каждого решения проверить, сводится ли оно к перечислимым структурным признакам.
- Если сводится, перенести в код. Если нет, оставить модели, но явно ограничить зону: что она получает на вход, что возвращает, что ей запрещено.
- Для каждого отказа определить логически разные причины и разные реакции, включая следующий шаг для пользователя.
- Добавить структурные проверки результата: инварианты, сверки, сравнение с эталонными значениями.
- Зафиксировать границу в документации и в тестах, чтобы при следующей итерации было видно, что именно поехало.
Тесты стоит строить на перефразировках, а не на одном эталонном вопросе: три-четыре формулировки одного и того же по смыслу запроса, включая намеренно неряшливую. Если поведение расходится, значит соответствующее решение осталось на стороне модели.
Чек-лист: что перенести в код, а что оставить модели
- Решение описывается конечным набором правил? Код.
- Результат проверяется структурно, через инвариант, сверку или эталон? Код.
- Есть несколько логически разных причин для одного действия? Код различает их, модель формулирует текст.
- Действие необратимо: запись, отправка, платёж, выдача прав? Решение о выполнении принимает код, модель может предложить план.
- Решение зависит от перефразировки? В текущем виде оно не годится ни для кода, ни для модели: сначала его нужно свести к признакам.
- Решение требует интерпретации смысла и не сводится к перечислимым признакам? Зона модели.
Если на первые четыре вопроса ответ «да», а на пятый «нет», это кандидат на код. Типовое распределение по агенту, который отвечает на запросы к данным, выглядит так:
| Решение агента | Зона | Почему |
|---|---|---|
| Разбор периода из запроса | Код | Период сводится к разбору дат и явному указанию границ |
| Категория запроса: чтение или запись | Код | Категория определяется набором операций |
| Массовость изменения | Код | Считается по числу затронутых записей из фильтра |
| Уточняющий вопрос при неполном запросе | Модель | Требует формулировки на естественном языке |
| Тип отказа | Код | Причина - результат проверки данных и прав |
| Текст отказа для пользователя | Модель | Нужен понятный язык, причина приходит из кода |
| Проверка числа в ответе | Код | Проверяется сверкой и инвариантами |
Процесс итеративный. При добавлении новой функции граница пересматривается: часть решений переезжает из промпта в код, часть возвращается обратно, если модель лучше справляется с неоднозначностью. Гарантий, что нестабильность исчезнет полностью, нет: вероятностная природа модели остаётся. Меняется другое: зона, где нестабильность допустима, становится известной и ограниченной, а необратимые действия перестают зависеть от формулировки вопроса.
Что это меняет для бизнеса и процессов
По данным BCG AI at Work, 74% сотрудников уже регулярно используют ИИ, а 42% пользователей отмечают экономию как минимум восьми часов в неделю. Цифры описывают индивидуальный эффект.
Производительность отдельного сотрудника и производительность компании - разные величины. Сотрудник может вдвое быстрее подготовить отчёт, но если документ проходит прежнюю цепочку согласований, а распределение обязанностей не меняется, компания получает локальную экономию времени. Результат остаётся изолированным и замкнутым на себе.
С агентами добавляется второй эффект. Ошибка перестаёт быть промежуточной и превращается в действие. Агент с доступом к CRM и правом отправлять письма экономит часы, но при неспроектированной границе код/модель может массово изменить записи или уйти от клиента с неверным ответом. Именно поэтому возможность LLM в цифровой трансформации играет менее критичную роль, чем процессы компании и её готовность перестраиваться под технологию.
Граница код/модель - управленческая задача в той же степени, что техническая: она определяет, какие действия агент совершает без человека и что произойдёт при ошибке. Начните с одного списка: выпишите решения, которые агент принимает сейчас, отметьте те, что сводятся к перечислимым признакам, и перенесите их в код. Дальше проверьте каждое необратимое действие на наличие подтверждения и структурной проверки результата.