基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
知识卡片
  • 场景篇按分类整理的大前端场景考点
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
  • 动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
  • AI 助手随时提问,即时解析
  • AI 模拟面试模拟真实面试 + 报告
  • AI 知识地图串起全站知识点
  • AI 定制路线按你的简历现排
AI 热点
旧版
AIHT
内容
精选全部 AI 动态热点榜AI 日报收藏
分类
模型行业编程产品技术观点开源
更多
Agent 接入
返回首页开发者导航
AIHT
精选 / 资讯详情
8.0
热点
AI SCORE
技术实践2026-10-24 06:34

Agent 恢复执行时,如何避免重复退款

dev.to · AI#Agent#幂等性#故障恢复
Editor brief · 编辑速览

文章通过稳定操作 ID、执行前持久化意图、传递幂等键及记录执行结果,构建工具调用的恢复协议。遇到外部系统不支持去重且执行结果未知的情况,应先核对结果或转人工处理。

文章思维导图
Knowledge map
拖拽缩放
AI deep read

AI 深度解读

Agent 发出退款请求,支付平台已经处理成功,后面的模型调用却报错了。此时从检查点重跑,退款工具可能再执行一次。原文讨论的就是这类恢复事故:怎样让一次业务动作在重试、进程重启和模型重新生成调用之后,仍然被识别为同一件事。

作者给出的办法是稳定的操作 ID、持久化执行账本,以及服务商支持的幂等机制。它不能保证任意外部系统都只执行一次,但能让已完成的动作被复用,让结果不明的动作进入核对流程。思路值得采用,不过原文的 Python 演示有明显问题,不能直接照搬。

重试之前,先弄清究竟哪一步失败了

原文区分了四类容易混在一起的情况:请求尚未发出就失败;请求发出后连接超时;工具成功后模型调用失败;工作流恢复时重新执行未完成节点。

它们对应的处理方式不同。尤其是网络超时,只能证明调用方没有拿到响应,不能证明服务商没有执行。退款、邮件、数据库写入都有可能已经完成。

请求可能已经发出时,默认状态应该是「结果未知」,而不是「执行失败」。

原文引用的 Agno issue 展示了一个具体案例:工具执行成功,后续模型报错,整个运行重试后重复调用工具。这是问题报告和复现,不是事故发生率统计。文中提到的研究也强调了相同边界:Agent 运行时和外部工具通常没有共享事务,单一的成功或失败信号不足以判断副作用是否发生。

这里还要区分两种 ID。模型的 tool-call ID 标识某条消息里的调用;业务 operation ID 标识一次明确的业务意图,例如「给 invoice-17 执行第 2 次退款调整」。

恢复后,模型可能为同一个意图生成新的 tool-call ID;反过来,只拿客户 ID 去重,又可能吞掉这个客户后续的合法退款。原文给出的业务键示例是:

workflow-81:invoice-17:refund-2:v1

它包含工作流、业务对象、动作序号和版本。关键是由工作流边界生成并持久化,同一次意图始终复用。每次重试随机生成一个键,无法达到去重目的;把整段提示词做哈希也不可靠,因为措辞变化不等于业务意图变化。

执行账本要记录未知状态,不能只有 done

作者建议用四个基本状态描述一次动作:

  • prepared:业务意图已经接受,操作 ID 已经确定。
  • in_flight:请求可能已经开始发送。
  • succeeded:服务商结果已经持久化。
  • unknown:执行器无法证明成功,也无法证明没有执行。

只有确定副作用未发生,例如本地校验在发送前拒绝请求,才适合使用额外的 failed 状态。

单个 done=true 补不上这个缺口。发送前写,可能标记完成后进程崩溃,动作实际没发出;发送后写,可能服务商已经完成,进程却在落库前崩溃,恢复后再次发送。

账本还需要保存 run ID、operation ID、工具名称及版本、规范化参数哈希、服务商、状态、尝试次数、创建与更新时间、服务商请求 ID、结果引用和脱敏错误分类。它应当能定位权威结果,不必再复制一份客户数据库。

下面这段原文代码展示了两个关键检查:同一操作 ID 的参数是否变化,以及成功结果能否直接复用。

        row = db.execute(
            "SELECT params_hash, status, result FROM action_ledger "
            "WHERE operation_id = ?", (operation_id,)
        ).fetchone()
        if row and row[0] != digest:
            raise ValueError("operation ID reused with changed parameters")
        if row and row[1] == "succeeded":
            return json.loads(row[2])

要看的是参数冲突那一行:退款金额或收款人变了,不能继续沿用原操作的身份,也不能悄悄返回旧结果,应当先明确这是一次新动作。

执行顺序同样重要:先验证和授权,再创建或读取账本;成功记录直接返回,处理中或未知记录先核对;只有新的 prepared 动作才能被领取发送。服务商确认后,必须先保存结果,再让模型继续。

