Ошибочный tool call: dry-run, квоты и откат на практике
Ошибочный tool call не должен списывать деньги, удалять данные и менять статусы. Разберем dry-run, квоты действий и откат для рискованных операций.

В чем здесь риск
Ошибка в tool call опаснее обычного плохого ответа модели. Текст можно проигнорировать. Вызов инструмента меняет что-то в реальной системе: списывает деньги, ставит статус, удаляет запись, отправляет письмо, открывает доступ. Один неверный аргумент уже запускает действие, и система честно выполняет команду.
До первого инцидента проблема часто кажется мелкой. Агент перепутал refund_amount, подставил не тот customer_id или выбрал cancel_order вместо update_order. Для модели это разница в нескольких токенах. Для бизнеса это реальные деньги, чужие данные и сломанный процесс.
Ущерб обычно идет по трем линиям: деньги, данные и статусы. Деньги теряются через лишние возвраты, скидки и повторные выплаты. Данные страдают из-за удаления, перезаписи или раскрытия не той записи. Статусы ломают процесс дальше по цепочке: заказ отменен, заявка закрыта, доступ выдан не тому человеку.
Статусы особенно часто недооценивают. Одна неверная отметка в CRM или биллинге запускает цепочку дальше: клиент получает письмо, склад останавливает отгрузку, поддержка видит ложную картину. Потом команда тратит часы уже не на исправление одной ошибки, а на разбор последствий.
Массовый вызов делает все хуже в разы. Агент работает быстро, а цикл с ретраями, батчами или обработкой очереди может повторить ошибку сотни раз за минуту. Если инструмент умеет делать массовый возврат, менять цены или закрывать тикеты пачкой, ущерб растет почти мгновенно.
Пользователь замечает проблему поздно. Обычно он видит уже результат: пропали данные, пришло странное уведомление, деньги ушли, статус сменился. Логи и трассировка помогают понять, что случилось, но доверие сами по себе не возвращают.
Если команда строит LLM-агента через единый API-шлюз, подключать модели проще. Риск все равно остается в точке, где модель получает право дергать бизнес-инструменты. Поэтому защиту ставят перед действием, а не после жалобы клиента.
Какие операции считать опасными
Опасной стоит считать не саму команду, а ее последствия. Если вызов меняет деньги, права, данные или состояние бизнес-процесса, ошибка быстро выходит за рамки одного запроса. Чем труднее отменить эффект, тем строже нужен контроль.
Платежи, возвраты, списания, начисления бонусов и любые денежные движения почти всегда попадают в эту группу. Один неверный tool call может вернуть деньги не тем клиентам, списать лишнее или создать долг в учете. Даже если сумма маленькая, проблема редко остается маленькой. Потом подключаются бухгалтерия, поддержка и сверка с платежным провайдером.
Удаление тоже часто недооценивают. Удалить запись, файл, вложение или переписку легко, а восстановить - не всегда. Если агент стер историю тикета или переписку с клиентом, команда теряет контекст, а иногда и след для аудита.
Изменение прав доступа опасно по другой причине. Ошибка в роли может открыть данные не тому сотруднику, дать лишние действия боту или закрыть доступ тем, кто должен работать прямо сейчас. В банке, ритейле или SaaS это уже инцидент, а не просто баг.
Массовые коммуникации тоже требуют жестких ограничений. Письмо, SMS или push-уведомление легко уходят тысячам людей за секунды. Если агент ошибся с сегментом, текстом или временем отправки, откатить это уже нельзя. Остается только разбирать последствия.
Смена статуса заказа, заявки или тикета выглядит безобидно, но часто запускает цепочку действий. Статус "возврат одобрен" может открыть выплату. Статус "закрыто" убирает задачу из очереди поддержки. Статус "отгружено" уходит в складской или курьерский контур.
Простой тест на опасность
Считайте операцию рискованной, если выполняется хотя бы одно условие: она меняет деньги или юридически значимый статус, дает или снимает доступ, затрагивает много объектов сразу, ее трудно быстро и точно отменить, либо ее результат увидит клиент или внешний партнер.
Полезное правило простое: чтение почти всегда безопаснее записи. Но даже чтение становится опасным, если следующий шаг агент делает сам. Поиск "всех заказов со спорным платежом" обычно нормален. Автоматический возврат по найденному списку - уже нет.
Если сомневаетесь, помечайте операцию как опасную заранее. Лишняя проверка стоит минуты. Ошибочный массовый вызов потом может стоить дней разборов.
Как пропускать вызов через защитный маршрут
Один и тот же tool call не должен идти по одному пути. Чтение справочника, пересчет скидок и массовый возврат денег - это разный риск. Поэтому у каждого инструмента нужен свой уровень: безопасный, ограниченный или опасный. Эту метку лучше хранить рядом с описанием инструмента, чтобы агент не определял риск на глаз.
Защитный маршрут лучше строить как отдельный конвейер, а не как одну проверку перед запуском. Сначала система принимает запрос агента, потом прогоняет его через dry-run, сверяет лимиты и только после этого решает, можно ли выполнять реальный вызов.
Dry-run должен показывать не только название действия. Нужен понятный план: какие записи изменятся, по какому фильтру они выбраны, сколько объектов затронет операция, какая сумма пройдет через действие и в какое окно времени это укладывается. Если агент хочет удалить 12 тестовых заказов, оператор должен увидеть именно это, а не расплывчатое "будет выполнена очистка".
Проверка обычно сводится к пяти вещам: лимиту по сумме для денежных действий, лимиту по числу объектов, ограничению по времени, признаку обратимости и правилу, нужно ли подтверждение человека. Если хотя бы один лимит не проходит, маршрут должен остановить вызов или перевести его в ручное согласование.
Это особенно важно для необратимых шагов: удаление, списание, публикация наружу, отправка уведомлений клиентам. Тут лучше быть скучным и медленным, чем потом весь день чинить последствия.
Для необратимых действий просите явное подтверждение на основе плана из dry-run. Не "продолжить?", а конкретный вопрос с цифрами: "Будет создано 480 возвратов на 2,1 млн рублей. Подтвердить?" Такая формулировка заметно снижает риск машинального согласия.
До реального вызова система должна сохранить данные для отката. Это может быть снимок исходных полей, список затронутых ID, старая сумма, старый статус, версия записи и idempotency key. Без этого откат превращается в ручной поиск по логам, а в стрессе там легко ошибиться второй раз.
Если вы пропускаете вызовы через шлюз вроде RU LLM, удобно держать dry-run, подтверждение и реальный вызов в одном трассировочном контуре. В такой схеме проще понять, кто одобрил действие, какой план видел оператор и почему система все-таки выполнила вызов.
Хороший защитный маршрут не делает агента умнее. Он просто не дает одной ошибке превратиться в массовую операцию.
Что должен показать dry-run
Dry-run нужен не для галочки. Он должен показать будущий результат так, чтобы человек за 10 секунд понял: запускать можно или нельзя. Если превью пишет только "запрос корректен", пользы почти нет.
Сначала dry-run должен раскрыть точный вызов. Нужны полное имя инструмента и все аргументы, включая значения по умолчанию, которые агент подставил сам. Ошибка часто прячется именно там: не в команде "сделать возврат", а в notify=true, лишнем фильтре или пустом ограничении по периоду.
Дальше нужен список объектов, которых коснется действие. Важны не только число, но и состав: какие ID, какие группы, по какому фильтру агент их выбрал, что он исключил. Если объектов слишком много, покажите первые несколько, общее количество и правило выборки. Иначе человек видит "затронет 842 записи" и все равно не понимает, что именно пойдет под изменение.
Отдельно dry-run должен посчитать внешний эффект. Сколько записей изменится, какая сумма пройдет, какие статусы сменятся, уйдут ли письма, webhook-события или списания. Хорошее превью пишет прямо: "Изменит 84 заказа, создаст возвраты на 146 200 руб., отправит 84 письма клиентам". После такой строки спорных мест обычно почти не остается.
Если правило защиты не проходит, dry-run не должен отвечать общим "операция заблокирована". Нужна точная причина. Например: "Лимит на возврат без ручного одобрения - 100 000 руб., dry-run посчитал 146 200 руб." Или: "Фильтр захватывает клиентов из другого региона". Тогда инженер сразу видит, что исправлять.
Человеку нужен и короткий текст для подтверждения. Не длинный лог, а одна ясная фраза: "Подтвердить возврат 84 заказов на 146 200 руб. Отправить письма клиентам: да". Такой формат снижает шанс, что ошибочный tool call проскочит из-за спешки.
Если команда хранит audit trail, этот же dry-run удобно сохранять рядом с запросом. Потом легко разобрать, что агент собирался сделать и кто это одобрил.
Какие квоты режут масштаб ошибки
Даже аккуратный агент иногда ошибается. Проблема редко в одном неверном действии. Настоящий ущерб начинается, когда агент повторяет ошибку десятки раз подряд. Поэтому квоты должны ограничивать не только доступ, но и объем действий.
Первый слой - лимит на одно действие. Если инструмент умеет списывать деньги, делать возвраты, менять цены или удалять записи, один вызов не должен затрагивать слишком много объектов. Для возврата это может быть один заказ за вызов, для удаления - десять записей, но не десять тысяч. Тогда ошибочный tool call ломает меньше.
Второй слой - лимит на пользователя и на сессию. Один и тот же пользователь может случайно спровоцировать цепочку повторов, а агент может зациклиться в рамках одного диалога. Полезно поставить потолок вроде трех возвратов на пользователя за сессию и пяти рискованных действий за час.
Третий слой - лимит на окно времени. Он режет скорость ошибки. Если агент за 30 секунд пытается отправить 200 запросов на изменение статуса заказов, система должна пропустить только малую часть и остановить остальное.
Для массовых операций нужен отдельный, более жесткий потолок. Массовое изменение цен, ролей доступа или статусов выплат лучше вообще вынести в другой режим: отдельный tool, отдельная роль, отдельное подтверждение. Обычные квоты тут слишком мягкие.
На практике рабочая схема выглядит так: вы ограничиваете размер одного действия, ставите потолок на пользователя и сессию, режете частоту за 5, 15 и 60 минут, а для массовых операций вводите отдельные правила. После серии отказов полезно включать автостоп. Если агент три раза подряд получил отказ по политике, четвертую попытку лучше не давать. Закройте сессию, поднимите флаг и передайте случай человеку.
Хорошая квота не мешает нормальной работе. Она просто делает так, чтобы одна ошибка стоила минуту ручной разборки, а не полдня отката и звонков клиентам.
Как готовить откат заранее
Откат нельзя придумывать после ошибки. Его пишут в тот же момент, когда команда описывает прямое действие инструмента. Когда случается ошибочный tool call, у вас обычно есть минуты, а не часы. Если заранее не сохранить исходное состояние, откатывать будет уже нечего.
Перед любым изменением агент или сервис должен зафиксировать, что именно он собирается поменять: идентификатор объекта, старое состояние, планируемое новое состояние, время вызова, инициатора и идентификатор операции для повторов. Это простой набор, но без него откат быстро превращается в догадки.
Лучше хранить обратную операцию рядом с прямой, а не в другой части кода или в отдельной задаче. Если инструмент умеет сделать "списать бонусы", рядом должен жить и понятный путь "вернуть бонусы". Такой подход быстро вскрывает слабые места. Иногда команда сразу видит, что прямое действие есть, а честного отката нет. Это хороший повод упростить саму операцию или разбить ее на шаги.
Идентификатор повтора нужен почти всегда. Сеть рвется, агент переспрашивает, очередь дублирует сообщение. Без одного и того же ID система может дважды выполнить и прямое действие, и откат. Тогда ошибка растет сама по себе. С ID сервис понимает: эту команду он уже видел, второй раз выполнять ее не надо.
Журнал шагов тоже лучше привести к одному формату. Не смешивайте текстовые заметки, сырые логи и отдельные поля в базе. Когда инцидент уже идет, команда не должна собирать картину по кускам. Если вы работаете через шлюз вроде RU LLM, удобно связать вызов модели, tool call и действие в бэкенде в одну цепочку и хранить это в одном месте.
Самая частая ловушка такая: откат сам запускает новый вред. Например, агент ошибочно оформил массовый возврат, а откат пытается снова списать деньги у клиентов, чьи заказы уже закрыты или ушли в бухгалтерию. Поэтому откат должен проверять текущий статус объекта, лимиты и допустимость действия так же строго, как и прямая операция. Хорошее правило простое: откат - это отдельная безопасная команда, а не слепая попытка "сделать наоборот".
Если для операции нельзя описать ясный откат, не давайте агенту право выполнять ее автоматически. Пусть он только готовит dry-run и передает решение человеку.
Пример: агент ошибся с массовым возвратом
Представим интернет-магазин, где агент помогает поддержке оформлять возвраты. Оператор просит вернуть деньги по проблемным заказам, но агент выбирает слишком широкий фильтр: берет почти все заказы с похожим статусом, а не только нужную группу.
Если пустить такой tool call сразу в боевой режим, ошибка станет дорогой за секунды. Деньги уйдут сотням клиентов, бухгалтерия получит хаос, а часть операций потом придется отменять вручную.
Dry-run ловит это до реального запуска. Вместо тихого выполнения система показывает, что агент собирается сделать: 480 возвратов вместо ожидаемых 12, общую сумму, диапазон дат и несколько примеров заказов для быстрой проверки.
Полезно, когда dry-run выводит не только счетчик, но и короткое объяснение выборки: какой фильтр агент собрал, сколько записей он затронет, на какую сумму пройдет операция и какие заказы попали в первые строки результата. На этом этапе ошибка уже видна человеку.
Но полагаться только на внимательность оператора не стоит. Второй барьер - квоты. Например, не больше 20 возвратов за один запуск или не больше 100 000 рублей без отдельного подтверждения. Лимит на сумму часто спасает лучше всего. Агент может ошибиться в одном условии и внезапно развернуть массовую операцию, но система не даст перейти от dry-run к выполнению.
Дальше оператор исправляет выборку, снова запускает dry-run и видит уже 12 записей, которые совпадают с задачей. После этого он подтверждает действие вручную, и возврат идет только по нужным заказам.
Журнал в таком сценарии нужен не для формальности. Он сохраняет первую попытку: исходный запрос, собранный фильтр, расчет на 480 возвратов, сработавшую квоту и финальное исправление. Потом эти данные помогают править правила и разбирать инцидент без догадок.
Здесь сработали сразу три слоя защиты: dry-run показал масштаб ошибки, квота не дала выполнить лишнее, журнал сохранил материал для разбора. Без любого из этих слоев команда узнала бы о проблеме уже после возвратов.
Где команды чаще всего ошибаются
Первая частая ошибка выглядит безобидно: dry-run есть, но только в интерфейсе для людей. API при этом может сразу выполнить действие, если агент вызвал инструмент напрямую. В итоге разработчик видит аккуратную кнопку "Проверить", а продакшен-агент обходит эту защиту одной строкой запроса.
Ошибочный tool call редко ломает систему из-за одного неверного аргумента. Обычно проблема в масштабе. Команда ставит один общий лимит на все инструменты, и этого почти всегда мало. Чтение справочника, отправка письма и массовый возврат денег не должны жить под одним счетчиком.
Лучше резать действия по уровню риска. Обычно хватает простого деления: отдельные лимиты на чтение данных, на изменения и на массовые операции, плюс ручное подтверждение для внешних эффектов. Иначе агент тратит весь допустимый бюджет на дешевые вызовы, а потом все же получает шанс сделать один дорогой и опасный шаг.
Еще одна типичная ошибка - писать откат уже после инцидента. Это почти всегда дороже, медленнее и грязнее, чем кажется на планировании. Если инструмент может менять заказ, тариф, баланс или права доступа, сценарий отката нужно описать до запуска.
С этим связан другой промах: команда не сохраняет снимок исходного состояния. Без него откат быстро превращается в догадки. Если агент сменил статус 800 заявок, вам нужен не только факт вызова, но и точное состояние каждой записи до изменения.
Часто забывают и про отдельный флаг для массового режима. Команда разрешает одному и тому же инструменту работать и с одной сущностью, и с тысячей, различая это только по размеру списка. Так делать не стоит. Массовый вызов должен включаться явно, с другим маршрутом проверки, другой квотой и другим журналом аудита.
В командах, которые работают с LLM через единый API-шлюз, эту грань особенно легко потерять: эндпоинт один и тот же, а риск у инструментов разный. Поэтому правила безопасности надо задавать не у модели в целом, а у каждого действия отдельно.
Быстрая проверка перед запуском
Перед тем как дать агенту право на реальное действие, остановитесь на минуту и проверьте пять вещей. Такая пауза часто спасает от массовой ошибки, особенно если tool call меняет деньги, доступы или данные клиентов.
Во-первых, система должна показывать точный список объектов до выполнения: номера заказов, ID счетов, пользователей или документов. Формулировки вроде "подходящие записи" опасны, потому что никто не видит реальный охват.
Во-вторых, ставьте два лимита сразу. Один режет количество действий, второй режет общий объем ущерба: сумму возвратов, число сообщений, объем списаний или количество удаляемых строк.
В-третьих, необратимый шаг лучше отдавать человеку. Если операцию нельзя быстро отменить одной командой, агенту не стоит завершать ее без подтверждения.
В-четвертых, до запуска проверьте, что данных для отката хватит. Нужны исходные статусы, старые значения полей, связанные идентификаторы и порядок изменений.
В-пятых, сохраняйте журнал решения. В нем должны остаться аргументы вызова, кто и что одобрил, какой dry-run агент видел перед действием и почему он выбрал именно этот tool call.
На практике ломаются именно детали. Команда ставит лимит на 100 операций, но забывает про лимит суммы. В итоге агент делает 20 возвратов, и каждый на крупную сумму. Или лог хранит факт вызова, но не сохраняет исходные аргументы, поэтому потом нельзя понять, что именно модель собиралась сделать.
Хороший быстрый тест выглядит так: агент готовит массовый возврат, dry-run показывает 184 заказа и общую сумму 1,9 млн рублей, а политика разрешает не больше 30 заказов и не больше 200 тысяч рублей за один запуск. Такой вызов должен остановиться до реального действия, а интерфейс должен показать точную причину блокировки.
Если вы пропускаете вызовы через единый API-шлюз, журнал лучше хранить рядом с audit trail запроса. Для команд с российскими требованиями по данным это упрощает разбор инцидента: видно, какой аргумент ушел в tool call, кто дал добро и что нужно восстановить вручную, если автоматический откат не сработал.
Что делать дальше
Если защита от ошибок еще не собрана, не пытайтесь закрыть все сразу. Возьмите два инструмента, где ошибка бьет больнее всего: списание денег, массовое изменение данных, удаление записей, возвраты или смена статусов заказов. Этого уже хватит, чтобы резко снизить риск, когда случится ошибочный tool call.
Дальше зафиксируйте один формат dry-run и один формат журнала действий. У каждого вызова должны быть одни и те же поля: кто запустил действие, какой инструмент выбрал агент, какие параметры передал, что именно изменится, сколько объектов затронет операция, кто ее подтвердил и как сделать откат. Когда формат один, инциденты разбираются быстрее, а правила не расползаются по разным командам.
Стартовая последовательность простая: выберите два самых опасных инструмента и переведите их через dry-run, поставьте квоты на число объектов, сумму операции и частоту вызовов, включите единый журнал действий и прогоните учебный инцидент. Такой прогон быстро показывает слабые места: подтверждение, лимиты или логи.
Для учебного сценария не нужно ничего сложного. Смоделируйте простую ошибку: агент решил, что нужно вернуть деньги не по одному заказу, а по всем заказам за день. Сначала посмотрите, что произошло бы без ограничений. Потом повторите тот же сценарий с dry-run, квотами и откатом. После этого обычно сразу видно, где защита еще дырявая.
Если вы работаете в контуре с требованиями 152-ФЗ, проверяйте не только сам вызов инструмента, но и весь след вокруг него. Где лежат логи, кто имеет к ним доступ, маскируются ли PII, хватает ли данных в audit trail, чтобы потом понять, кто и когда запустил операцию. Часто команда ставит фильтр перед инструментом, но забывает про хранение логов и резервных копий. Это дорогая ошибка.
В такой схеме полезно сверить свою инфраструктуру с тем, что уже дает RU LLM: единый OpenAI-совместимый эндпоинт, хранение логов и бэкапов внутри РФ, маскирование PII и audit trail на каждый запрос. Даже если вы не меняете провайдера сейчас, такой набор удобно использовать как практический чек-лист для своей системы.