Anthropic в декабре 2024 года в материале Building Effective Agents зафиксировала различие между workflow и агентом: в workflow потоком управляет заранее заданный код, в агенте — модель. С тех пор это различие пересказывали не раз, в том числе в русскоязычных текстах. Например, статья «От болтливых LLM-агентов к управляемым системам» на Хабре, вышедшая месяц назад, подчёркивает: не стоит поручать модели то, что код сделает точнее и надёжнее. Но на практике в типичном LLM-агенте для запросов к данным — text2sql, аналитическом ассистенте или чат-боте над базой — никто явно не решает, что делает код, а что модель. Это складывается само, по ходу написания системного промпта.

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

Тип ошибкиЧто происходитПоследствия
Неправильное повышениеЗапрос обработан более дорогой и строгой моделью, чем требовалосьПотрачено больше ресурсов, но основание ответа остаётся актуальным
Неправильное понижениеЗапрос обработан более слабой моделью, чем необходимоОснование ответа молча подменяется более слабым, проверить подмену задним числом нельзя

Именно здесь кроется и проблема, и её решение. Проблема в том, что в описанном сценарии никто явно не решал, что в системе делает код, а что модель. Это сложилось само, по ходу генерации промпта. Спросите, почему модель выбирает форму результата запроса, а не разработчик пишет десять строк обычного кода, — и внятного ответа, как правило, не найдётся. Отсюда и решение: граница между тем, что делает код, и тем, что делает модель, — это не деталь реализации, а отдельный архитектурный объект. Если такое решение не принять явно, оно всё равно будет принято по умолчанию, и чаще всего не в пользу устойчивости системы.

Добавление новых правил в промпт помогает временно, но затем правила начинают противоречить друг другу, и нестабильность прячется в порядке их применения.

Опыт разрешения этой ситуации можно сформулировать одной главной мыслью: устойчивость LLM-агента зависит не от числа ограничений в промпте, а от того, насколько явно проведена и зафиксирована граница между алгоритмом (релевантным ему кодом) и моделью, достаточно самостоятельно и независимо работающей в концептуальном пространстве. Самый известный частный случай этой границы — маршрутизация запроса: сначала дешёвая эвристика или модель, потом эскалация по порогу уверенности. Паттерн называют cascade routing и разбирают вплоть до продакшн-деталей — дрейф порога при обновлении модели, атаки на роутер через специально составленный запрос.

Рассмотрения требует вопрос, где граница плавает и каким образом её можно удержать. Первое — правило асимметрии. Классификатор отнёс запрос к одной категории. Дальше выясняется, что запрос сложнее, чем казалось. Пересмотр в этом случае обязателен, но только в одну сторону. Категория может подняться до более строгой и дорогой обработки, а понижаться она никогда не может. Ошибка повышения и ошибка понижения относятся к разным категориям. Неправильное повышение — это ошибка исполнения: потрачено больше ресурсов, чем нужно, но само основание ответа остаётся при этом актуальным. Это можно поправить на следующем этапе. При неправильном понижении основание ответа молча подменяется более слабым, недостаточным, но тот, кто получает результат, об этом не знает. Задним числом проверить подмену нечем, поскольку ответ выглядит как и любой другой, высказывается столь же уверенно. Сравнивать эти две ошибки по любой шкале нельзя, это несопоставимые вещи — всё равно что сравнивать цену лишнего шага с ценой шага в неверном направлении.

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