跳到主要内容
博客
首页>技术博客>Agent 写操作验收:明确失败、结果未知、重复执行三种状态怎么测
工程实践 · Engineering Notes

Agent 写操作验收:明确失败、结果未知、重复执行三种状态怎么测

企业里的 Agent 大多死在写操作上:重复下单、重复退款、工单改两次。这篇把失败拆成明确失败、结果未知、重复执行三种状态,给出可搬进验收会的故障注入表。

优码云团队阅读约 15 分钟
Agent 验收工具调用失败幂等设计Agent 工程化重试策略

让 Agent 去动真实系统,最难验的不是它会不会报错,而是报错之后那笔动作到底生没生效。只读查询出问题,最多是一次答非所问;写操作出问题,落到业务上是重复下单、重复退款、同一条短信发两遍、同一张工单被改两次。

本文只讨论写操作——下单、退款、改地址、发消息、改工单状态、往 ERP 写单据这一类。只读查询不在范围内,因为只读接口通常天然可重试,安全边界完全不同。读者对象是采购 Agent 定制开发的企业负责人、研发负责人和项目经理,文章给三样东西:三种失败状态的区分口径、能直接带进验收会的故障注入表、必须拿到书面回答的问题清单。文中引用原文的地方给链接,属于判断的地方写明是建议。

一、先把失败拆成三种状态,再谈重试

把"工具调用失败"当成一个统一概念,是验收阶段最常见的起点错误。同一个"失败"背后,下游系统的状态可能截然不同,处置方式也完全相反。至少要先拆成三档:

状态下游系统真实情况常见错误处理验收时要看到的信号
明确失败 请求未被执行,或被明确拒绝(参数非法、无权限、余额不足) 无差别重试;或直接放弃但不留痕 错误是否可机读区分"可重试/不可重试";放弃时是否留结构化记录
超时后结果未知 请求可能已生效,也可能没到下游;客户端拿不到结论 换一个新请求再发一次(最常见的资损来源) 重试是否沿用同一幂等键;是否进入待人工核对队列
重复执行 同一业务动作被真正执行了两次或多次 事后对账才发现,且往往已过客户投诉节点 幂等键是否真的落到下游;重复请求是否产生告警而不是静默成功
Agent 写操作验收主题插图
同一次工具调用,落到下游可能是三种完全不同的状态,处置动作也互不相同。

这三档里,第一档最好处理,第三档最贵,第二档最容易被漏测——因为它在监控上看就是一串超时,指标并不刺眼。

为什么"超时"不能等同于"失败",AWS 的构建者文库讲得很直接:

超时或失败并不一定意味着没有产生副作用。如果不希望多次产生副作用,则最佳实践是将 API 设计为幂等形式,也就是说可以安全地重试它们。

这段话出自 AWS Builders' Library:超时、重试和抖动回退。它把问题指向了一个设计层面的判断:能不能安全重试,不是客户端的态度问题,是接口本身是否幂等。(建议:把这句话作为验收会的开场,它比任何流程图都更容易让业务方理解为什么这一项不能省。)

二、结果未知这一档最贵,也最少被测

IETF 有一份专门讨论幂等键请求头的草案,它开篇描述的场景就是"结果未知":

(译意)设想这样一个场景:客户端已经向服务端发出了一个 POST 请求,但请求超时了,客户端因此无法确定该资源最后处于什么状态。

紧跟着一句更值得抄进验收文档:

(译意)把同一个请求重复发出多次,可能导致重复创建或错误的更新。

来源:draft-ietf-httpapi-idempotency-key-header-07。这份文档的性质需要说明清楚。它是 2025 年 10 月发布的草案第 07 版。页面标注的有效期是 2026 年 4 月 18 日,截至本文更新时该日期已经过去,所以不能当作现行正式标准。IETF 也声明这类草案只能作为进行中的工作参考,不能当规范引用。它的价值在于把"结果未知"定义清楚,而不是给你一条可以照抄的强制标准。

真正在生产里跑的商业实现,可以看 Stripe 的口径,它对结果保存的边界写得非常具体:

(译意)对 API v1,只有在端点开始执行之后我们才会保存结果。如果传入参数未通过校验,或者该请求与另一个正在并发执行的请求发生冲突,那么由于没有任何 API 端点启动执行,我们不会保存幂等结果;这类请求可以重新发起。

以及重试时到底返回什么:

(译意)对 API v1,端点开始执行后,Stripe 会保存用某个幂等键发出的第一个请求的状态码和响应体;之后用同一个键重试,返回的就是那份已保存的响应,500 错误也不例外。

来源:Stripe API 文档:Idempotent requests。两段话拼起来才是完整的机制:结果在端点开始执行后就被保存,之后用同一个键重试会拿回保存的那份响应,连 500 也一样;而参数校验失败、并发冲突这类"端点根本没开始执行"的情况不保存结果,因此可以放心重试。

