AI-агент повторяет действия: как не создавать дубли после таймаута
Пользователь просит агента создать заявку о сбое экспорта. Инструмент записывает её, но ответ теряется на обратном пути. Агент видит таймаут и ещё раз вызывает создание. В итоге в системе две одинаковые заявки, хотя пользователь просил одну. Эта ошибка возникает между успешным внешним действием и сообщением об успехе, поэтому одним увеличением таймаута она не исчезает.
Где появляется повтор
Дубли могут прийти из разных мест: модель снова предложила действие, оркестратор повторил шаг после ошибки, HTTP-клиент отправил запрос ещё раз. Для разбора сохраните три идентификатора: пользовательского запроса, самой операции и созданной записи. Если у двух вызовов один ID операции, это технический повтор. Если ID разные, надо выяснить, кто создал вторую операцию и было ли новое намерение пользователя.
ID операции выдаёт приложение, когда принимает одно конкретное намерение пользователя. Модель может выбрать действие и заполнить его аргументы, но не должна каждый раз придумывать новый ключ. Один и тот же ID проходит через все попытки вызова инструмента. Новое осознанное действие пользователя получает новый ID, даже если текст заявки похож.
Перед повтором полезно посмотреть журнал вызовов: совпадают ли имя инструмента, нормализованные аргументы и ID операции. Если модель предложила два разных текста заявки в одном пользовательском запросе, это уже не простой сетевой retry. Потребуется решить, какое действие действительно было подтверждено. Сервис защитит от двух одинаковых вызовов с одним ключом, но не угадает намерение пользователя за оркестратор.
Одна операция — один результат
На стороне сервиса, который создаёт заявку, храните ID операции вместе с результатом. При повторе с тем же ID и тем же содержанием верните старую запись. Если под тем же ID пришло другое содержимое, сообщите о конфликте: молчаливо возвращать прежний результат здесь опасно. Для конкурентных вызовов опирайтесь на уникальное ограничение в базе, а не только на проверку «сначала найти, потом вставить».
Следующий пример использует SQLite в памяти и показывает последовательный повтор. Он не обращается к внешнему сервису; в настоящей системе такую таблицу размещают в устойчивой базе сервиса, который выполняет действие.
import sqlite3
db = sqlite3.connect(':memory:')
db.execute('''CREATE TABLE tickets (
id INTEGER PRIMARY KEY,
operation_id TEXT NOT NULL UNIQUE,
body TEXT NOT NULL
)''')
def create_ticket(operation_id, body):
previous = db.execute(
'SELECT id, body FROM tickets WHERE operation_id = ?',
(operation_id,)
).fetchone()
if previous:
if previous[1] != body:
raise ValueError('same operation_id with different body')
return previous[0]
with db:
cursor = db.execute(
'INSERT INTO tickets (operation_id, body) VALUES (?, ?)',
(operation_id, body)
)
return cursor.lastrowid
first = create_ticket('op-42', 'Ошибка экспорта')
again = create_ticket('op-42', 'Ошибка экспорта')
new_request = create_ticket('op-43', 'Ошибка экспорта')
count = db.execute('SELECT COUNT(*) FROM tickets').fetchone()[0]
print(first, again, new_request, count)
Код вычислит 1 1 2 2: повтор вернул прежний ID, а новая операция создала вторую запись. Проверьте также конфликт аргументов с тем же ID и одновременные запросы. Последние требуют реальной базы и обработки её ошибки уникальности; пример в памяти доказывает только поведение последовательных вызовов.
Как связать это с агентом
В агентном приложении создайте operation_id до вызова инструмента и передайте его как служебный аргумент через оркестратор. При таймауте повторите тот же вызов с тем же ID. Инструмент должен вернуть прежний ticket_id или явную ошибку конфликта. Не берите новый ID из очередной генерации модели. Для шага, который только читает данные, такое хранилище обычно не нужно; оно важно для действий с внешним эффектом.
Если агент работает через LangGraph, его чекпойнт помогает восстановить состояние графа после сбоя. Он не отменяет запись, уже принятую внешним сервисом. Поэтому сохраняйте ID операции в состоянии графа и проводите его до инструмента, а защиту от дубля обеспечивайте на стороне самого действия. Это позволит продолжить диалог после падения, не создавая новую заявку при каждом восстановлении.
В платном курсе AI Agents PRO: LangGraph, AutoGen и LLMOps студент строит агента с графом состояний, инструментами и API, а затем отрабатывает повторы, очередь неуспешных задач, идемпотентность и наблюдаемость. На открытом уроке «FSM и DAG: оркестрация шагов» можно увидеть, как действия раскладываются по состояниям. Открыть программу курса; для самостоятельной проверки есть подборка задач по ответам агента.
Если ещё выбираете путь обучения, поможет разбор курсов по AI-агентам.
Продолжить на практике
- AI Agents PRO: LangGraph, AutoGen и LLMOps в продакшне ↗
Практический курс по AI-агентам. Соберёте продакшн RAG-сервис: от индекса и графа состояний до API, Docker, метрик качества и контроля стоимости. Плюс профессиональные блоки: LangGraph/AutoGen, Qdrant/FAISS+rerank, eval (RAGAS/custom), quality-гейты, OpenTelemetry+Prometheus/Grafana, CI/CD, ретраи/DLQ/идемпотентность, PII/RBAC.
