退款接口成功但Agent未收到响应时是否可以安全重试
时间:2026-08-18 | 作者:冻月看渠 | 阅读:0一个 Agent 调用退款接口:
POST /refunds
业务系统已经完成扣账、生成退款单,并提交到支付渠道。
但就在响应返回前,网络连接中断了。
Agent 运行时看到的只有:
timeout
这时候最自然的动作似乎是:
调用失败 -> 重试
可第一次请求也许根本没有失败。
它只是“响应没有被调用方看到”。
如果系统直接再发一次请求,可能产生:
- 两笔退款;
- 两条库存调整;
- 两次优惠券发放;
- 两个账号;
- 两次部署;
- 两份不可逆的外部指令。
这说明 Agent 执行真实业务动作时,必须区分三个完全不同的问题:
传输有没有成功?业务后果有没有发生?调用方有没有拿到足够证据?
三者不能被一个 success / failed 布尔值代替。
为什么“失败就重试”在 Agent 场景更危险
在只读查询中,重试通常问题不大:
GET /orders/SO-1001
多查一次,最多增加延迟和负载。
但 Agent 的目标是完成任务,它可能连续调用:
创建退款修改库存关闭账号发送合同触发部署提交采购
这些操作会改变真实世界。
模型和运行时还可能把错误文本理解成新的行动提示:
第一次失败了,请再试一次
如果系统缺乏稳定的任务身份、业务幂等键以及结果查询机制,Agent 在执行过程中越是“积极”地追求目标,就越容易引发重复的副作用。
所以,重试策略不能只围绕 HTTP 状态码设计,而必须围绕业务后果设计。
先区分三种成功与失败
一次 Agent 业务调用至少存在三层状态:
| 层次 | 成功意味着什么 | 失败意味着什么 |
|---|---|---|
| 传输层 | 请求和响应完成交换 | 超时、断连、网关错误 |
| 业务层 | 业务系统产生或拒绝业务后果 | 业务校验失败、权限不足、事务回滚 |
| 证据层 | 调用方获得可验证的结果与审计记录 | 结果丢失、审计写入失败、回执不完整 |
下面这个状态完全可能发生:
传输层:失败业务层:成功证据层:失败
也就是:
退款已经发生但 Agent 没收到响应审计系统也暂时不可用
如果运行时把它压缩成:
{ "status": "failed"}
上层很可能自动重试,从而制造第二次业务效果。
任务身份、调用尝试和业务幂等键不是一回事
为了正确处理重试,系统至少需要区分三种标识。
持久任务 ID
task_id = task-7f3...
它代表用户希望完成的一个业务目标:
为订单 SO-1001 申请退款 199 元
无论任务经历多少次暂停、恢复、重启或查询,这个身份都不应改变。
传输尝试 ID
attempt_id = attempt-03
它代表某一次向下游发出的网络请求。
同一任务可能有多个传输尝试,但这不意味着可以产生多个业务后果。
业务幂等键
idempotency_key = refund:SO-1001:199.00:duplicate-payment
它由业务系统识别,用来保证同一业务意图重复到达时,只兑现一次结果。
正确关系是:
一个持久任务-> 可以有多个传输尝试-> 但必须绑定同一个业务幂等身份-> 最终只允许一个业务后果
如果每次重试都生成新的幂等键,幂等机制等于不存在。
execution.idempotent: true 不是运行时的魔法
能力声明可以表达:
execution:idempotent: true
但这个字段不能凭空让接口变得幂等。
它只能表示:
真正的幂等必须由业务系统兑现。
例如:
UNIQUE (tenant_id, idempotency_key)
或者:
收到相同幂等键-> 返回第一次执行的结果-> 不重复创建退款
运行时负责:
- 生成或传递稳定幂等键;
- 确保重试不改变业务身份;
- 保存任务和尝试之间的关系;
- 在结果未知时优先查询而不是盲目重放。
业务系统负责:
- 原子地记录幂等键和业务结果;
- 对重复请求返回同一业务结果;
- 保证并发请求不会产生多个副作用。
二者缺一不可。
结果未知必须是一种显式状态
很多系统只有:
pendingsuccessfailed
这对于真实业务执行不够。
至少还需要:
outcome_unknown
它表示:
请求已经发出但当前无法证明业务效果发生或未发生
进入这个状态后,系统不应直接自动重试高风险写操作,而应:
- 使用幂等键查询业务系统;
- 查询业务对象当前状态;
- 检索下游回执或支付流水;
- 等待异步回调;
- 必要时进入人工对账。
只有确认第一次请求没有产生业务效果,或者业务系统能够保证相同幂等键不会重复执行时,才可以继续传输重试。
一个更准确的任务状态机
可以把执行过程建模为:
approved-> dispatching-> acknowledged-> committed-> evidence_recorded
故障可能发生在任何两步之间。
因此,外部可见状态可以包括:
| 状态 | 含义 | 是否允许自动重试 |
|---|---|---|
not_dispatched |
尚未向业务系统发送 | 可以 |
dispatching |
已开始发送,结果尚不明确 | 取决于幂等保证 |
outcome_unknown |
无法确认业务后果 | 默认不允许 |
succeeded |
已确认成功并获得结果 | 不需要 |
failed_before_commit |
已确认提交前失败 | 可以按策略重试 |
committed_evidence_degraded |
业务已成功,但证据链不完整 | 不允许重复执行 |
reconciliation_required |
需要查询或人工对账 | 不允许盲目重试 |
这比“接口报错了”更接近真实世界。
审计存储故障,不能抹掉已经发生的业务事实
考虑下面的顺序:
1. 业务系统完成退款2. 返回成功3. 运行时写审计日志4. 审计存储不可用
如果系统因为第 4 步失败,向上层返回:
执行失败,请重试
就会产生严重误导。
真实状态不是“失败”,而是:
业务已提交证据记录降级需要补写或对账
因此应当显式表示:
{ "status": "committed_evidence_degraded","retryable": false,"reconciliation_required": true}
治理系统可以选择在执行前要求审计存储可用,以实现 fail closed。
但如果业务提交已经发生,就不能假装它没有发生。
此时重点应从“是否执行”切换到“如何补全证据与恢复一致性”。
不可变执行信封让重试可以被验证
为了证明多次尝试属于同一业务行动,可以构造一个不可变执行信封:
{ "task_id": "task-7f3...","trusted_subject": "user-1008","capability": "refund.request.create","canonical_arguments": { "order_id": "SO-1001","amount": "199.00","currency": "CNY","reason": "duplicate-payment"},"policy_version": "refund-policy-2026-07-30","approval_evidence": "approval-...","business_idempotency_key": "refund:SO-1001:199.00:duplicate-payment","downstream_result_reference": null}
对该信封进行规范化和哈希:
execution_hash = SHA256(canonical_execution_envelope)
接下来的每一次传输尝试,都会严格复用同一组标识:task_id、business_idempotency_key 以及 execution_hash。
这样,审计人员可以区分:
同一业务行动的传输重试
与:
参数或主体已经变化的第二次业务请求
但要注意:
不可变信封!= 最终授权!= 业务幂等实现!= 审批永久有效
它只是让行动身份和证据关系可验证。
九个关键故障点
一条受治理执行链路至少应测试以下故障:
- 审批持久化后、下游发送前崩溃;
- 下游已提交、响应返回前断连;
- 响应收到后、任务状态持久化前重启;
- 业务成功后审计存储不可用;
- 任务被接受后协调器重启;
- 审批暂停期间策略版本变化;
- 审批过期后任务恢复;
- 同一幂等键并发到达业务系统;
- 结果查询接口暂时不可用。
每个测试都不应只验证“最终状态看起来正确”,而应证明:
同一个持久任务-> 最终收敛到明确状态-> 不产生第二次业务效果-> 能解释每次传输尝试
一个退款故障示例
第一次调用:
task_id: task-001idempotency_key: refund-SO-1001attempt_id: attempt-001
业务系统完成退款,但响应超时。
运行时进入:
outcome_unknown
这时不应创建:
idempotency_key: refund-SO-1001-retry-2
而应使用原键查询:
GET /refunds/by-idempotency-key/refund-SO-1001
如果查询返回:
{ "status": "succeeded","refund_id": "RF-9001"}
运行时应把原任务收敛到成功,而不是再次调用退款接口。
如果查询仍无法确认:
reconciliation_required
比“再试一次看看”更诚实,也更安全。
ACC 在这里负责什么,不负责什么
ACC 可以声明:
- operation 是否为只读;
- 是否声称具备幂等语义;
- 建议超时;
- 速率限制;
- 风险等级;
- 是否需要可信主体和审批。
这些语义帮助不同运行时选择保守执行策略。
但 ACC 不替代:
- 业务系统的幂等账本;
- 分布式事务;
- 下游状态查询接口;
- 任务队列持久化;
- 审批证据存储;
- 审计补偿流程;
- 最终业务授权。
因此,声明:
execution:idempotent: true
不是安全证明,而是一项需要实现方兑现和测试的契约。
八个常见误区
误区一:HTTP 超时就是业务失败
超时只说明调用方没按时获得响应。
误区二:重试时生成新 request ID 更安全
新的传输 ID 可以生成,但业务幂等身份必须保持稳定。
误区三:运行时去重就足够
并发、重启和旁路调用仍可能绕过运行时,最终幂等必须由业务系统兑现。
误区四:审计写失败就返回通用失败
如果业务已经提交,通用失败会诱发重复执行。
误区五:幂等只适用于支付
创建账号、发券、发货、部署、发消息、修改库存都可能需要业务幂等。
误区六:相同参数一定是同一行动
还要比较主体、租户、能力、目的和业务对象上下文。
误区七:有幂等键就不用查结果
幂等键防止重复副作用,结果查询帮助任务收敛到可解释终态。
误区八:模型可以根据错误信息决定是否重试
高风险重试必须由确定性运行时策略控制,不能交给模型临场猜测。
一份实现检查表
- [ ] 是否区分持久任务 ID、传输尝试 ID 和业务幂等键?
- [ ] 同一业务意图重试时,幂等键是否保持不变?
- [ ] 业务系统是否原子地兑现幂等语义?
- [ ] 是否存在
outcome_unknown或等价状态? - [ ] 高风险写操作结果未知时,是否默认禁止盲目重试?
- [ ] 是否提供按幂等键或业务对象查询结果的路径?
- [ ] 业务已提交但审计失败时,是否返回非重试的降级状态?
- [ ] 运行时重启后,任务和审批证据是否仍可恢复?
- [ ] 是否能区分一次业务行动的多次传输尝试与第二次业务请求?
- [ ] 故障测试是否验证“没有第二次业务效果”?
- [ ] 业务系统是否仍然执行最终权限和状态校验?
结语:可靠执行的目标不是“尽量成功”,而是“只产生一次正确后果”
Agent 很擅长持续尝试。
这在搜索信息、生成文本和调用只读工具时通常是一种优点。
但当它开始退款、改库存、创建账号或触发部署时,“再试一次”可能不是韧性,而是事故。
因此,真实业务执行需要一种更严格的可靠性目标:
同一个用户意图-> 一个持久任务身份-> 一组不可漂移的行动参数-> 一个业务幂等身份-> 最多一次业务后果-> 一个可解释的终态
接口是否返回 200,只是这条链路中的一个瞬间。
真正重要的是:
Agent 从“会调用工具”走向“可以承担真实业务执行”,前提不是更激进地重试,而是确保同一业务意图只产生一次正确后果。
免责声明:文中图文均来自网络,如有侵权请联系删除,心愿游戏发布此文仅为传递信息,不代表心愿游戏认同其观点或证实其描述。
相关文章
更多-
- SDD是什么东西?一文看懂SDD的定义与作用
- 时间:2026-08-21
-
- 长沙通优惠券查询方法
- 时间:2026-08-19
-
- 一鹿拼签到方法与操作步骤详解
- 时间:2026-08-17
-
- Yahoo域名转出超详细图文教程完整版操作指南
- 时间:2026-08-16
-
- 抖省省App已领取优惠券查看入口在哪儿
- 时间:2026-08-15
-
- Golang与Gin框架实现简易优惠券核销系统
- 时间:2026-08-14
-
- 众合在线App优惠券怎么用及使用方法指南
- 时间:2026-08-13
-
- 阿里云16核32G云服务器价格查询:包年包月与按量付费明细
- 时间:2026-08-10
精选合集
更多大家都在玩
大家都在看
更多-
- 糖尿病完全不能吃糖吗
- 时间:2026-09-15
-
- 蚂蚁庄园小课堂2026年9月16日最新题目答案
- 时间:2026-09-15
-
- 小鸡答题今天的答案是什么2026年9月16日
- 时间:2026-09-15
-
- 蚂蚁庄园每日答题答案2026年9月16日
- 时间:2026-09-15
-
- 以下哪种粮食是酿造绍兴黄酒的主要原料 蚂蚁庄园今日答案9月16日
- 时间:2026-09-15
-
- 劝学名句“及时当勉励,岁月不待人”出自哪位诗人 蚂蚁庄园今日答案9.16
- 时间:2026-09-15
-
- 蚂蚁庄园今天答题答案2026年9月16日
- 时间:2026-09-15
-
- 蚂蚁庄园答题今日答案2026年9月16日
- 时间:2026-09-15