(建议)验收时问供应商两个问题,就能判断它对这套机制是真懂还是背过名词:一是"超时之后你们重试用的是同一个幂等键还是新键",二是"端点开始执行但下游还没返回的那段时间,客户端看到的是什么"。第二个问题的答案必须是"未知,等待或按同一键查询",而不是"失败"。

三、幂等键要验的不是"有没有传",而是这五个点

很多验收单上只写一句"接口需支持幂等"。这句话可测性为零。把下面五个点拆开,每一项都能在测试环境里被证伪:

测点参考口径(来源见文末)通过标准典型不通过表现
键的唯一与不可挪用 草案要求键必须唯一,且不得用同一键发送载荷不同的请求 同键换参数时下游明确拒绝,而不是把它当新请求执行 静默执行了第二次,参数按后一次算
载荷一致性校验 Stripe 会把传入参数与原请求比对,不一致则报错 篡改金额或对象后同键重发 → 报错 无校验,直接返回上一次的响应体
同键并发 草案:原请求仍在处理时应回 409,并说明该键已有请求在处理中 并发第二个请求收到 409,客户端等待而非改键重发 两个请求都穿透到下游,各自执行一次
键的保留窗口 Stripe:v1 的键可在至少 24 小时后清理 文档写明窗口长度;窗口外同键重发被当作新请求且有告警 窗口长度无人知晓,事后对账才发现重放
键在下游可见 —(这是我们的建议,不是任何规范的要求) 按幂等键能在日志或库里反查到那次调用的最终状态 键只在 Agent 侧存在,出问题只能靠业务字段猜

草案对 409 这一档的处理还有一句常被忽略的话:客户端在重试前必须先修正请求,唯一例外是 409——因为原请求还在处理中,没什么可修正的,等它出结果就行。这句话落到验收上就是一条明确的交互要求:Agent 收到 409 时不允许换键重发,也不应该把这次调用记成失败。

四、重试策略的验收:上限、层次、可重试判定

"我们配了重试"不是验收结论。AWS 那篇文章里有个数字,适合在评审会上让所有人安静下来:假设调用链有五层,每层各自重试三次,数据库开始失败的时候,"数据库上的负载将增加 243 倍,导致恢复几乎成为不可能完成的任务"。它的建议是,对于低成本的控制面和数据面操作,在堆栈中的单个点执行重试。

把这个结论搬进 Agent 项目的验收,要检查三件事:

  • 重试只能有一层。Agent 框架层、网关层、下游 SDK 层,三层各自重试,等于一次失败放大成几十次写请求。验收时要在链路上问清楚"到底哪一层在重试",并让其余层显式关闭自动重试。
  • 重试要有次数上限和预算。AWS 的原文提醒"重试是自私的"——客户端在索取更多服务端资源。没有上限的重试在故障期间等于二次攻击。建议把总时长预算写进验收项,而不是只写次数。
  • 可重试与否必须是机器可读的。HTTP 用客户端错误和服务端错误区分,前者重试也不会成功;但最终一致性会让这条界线变模糊,AWS 原文也承认这一点。2026 年 6 月的一份面向 Agent 的 HTTP 接口草案把话说得更直白:Agent 会在超时后重试、会在结果不明时重试、还会因为计划变化而再发一次请求,写操作的误触发代价比人工客户端更高,因此错误里应该带一个 Agent 能读的 retryable 标记(draft-gaikwad-agent-friendly-http-api-profile-01;纯文本版)。

同一种错误码该不该重试,往往只能看下游自己那份文档怎么写的,并没有一把到处通用的尺子。所以验收时不能从任何一篇文档里抄一条规则,只能对着一张明确的错误码表逐条确认,并把这张表作为交付物收下。错误码表、超时、熔断这些工具接口层面的防御性设计,站内Agent 工程化与工具接口设计里有更细的拆解,可以两篇对着看。

五、人工核对要变成机制,不是一句兜底口号

模型上下文协议(MCP)的规范在工具页的安全考虑里有一句 SHOULD 级别的表述:

(译意)出于信任、安全与保障方面的考虑,工具调用的过程中始终应当(SHOULD)有一个人在回路中,并且这个人有能力拒绝工具调用。

规范同时建议在调用工具时给出明确的视觉提示,并对操作类调用给出确认提示(MCP 规范 Tools 页,2026-07-28 版本)。注意这是 SHOULD 而不是 MUST——规范给了方向,具体机制得项目自己设计。

(建议)"人工核对"能不能验收,取决于三个环节是否都能当场演示,而不是请假想:

  1. 待核队列。凡是进入"结果未知"状态的写操作,必须落到一张可查询的待核表里,字段至少包含幂等键、业务单号、下游接口、发起时间、当前状态。验收要求:注入一次超时,队列里必须出现对应记录,且带得回业务单号。
  2. 按幂等键反查最终状态。人工核对的本质是"去问下游到底发生了什么"。这要求下游提供按幂等键或业务单号查询的只读接口。验收要求:用待核表里的键能查出"已成功/不存在/处理中"三种结论之一,查不出第四种含糊答案。
  3. 处置闭环与留痕。核对之后要么确认已执行(不再重发),要么判定未执行(用同键重试),要么走人工补偿(冲正、撤销、通知客户)。验收要求:三个动作都有操作人、时间、依据记录,且 Agent 不会在处置完成后按原计划再发一次。