并发恢复还需要数据库唯一约束和原子领取,例如带旧状态条件的更新,或带 fencing token 的租约。两个 worker 都读到 prepared 后分别发送,进程内的 Python set 挡不住。

外部幂等键能缩小崩溃窗口,但有有效期

本地数据库和远程 API 是两个系统。即使本地事务写得再严谨,也存在「远程已提交,本地还没记录成功」的窗口。

服务商幂等键的作用,是让接收方识别重复请求。同一次业务意图重试时,发送相同键和相同参数,接收方按约定复用结果。Stripe 文档中的细节尤其重要:键可能在至少 24 小时后被清理,清理后再次使用可能产生新请求。自己的恢复窗口必须与服务商保留周期匹配。

如果服务商不支持去重,就需要按请求 ID、业务引用或其他稳定标识查询结果,并核对收款人、金额、币种、发票、目标和状态。查询暂时返回不存在,也可能只是索引尚未更新,需要轮询窗口、退避安排和最终升级条件。既不能安全重试,也无法查询时,就得人工处理。

原文用 SQLite 模拟服务商,数据库文件是 agent-replay-demo.sqlite3,连接启用 PRAGMA journal_mode=WAL,运行命令是 python demo.py。示例对 invoice-17 发起 2500 cents 退款,试图演示首次响应丢失、相同键恢复、重复读取相同结果。

但下面这段实现并没有可靠模拟它声称的行为:

        db.execute("INSERT INTO provider_keys VALUES (?, ?)",
                   (key, json.dumps(response)))
        if lose_first_response:
            raise TimeoutError("simulated lost response after provider commit")
        return response

注意抛出 TimeoutError 的位置:它还在 with connect() as db: 事务上下文中,异常退出会导致当前事务回滚,代码没有先提交。此外,完整函数先查询 provider_keys,随后才建表,在空目录首次运行会先遇到表不存在。

这属于静态阅读即可发现的问题。原文演示不能作为「服务商已经提交,响应随后丢失」的有效验证;修复时需要先初始化表,并把服务商提交与响应丢失分成两个阶段。

我的判断

把恢复决策交给确定性的执行器,而不是交给模型回忆,是这篇最值得采用的结论。

对话记录可能被压缩、丢弃,或者不在恢复出的检查点里。模型可以提出调用,执行器必须根据账本判断这是新动作、已完成动作,还是待核对动作。

但原文没有提供重复执行率、吞吐损耗或不同框架的对照数据。这是一套有工程依据的设计建议,不能据此认定某种 Agent 架构已经得到生产验证。演示代码的缺陷也进一步限制了它的证明力度。

与常见做法相比,增加重试次数只能提高部分瞬时故障的成功率;限制最大迭代数、函数调用数和运行时长可以约束工作量,却不能识别已经完成的退款。原文提到 Microsoft 的相关限制是尽力执行、在请求批次之后检查,也不应把它当成防重复机制。NIST 的 Govern、Map、Measure、Manage 可以帮助分配责任,但其 Playbook 建议是自愿采用的,并不规定幂等实现。

还要区分本地与远程:如果业务修改和账本就在同一个数据库,可以放进同一事务,保证更强;跨支付、邮件或 SaaS API 时,不能把本地事务宣传成端到端 exactly-once。

能拿来做什么

  • 给 Node 服务中的退款、发信、创建外部工单等工具增加持久化 operation ID,并核对服务商的幂等键范围、参数比较规则和保留周期;每次恢复都先读账本。
  • 把多动作工作流拆到副作用粒度。发送邮件后再更新 CRM,如果第二步出错,应从第一个未解决的动作继续,已发送的邮件直接复用记录。
  • 增加「远程已完成、本地未保存」「响应已保存、后续模型失败」「两个 worker 同时恢复」的故障验证,并监控长期 in_flight、参数哈希冲突、重复回放和超期 unknown。
  • 在面向用户的状态中区分失败与未知。结果不明时显示「暂时无法确认是否完成」,同时保留原操作 ID 进入核对,避免用户或 Agent 再创建一次相同动作。
Original source

本文由 AI 通读原文后重新编写,观点部分为站内判断,细节以原文为准,来源 dev.to · AI,原文版权归原作者所有。

阅读原文
上一篇
把 AI 接入 Ghidra:从反编译到函数命名
下一篇
FixProof 用修复前后测试核验 AI 补丁

资讯速览

dev.to · AI · 2026-10-24 06:34

AI 评分 8.0 / 10

相关标签

#Agent#幂等性#故障恢复

继续阅读

  • 如何划定运维 Agent 的自主操作边界
  • 把 AI 接入 Ghidra:从反编译到函数命名
  • FixProof 用修复前后测试核验 AI 补丁
  • 用输出校验驱动大小模型分级调用
  • 端侧模型交付要按软件发行管理