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

智能体工具重试,如何避免重复扣款退款

dev.to · AI#智能体#幂等性#后端
Editor brief · 编辑速览

网络超时、模型重复调用及崩溃恢复,都可能让同一工具动作执行多次。文章建议为会产生副作用的工具引入幂等键,防止重复退款、发送邮件或创建工单。

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

AI 深度解读

Agent 调用退款工具超时,框架重试,客户却收到两笔退款。问题可能不在模型:第一次请求已经成功,只是结果没传回来。作者的核心主张是,把后端早已使用的幂等键带进工具层,让同一次业务操作重试多次,也只产生一次副作用。

这篇值得读的地方,是把网络重试、模型重复调用、任务崩溃恢复放到一起看。做 Agent 时,除了管模型调用哪个工具,还得管工具执行两次会发生什么。

为什么换个提示词挡不住重复退款

作者列出了四条重复执行的路径:

  • HTTP 客户端或 Agent 框架在超时后重试,但服务端第一次已经执行成功。
  • 模型再次请求同一个工具。原文援引 OpenAI 文档:一次响应可以包含零个、一个或多个调用,某个 gpt-4.1-nano 快照开启并行调用后,还可能重复调用同一工具。
  • Worker 在完成副作用后、保存工具结果前崩溃,恢复时重放步骤。
  • 工具输出被截断、出错或丢失,模型没看到结果,于是再次尝试。

这些路径不都受提示词控制。模型甚至可能做出了合理决定:它不知道退款已经成功。

作者建议先把工具分成读取、幂等写入、非幂等写入三类。读取通常可以重复;幂等写入重复执行后仍保持相同目标状态;退款、发邮件、建工单这类非幂等写入,则需要额外保护。

该去重的是同一次业务操作,而不是所有参数相同的请求。

幂等键怎样让重试拿到第一次结果

幂等键(idempotency key)相当于一次业务操作的凭证。客户端重试时继续提交同一个凭证,服务端识别后返回已保存的结果,避免重新执行。

作者以 Stripe 为例说明具体约定:

  • 请求通过 Idempotency-Key 请求头携带键,建议使用 V4 UUID 或其他高随机性字符串,最长 255 个字符。
  • 对 API v1,请求开始执行后,Stripe 保存首次请求的状态码和响应体。后续同键请求拿到保存的响应,包括 500 错误。
  • 同一个键携带不同参数会报错,防止它被悄悄用于另一件事。
  • 键至少保留 24 小时后可以清理;清理后再次使用,会被当成新请求。
  • 键里不要放邮箱、个人标识等敏感信息。

这里最容易写错的是生成时机:每次 HTTP 尝试都生成新 UUID,完全起不到去重作用。同一次意图的重试必须沿用原键。

作者给出的来源包括 Agent run ID 加步骤编号、OpenAI 返回的工具 call_id,以及业务键,例如 refund:{order_id}。最后一种有明确前提:业务规则确实是每个订单最多退款一次。

最小封装能说明思路,但还不能直接上线

下面这段代码用运行标识、工具名和排序后的参数生成键,并尝试复用保存的结果:

import hashlib, json

def idem_key(run_id: str, tool: str, args: dict) -> str:
    payload = json.dumps(args, sort_keys=True)
    return hashlib.sha256(f"{run_id}:{tool}:{payload}".encode()).hexdigest()

def run_side_effect(store, run_id, tool, args, fn):
    key = idem_key(run_id, tool, args)
    saved = store.get(key)              # e.g. a Postgres table with a UNIQUE key
    if saved is not None:
        return saved                    # replay: return the first result, don't redo it
    result = fn(**args, idempotency_key=key)  # pass through if the downstream API supports it
    store.put(key, result)
    return result

要盯住的是 store.get(key) 到 fn(...) 之间的空隙。两个并发调用可能都查不到记录,随后各执行一次。作者因此补充:应先插入 pending 记录,用数据库唯一约束拒绝重复,而不能只做「先查再写」。示例没有实现这部分。

另一个空隙发生在下游操作成功之后:如果本地 store.put 失败,下次仍可能重新调用下游。作者建议把键继续传给支持幂等的服务商。本地存储负责复用结果,下游的幂等能力负责挡住这类重复请求。

重放结果也要让模型看得懂,例如:

{"status": "already_done", "refund_id": "re_123", "note": "This refund was already issued for this order. Do not retry."}

注意 status 和 refund_id:它们明确表达操作已完成,并给出已有结果。含糊的错误更容易引发再次尝试,但这种返回格式也不能保证模型一定停止。

我的判断

我赞成把幂等性放进工具接口设计。它直接处理执行层问题,比继续叠加「不要重复退款」的提示词更切题。但原文没有给出事故统计或对照实验,应把它看作工程经验建议。

键的作用范围还需要自己判断。call_id 适合同一个调用的重放;模型重新发起一次调用时,原文没有说明它会沿用旧标识。示例的哈希方案也会把同一 run 内、同工具同参数的操作合并,若用户本来就想执行两次,便可能误去重。

另外,唯一约束只解决并发争抢,不会自动消除外部副作用与本地落库之间的崩溃窗口。下游不支持幂等时,原文没有给出完整方案。pending 记录如何恢复、等待者何时重试,也没有展开。

对特定模型设置 parallel_tool_calls=false 可以减少一种重复来源,网络重试和崩溃恢复仍然需要处理。

能拿来做什么

  • 给日常 AI 编程项目里的工具补上读取、幂等写入、非幂等写入分类,优先检查退款、邮件和工单等会改变外部状态的工具。
  • 在 Node 工具层统一传递业务操作键,让框架重试和任务恢复沿用它;采用 refund:{order_id} 前,先确认是否允许分次退款。
  • 保存执行状态和首次结果,用数据库唯一约束处理并发,并将键传给支持幂等的下游 API。
  • 按作者建议做一次故障测试:在副作用完成后立即终止 Worker,再重放运行,检查是否只产生一次退款,以及工具是否返回已有结果。
Original source

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

阅读原文
上一篇
多智能体分工:采集、裁决与执行分离
下一篇
设计Agent权限,先分清身份与审批

资讯速览

dev.to · AI · 2026-11-09 17:20

AI 评分 8.0 / 10

相关标签

#智能体#幂等性#后端

继续阅读

  • Seedream 5.5强化连续编辑与透明素材生成
  • 多智能体分工:采集、裁决与执行分离
  • 设计Agent权限,先分清身份与审批
  • 智能体校验如何避免把未知当作通过
  • 访问日志揭示假冒爬虫与敏感文件探测