智能体重复下单基本不是模型的问题,而是「重试、并行、超时」三件事叠加的必然结果;解决办法不在提示词里,而在工具层——给每个写操作配幂等键、预检、补偿动作和对账,四件套缺一件,重复动作迟早会发生。
重复动作从哪来
三个来源,单独看都不难理解,叠在一起就防不住:
- 结果丢失后的重试:退款已经成功,但网络在返回结果前抖动了一下。智能体认为失败,于是再调一次,第二笔退款真实发生;
- 并行调用同一动作:为了提速让多个工具并行执行,两个分支都判断「还没下单」,于是各下一单;
- 超时后的重新发起:调用超时,智能体不知道服务端到底执行了没有,只能重来一次。
共同点是:智能体并不知道这次调用到底成没成功。它在猜,而猜的代价由业务承担。所以正确的位置是工具层——让重试天然安全,而不是让模型学会谨慎。
四件套之一:幂等键
每个有副作用的工具都接受一个 idempotency_key 。同一个键重复调用时,直接返回首次的结果,不重复执行动作:
async function sendEmailTool(input) {
const key = sha256(JSON.stringify(normalize(input)));
const existing = await db.toolCalls.findFirst({
where: { key, tool: "send_email" }
});
if (existing?.result) return existing.result;
const result = await actuallySend(input);
await db.toolCalls.create({
data: { key, tool: "send_email", result, createdAt: new Date() }
});
return result;
}
三个设计要点:
- 键由调用方生成,工具层校验:不要让工具自己随机生成,否则重试时键不同,等于没做;
- 入参要规范化后再哈希:字段顺序、空格、大小写差异都会让同一个意图算出不同的键;
- 记录要落库并设置保留期:内存里存一份没有意义,进程重启就失效了。
键的构成建议是 工具名 + 业务域 + 规范化入参哈希。纯用入参哈希在「同一分钟内确实要发两封相同内容的邮件」时会误杀,加一个业务域或显式序号可以区分。
四件套之二:写前预检
在执行写操作前先查一次当前状态。比如下单前先查该订单号是否已存在、发券前先查该用户是否已领过。预检本身是只读操作,重试无害,成本极低,能挡掉大部分「重复是因为判断滞后」的场景。
预检的局限是存在竞态:查的时候没有,写的时候可能已经有了。所以预检不能替代幂等键,两者是叠加关系。
四件套之三:补偿动作
每个写工具配一个反向工具,并且反向工具自身也必须幂等:
| 写操作 | 补偿动作 | 补偿的幂等要求 |
|---|---|---|
| 创建订单 | 取消订单 | 重复取消返回成功,不报错 |
| 扣款 | 退款 | 同一笔只能退一次,重复退款请求返回首次结果 |
| 发通知 | 撤回或补发更正 | 撤回幂等,已撤回再撤返回成功 |
| 写入记录 | 软删除或标记作废 | 标记操作是幂等的,物理删除不是 |
一条重要原则:优先设计成可撤销,而不是不可逆。能软删除就别物理删除,能标记作废就别直接覆盖。不可逆操作的出错成本无法用兜底逻辑挽回。
四件套之四:对账
前三件都是事前防护,对账是事后兜底。按 trace 汇总每次运行的全部工具调用,定期与业务侧比对:
- 同一 trace 内同一工具的写调用次数;
- 业务侧的实体数量与工具侧成功次数是否一致;
- 存在「工具成功但业务侧无记录」或反之的情况,标记人工核查。
对账不需要实时,一天一次足够。它的价值在于把「偶尔重复」从用户投诉变成内部告警。工具调用重试与降级的分层设计可以参考工具调用重试与降级。
错误分类决定重试策略
| 错误类型 | 能否重试 | 智能体该怎么走 |
|---|---|---|
| 参数校验失败 | 可以,但要改参数 | 修正入参后重试,不改参数重试必然再失败 |
| 限流 | 可以,稍后 | 按 retry_after 等待,不要立即重试 |
| 服务暂时不可用 | 可以,退避重试 | 指数退避,超过上限转人工 |
| 记录不存在 | 不可以 | 换标识符查询,或向用户澄清 |
| 权限不足 | 不可以 | 直接上报,不要在权限不足时反复尝试 |
落地步骤
- 盘点全部有副作用的工具,逐个标记:创建、更新、删除、发送、支付都算写操作。
- 为每个写工具的入参补上 idempotency_key 字段,并在描述里说明它的作用。
- 实现幂等中间件:规范化入参、算哈希、查重、命中则直接返回首次结果。
- 给每个写工具补一个只读预检接口,写之前先查状态。
- 为每个写工具配补偿工具,确认补偿本身也走幂等中间件。
- 写操作返回结果里带上操作 ID,供后续对账与人工追溯。
- 建立每日对账任务,比对工具侧与业务侧的写操作数量差异。
和相邻机制的分工
幂等解决「重复执行」,不等于解决「执行失败」。失败之后怎么重试、什么时候降级、何时转人工,属于另一层设计。工具本身的命名与边界划分参考智能体工具设计实践;并行调用带来的重复风险见并行工具调用;输出需要被下游可靠解析时,用结构化输出 schema固定字段;改动之后要不要上线,交给提示词版本管理与回归测试来判断。
只读工具也需要幂等键吗
幂等键应该由谁生成
加了幂等键还会有重复吗
幂等记录要保存多久
结论:智能体的写操作安全不靠模型自觉,靠工具层兜底。幂等键挡住重试,预检挡住判断滞后,补偿挡住已经发生的错误,对账挡住前面三道都没拦住的漏网之鱼——四件套装上之后,重复下单才会从「迟早会发生的意外」变成可被系统吸收的常规情况。










评论 (0)