Ретраи и идемпотентность: почему агент дважды платит по счёту
Агент отправил платёж, не дождался ответа сети, повторил запрос — и деньги ушли дважды. Разбираем, как ретраи ломают действия агентов и что с этим делают ключи идемпотентности.

Агент на базе LLM вызывает инструмент «создать платёж». Запрос уходит, банковский API его принимает и списывает деньги, но ответ теряется в сети по таймауту. Агент видит ошибку, срабатывает логика повторов — и он отправляет тот же запрос ещё раз. Итог: одно намерение пользователя, два списания. Это не гипотетический сценарий из презентации, а самый частый способ, которым автономные агенты причиняют реальный ущерб. И причина не в «галлюцинации» модели, а в старой инженерной проблеме, которую фреймворки агентов слишком часто оставляют на потом.
Откуда берётся двойное действие
Любой сетевой вызов может завершиться тремя способами: успех, явная ошибка, неопределённость. Третий — самый опасный. Клиент отправил запрос, но не получил ответа: соединение оборвалось, вышел таймаут, упал промежуточный прокси. Сервер при этом мог как выполнить операцию, так и не выполнить. Клиент этого не знает.
Стандартная реакция на неопределённость — повторить (retry). Для чтения это безопасно: запросить баланс дважды не страшно. Для записи — нет. Повтор «создать заказ», «отправить письмо», «списать средства» без защиты приводит к дублю.
У агентов проблема острее, чем у обычного бэкенда, по трём причинам:
- ретраи заложены на нескольких уровнях сразу: в HTTP-клиенте, в обёртке инструмента, в самом цикле рассуждения агента. Они накладываются друг на друга;
- агент может «сам решить» повторить действие, если посчитал результат неудачным — здесь повтором управляет не жёсткий код, а вероятностная модель;
- граница между «прочитать» и «изменить» модели неочевидна: для неё вызов инструмента — просто текст с параметрами.
Идемпотентность: определение и практика
Операция идемпотентна1, если её повторное выполнение с теми же параметрами даёт тот же результат, что и однократное, и не создаёт новых побочных эффектов. Запросить страницу — идемпотентно. Прибавить 100 рублей к балансу — нет. Установить баланс равным 100 рублям — да.
На практике идемпотентность записи обеспечивают ключом идемпотентности (idempotency key): клиент генерирует уникальный идентификатор запроса и передаёт его серверу, обычно в заголовке. Сервер запоминает: если запрос с таким ключом уже обработан — он не выполняет операцию заново, а возвращает сохранённый результат первого вызова.
Ретрай без ключа идемпотентности — это не отказоустойчивость. Это способ гарантированно навредить ровно тогда, когда сеть уже нестабильна.
Ключ должен генерироваться один раз на намерение, а не на каждую попытку. Если агент создаёт новый ключ при каждом повторе, защита не работает — сервер видит разные запросы.
Кто отвечает за ключ
Слабое место агентных систем — размытая ответственность. Ключ может создавать модель (плохо: она непредсказуема и может сгенерировать одинаковый ключ для разных намерений или разный для одного), обёртка инструмента (лучше: детерминированный код) или сам API-провайдер по содержимому запроса. Надёжнее всего — детерминированная обёртка, которая вычисляет ключ из намерения и передаёт его во все повторы.
Как сравниваются подходы к защите
Способов не допустить дубль несколько, и они не взаимоисключающие. Ниже — их сильные и слабые стороны.
| Подход | Что даёт | Ограничения |
|---|---|---|
| Idempotency key на стороне API | Дубль отсекается на сервере, работает при любом источнике повтора | Нужна поддержка на стороне провайдера; ключи обычно живут ограниченное время |
| Дедупликация по содержимому запроса | Не требует явного ключа от клиента | Легитимные одинаковые операции (два одинаковых перевода) можно ошибочно склеить |
| Условная запись (compare-and-set, версии, ETag) | Повтор проваливается, если состояние уже изменилось | Требует, чтобы операция выражалась через состояние, а не через приращение |
| Двухфазное действие (черновик → подтверждение) | Опасный шаг отделён от подготовки, легко вставить проверку человеком | Сложнее реализация, больше задержка |
| Отключить ретраи на записи | Просто | Теряется устойчивость к сетевым сбоям, часть операций «зависает» в неизвестности |
Что делать инженеру: порядок действий
- Разметьте инструменты агента на читающие и изменяющие. Ретраи по умолчанию разрешайте только читающим.
- Для каждого изменяющего инструмента определите, поддерживает ли внешний API ключ идемпотентности. Если да — используйте его.
- Генерируйте ключ в детерминированной обёртке один раз на намерение и переиспользуйте во всех повторах.
- Уберите дублирующие уровни ретраев: оставьте повторы на одном слое, а не в клиенте, обёртке и цикле агента одновременно.
- Задайте потолок попыток и экспоненциальную задержку, чтобы повторы не превращались в лавину при массовом сбое.
- Логируйте ключ и результат каждой попытки — без этого разбор инцидента «почему списали дважды» невозможен.
- Для действий с необратимыми последствиями (деньги, публикации, рассылки) добавьте двухфазную схему или подтверждение.
Ловушка «агент решил повторить сам»
Даже при идеальном коде агент может по своей инициативе снова вызвать инструмент, потому что «не увидел подтверждения» в тексте ответа. Защита здесь — не в промпте, а в инфраструктуре: если ключ идемпотентности стабилен для намерения, второй вызов вернёт результат первого без побочного эффекта. Промпт-инструкции вроде «не повторяй платёж» ненадёжны и не должны быть единственной линией обороны.
Кому это пригодится и кому нет
Разбор нужен, если ваш агент реально что-то меняет во внешнем мире:
- агент оформляет заказы, брони, платежи или возвраты — здесь дубль стоит денег напрямую;
- агент отправляет письма, сообщения или создаёт задачи в трекере — дубль стоит репутации и внимания получателей;
- агент управляет инфраструктурой (создаёт ресурсы в облаке, меняет конфиги) — повтор может поднять лишние платные ресурсы.
Тема почти не касается вас, если агент работает в режиме read-only: суммаризация, поиск, анализ, ответы на вопросы без записи. Там повтор безопасен, а сложная защита только добавит задержек. Если же ваш агент выдаёт черновики, которые применяет человек вручную, точка риска смещается на этап подтверждения — идемпотентность нужна уже тому шагу, где действие уходит наружу.
1 Идемпотентность — свойство операции, при котором её многократное повторение с одинаковыми входными данными приводит к тому же результату, что и однократное выполнение, без дополнительных побочных эффектов.
Prompt-инженер: Идеальные запросы для Midjourney, ChatGPT и других моделей.
Спросить за 15 ₽Источники: Stripe API: Idempotent Requests, MDN Web Docs: Idempotent (HTTP glossary)
Частые вопросы
Чем ретрай отличается от идемпотентности?
Можно ли просто запретить агенту повторять действия через промпт?
Кто должен генерировать ключ идемпотентности — модель или код?
Что делать, если внешний API не поддерживает ключ идемпотентности?
Нужна ли идемпотентность агенту, который только читает данные?
Как долго действует ключ идемпотентности?
Материал носит информационный характер и подготовлен редакцией «Агентуры». Он не является офертой, рекламой или индивидуальной консультацией. Упомянутые продукты, компании и торговые знаки принадлежат их правообладателям. Перед принятием решений, влекущих юридические или финансовые последствия, обратитесь к профильному специалисту.