Agent重试没做好,一个超时能变成十次扣款
重试是Agent工程里最被低估的风险点。本文用三个真实事故场景说明为什么LLM调用的幂等性比HTTP更复杂,并给出可落地的幂等键设计与状态机写法。
后端工程师对重试都很熟:超时就重试,加上指数退避。但把同一个模式搬到 Agent 系统上,会撞上一个截然不同的问题——
LLM 调用不是无副作用的。 一次“重试”可能触发两次退款、两封邮件、两次库存扣减。
三个真实事故
事故一:超时但已成功
Agent 调用“创建退款工单”工具,工具内部超时 30 秒。Agent 判定失败,重试。第二次成功。
结果:两张退款工单,用户被退了两次。
根因:超时是客户端的判断,不是服务端的事实。 服务端可能已经处理完了,只是响应没回来。
事故二:并发重放
用户连点两下“帮我下单”。前端没有防抖,发出两个请求。两个 Agent 实例同时执行,各自读到相同的库存,各自下单。
结果:买了两份。
事故三:多步链路的中间态丢失
Agent 执行三步:验券 → 扣券 → 发货。第一步成功,第二步超时,Agent 从头重试整个流程。
结果:券被扣了两次。
这个最典型——粒度过粗。重试的粒度必须是“最小副作用单元”,而不是整条链路。
幂等键:不是加个UUID就完了
大部分团队的第一反应是“加个 request_id 去重”。这在单一服务里够用,但在 Agent 场景不够,因为:
1. 幂等键的生成位置很关键。
必须在副作用意图产生的那一刻生成,而不是在 HTTP 请求发起时。如果 Agent 决定“要退款”是一个事件,那幂等键应该绑定这个决策,从决策点到工具调用之间的所有重试都复用同一个键。
推荐:idempotency_key = hash(conversation_id + intent_type + intent_params)
2. 参数必须规范化后参与hash
{"amount": 100} 和 {"amount": "100"} 必须产生同一个键。浮点数要按精度归一(100.00 → 100),数组要排序,缺失字段要补默认值。
这一条不做,去重形同虚设——因为重试时 LLM 可能给出形式上略有不同的参数。
3. 幂等窗口要有边界
去重记录不能永久保存,但也不能只存5分钟。合理值:覆盖一次会话的最长生命周期,通常 24 小时。同时区分“处理中”和“已完成”:
- 键存在且状态=已完成 → 直接返回缓存结果
- 键存在且状态=处理中 → 返回 409,让调用方等待而非重试
- 键不存在 → 执行
处理中 这个中间态是防并发重放的核心,很多实现漏了它。
状态机:把链路拆成可恢复的节点
比幂等键更根本的做法,是让整个流程可恢复而非可重试。
给每个任务维护一个持久化状态:
STEP_1_VERIFY_COUPON done
STEP_2_DEDUCT_COUPON done [key: abc123]
STEP_3_SHIP pending
失败时不是从头再来,而是从第一个 pending 节点继续。每个节点的完成状态落库,重试只重跑未完成的部分。
这带来的额外好处:你可以知道“卡在哪一步”、可以人工介入补完、可以做部分回滚。
什么工具绝不能自动重试
给工具打三个标签:
| 风险级别 | 例子 | 重试策略 |
|---|---|---|
| 幂等读 | 查订单、搜文档 | 随意重试 |
| 幂等写 | 设置字段为某值(PUT语义) | 可重试 |
| 非幂等写 | 下单、退款、转账、发邮件 | 禁止自动重试,只能人工确认或先查后补 |
第三类工具,正确的处理不是“重试”,而是先查询状态再决定:
超时 → 查询“该幂等键对应的操作是否已存在”
├─ 存在 → 视为成功
├─ 不存在 → 安全重试
└─ 查询也失败 → 报错并转人工
这就是“查询补偿”模式。它把不确定性收敛到一个可控动作上。
Agent 特有的坑:LLM 自己会重试
除了框架层重试,LLM 本身也会重试。当工具返回错误信息时,模型很可能换个参数再来一次。
所以错误信息必须设计成阻止重试的形状:
- ❌
"Error: transaction failed"→ 模型会再试 - ✅
"该订单已于 2026-10-27 14:02 完成退款(工单号 RF-2931)。请勿重复提交,直接告知用户结果。"
把“不要重试”写进工具返回,比在框架里加限制有效得多。
一条检查清单
上线前对着过一遍:
- 每个写操作工具都有幂等键吗?键的生成位置在决策点还是请求点?
- 幂等记录包含“处理中”中间态吗?
- 是重试整条链路,还是从断点恢复?
- 非幂等写工具,超时后的动作是查询还是一律重试?
- 工具错误信息的措辞,会不会诱导模型再试一次?
- 前端/入口有防连点吗?
重试的正确姿势,本质是承认**“失败”是一个模糊的判断,而“副作用”是一个确定的事实**。系统设计要建立在事实上,不能建立在判断上。