时延也是验收项。从"产生待核记录"到"有人完成处置"的时间要能被统计出来,否则这套机制在第一次大促就会被绕过。追溯留痕的粒度问题(谁调了什么、请求体和响应留多久、日志里能不能查到具体字段)在站内《MCP 协议接入内网:Agent 工具层的权限、审计与网关》里拆得更细,本文不重复。

六、一张可以带进验收会的故障注入表

下面这七条是我们建议的最小集合,每条都能在测试环境用代理、开关或手工造数复现。它们的共同特点是:只看日志看不出问题,必须观察业务结果和下游状态。

编号怎么注入期望看到判不通过
1 让下游响应超时,但服务端继续执行并最终成功 Agent 侧记录为"结果未知",进入待核队列;用同一幂等键查询得到"已成功" Agent 直接判失败并重发,下游出现两条业务单据
2 关掉幂等键向下游的透传 这个改动应当在自检或告警里被发现(键在下游不可见即可疑) 无人察觉,直到重复执行发生
3 同一个键,第二次请求改掉金额或对象 下游明确拒绝,Agent 不重试,错误进入人工视野 下游按第二次参数执行,或静默返回第一次的结果
4 同时发出两个同键请求 后到的一个收到 409,Agent 等待而非换键 两个都穿透执行
5 重试到第三次时,下游其实第一次就成功了 三次请求收敛为一次副作用,业务侧只有一条记录 出现两条及以上,或者对账口径对不上
6 超过幂等键保留窗口后再发同一键 被当作新请求,但触发告警或人工提示 无声地执行了第二次
7 人工拒绝或纠正之后,Agent 继续执行原计划 计划被阻断,执行链路终止并留痕 Agent 换个措辞绕开拒绝继续写

第 7 条尤其值得强调。人工核对环节最容易失效的方式,不是没人看,而是 Agent 在被拒绝后重新组织一次调用,把"人"变成流程里的装饰。

七、项目经理要拿到书面回答的八个问题

(建议)把这份清单在合同附件或验收记录里逐条要答案,答不上来的按未通过处理:

  1. 哪些工具属于写操作?请给完整清单,以及每个工具对应的下游接口。
  2. 每个写操作是否有幂等键?键由谁生成、生成规则是什么、生命周期多长?
  3. 下游是否真的接收并校验幂等键?如果下游不支持幂等,替代方案是什么(去重表、业务唯一约束、单号预占)?
  4. 整个链路上有几层在做自动重试?其余层的自动重试是否显式关闭?
  5. 超时时间怎么定的?依据是下游哪个延迟分位?
  6. 错误码表里哪些可重试、哪些不可重试?这张表是谁维护的,下游改了怎么同步?
  7. "结果未知"状态如何呈现给业务人员?待核队列在哪、谁负责、处置时长有统计吗?
  8. 重复执行的检测手段是什么?靠什么发现,多久能发现,发现后怎么补偿?

这八个问题有个共同点:都不能用"放心,我们有处理"来回答,只能给文档、给表、给演示。它们和成功率、人工兜底率那些指标并不冲突——指标告诉你系统整体好不好,这八问问的是出错那一刻系统还认不认账。指标怎么定义、评测集怎么造,可以对照站内《Agent 定制开发交付实录:5步流程、验收标准写法与3类踩坑》一起用。

八、什么情况下可以简化

这套东西不便宜,全部套用在低风险场景上没有必要。我们的判断是,满足以下条件的写操作可以降低强度:动作只影响草稿或可撤销状态、执行前有人工审批闸门、单次价值低且事后可低成本补偿。

反过来,三类动作必须按最严格标准验收:不可撤销的对外动作(发短信、发邮件、对外通知)、涉及资金或权益的动作(退款、扣款、发券)、以及跨系统多步写入但缺少补偿接口的动作。最后这类的风险在于,中间某一步成功了而后续步骤失败,系统会长期停在一个没人定义过的中间态。

还有一类情况需要提前说清楚:如果下游系统本身没有幂等能力,也没有可查询的业务单号,那么"Agent 层做幂等"是做不到的——键存在 Agent 这边,下游每次都会照做。这种场景的验收结论应该写成"依赖人工核对兜底,接受一定窗口内的重复风险",而不是签一份写着"已支持幂等"的记录。

参考

说明:本文引用的两份 IETF 文档均为草案,不能作为标准或法规依据;两份文档对同一问题的表述并不完全一致,落地口径请以下游接口的实际文档为准。

分享到
全国城市服务
Agent 写操作验收:三种失败状态与幂等重试验… - 优码云博客