智能体幂等性:为什么你的 AI Agent 会发送两次邮件
你的 Agent 处理了一笔退款,但响应超时了。框架进行了重试。结果客户收到了两次退款。你的 Agent 发送了一封跟进邮件,触碰了速率限制,在退避(backoff)后重试,结果客户收到了两条完全相同的消息。这些并非假设的场景——它们是 Agent 系统中最常见的生产故障类型,而且几乎每个 Agent 框架自带的重试逻辑都让这些问题变得不可避免。
根本原因看似简单:Agent 框架对所有工具调用的处理方式都一样,无论它是读取数据还是改变现实世界。get_user_profile() 调用重试一百次也是安全的。但 send_payment() 调用则不然。然而,大多数框架都将两者封装在相同的指数退避重试逻辑中,并美其名曰“可靠性”。
不确定完成问题
传统软件处理重试的心智模型非常直截了当:如果请求失败,就重试;如果成功,则不重试。但 Agent 运行在一 个“第三种状态”——不确定(uncertain)——最常见且最危险的世界里。
想象一下,当一个 Agent 调用支付 API 时,连接在 30 秒后断开。可能会发生三种情况:请求从未到达服务器;服务器处理了请求但响应丢失了;或者服务器仍在处理中。Agent 无法区分这些情况。每个 Agent 框架都默认将超时视为失败,这意味着会进行重试——并可能导致副作用的重复。
这种不确定完成问题在多步 Agent 工作流中会被放大。如果一个 Agent 在执行 14 个步骤的工作流中的第 7 步时崩溃,恢复逻辑需要确定前 7 步中哪些产生了副作用,哪些可以安全地重新执行,以及哪些需要人工审核。传统的“检查点与恢复”(checkpoint-and-resume)模式无法捕捉到这种细微差别,因为它们追踪的是执行在哪里停止,而不是已经有哪些影响传播到了现实世界。
读与写:大多数框架忽略的区别
对 Agent 可靠性影响最大的设计决策之一,几乎没有框架明确提出:即根据工具调用是“只读”还是“写入”操作进行分类。
只读操作——获取用户数据、查询数据库、检查账户余额——天生就是幂等的。你可以自由重试而不会产生后果。而写入操作——发送邮件、处理付款、创建资源、更新记录——则是每次重试都带有风险的地方。
然而,看看大多数 Agent 框架是如何定义工具的:名称、描述、一组参数以及要调用的函数。没有任何元数据指明该工具是否会改变状态。框 架的重试逻辑对两者一视同仁,用同样的错误处理策略对待 search_documents() 和 delete_account()。
解决方法始于在工具定义中明确这一区别。每个工具都应该声明它是只读的还是具有副作用。只读工具采用激进的重试策略——配合指数退避和抖动(jitter)重试五次。写入工具则采用保守处理:最多重试一次,并且仅在检查原始调用是否成功之后才进行。
幂等键无法简单映射到 Agent 工作流
支付 API 多年前就通过幂等键(idempotency keys)解决了重试问题。客户端生成一个唯一标识符,随请求一起发送,服务器确保带有相同键的重复请求返回缓存结果,而不是重新执行操作。Stripe、AWS 以及几乎所有严肃的支付处理器都支持这种模式。
但将幂等键应用于 Agent 工作流会引入支付 API 从未面临的复杂性。
粒度不匹配。 在支付 API 中,一个幂等键映射到一个逻辑操作:向这张卡收取这个金额。在 Agent 工作流中,一个用户意图(“帮我订下周二去东京的机票”)可能会分解为跨多个服务的十几个工具调用。每个工具调用应该有自己的幂等键吗?整个工作流应该共享一个吗?如果每一步都有自己的键,你可能会得到一个“正确但部分完成”的状态,即步骤 1 到 5 已完成,但步骤 6 失败了——而整个工作流的重试将跳过前五步,但在可能已过时的上下文中重新执行。如果工作流共享一个键,你又无法在不重新运行所有内容的情况下重试 单个步骤。
非确定性分解。 由于 LLM 是非确定性的,同一个用户请求在重试时可能会分解为不同的工具调用。“订去东京的机票”第一次可能尝试航空公司 A 的 API,而在重试时可能尝试航空公司 B。第一次尝试的幂等键无法保护第二次尝试,因为这在根本上是不同的操作。传统的幂等性假设相同的键意味着相同的请求——当 LLM 决定该做什么时,这个假设就失效了。
时间耦合。 幂等键具有生存时间(TTL),支付 API 通常为 24 小时。但 Agent 工作流持续的时间可能长得多——一个差旅预订 Agent 可能会在确认前保留预订好几天。如果幂等键在工作流完成前过期,重试就会在工作流最需要保护的时候失去保护。
真正有效的设计模式
考虑到这些限制,生产环境中的智能体 (Agent) 系统需要超越简单幂等键的模式。以下是四种已被证明行之有效的模式。
操作日志 (Operation Journals)
与其依赖工具层面的幂等键,不如在工作流层面维护一份已完成操作的日志。在执行任何写操作之前,Agent 会检查该日志。执行成功后,它会记录结果。重试时,它会重播日志以重建工作流状态,而不会重新执行副作用。
这是 Temporal 的事件历史 (event history) 和类似持久化执行引擎背后的模式。其核心洞察在于日志记录的是“影响” (effects) 而非“意图” (intents)。它不会记录“Agent 想要发送邮件”,而是记录“邮件 XYZ 已在时间点 T 发送,邮件 ID 为 M”。这使得即使在 LLM 的决策过程不确定时,重试也是确定性的。
两阶段工具调用 (Two-Phase Tool Calls)
将每个写操作分为预览阶段和提交阶段。预览阶段是只读的:它返回如果不执行会发生什么。提交阶段需要预览阶段提供的令牌,且该令牌具有较短的有效期。
这种模式具有双重作用。它使破坏性操作显式化且可确认,并创造了一个自然的检查点,让 Agent 在提交前验证其意图。如果 Agent 在预览和提交之间崩溃,不会产生副作用。如果它在提交后崩溃,操作已经完成,令牌会防止重复执行。
带补偿的影响跟踪 (Effect Tracking with Compensation)
对于多步工作流,维护一份已完成的影响列表以及相应的补偿动作——即撤销每个影响的逆向操作。如果一个 7 步工作流的第 5 步失败,你有两个选择:使用其幂等键重试第 5 步,或者补偿第 1 到第 4 步并重新开始。这就是分布式系统中的 Saga 模式,被适配到了 Agent 领域。
对于 Agent 而言,关键的补充是补偿动作本身也必须是幂等的。如果“发送邮件”的补偿是“发送撤回邮件”,你需要确保即使补偿逻辑本身被重试,撤回邮件也只发送一次。
条件执行卫句 (Conditional Execution Guards)
在执行写操作之前,查询目标系统以确定操作是否已经完成。如果付款已经存在,就不要再发起支付。如果用户已经存在,就不要再创建用户。如果邮件 ID 已经在发件箱中,就不要再发送邮件。
这种模式很简单,但在 Agent 系统中却被出人意料地低估了。它将幂等的负担从 Agent 框架转移到了业务逻辑上,而业务逻辑通常是处理它的正确位置。目标系统知道操作是否发生了——Agent 框架只是在猜测。
基础设施层:持久化执行 (Durable Execution)
这些模式很强大,但为每个 Agent 从零开始实现它们会很繁琐。这就是像 Temporal 构成的持久化执行引擎在 Agent 技术栈中占有一席之地的地方。
Temporal 的核心承诺——工作流代码将无视基础设施故障执行直至完成——直接解决了执行结果不确定的问题。每一次工具调用都变成了一个具有可配置重试策略、超时和心跳的“活动” (activity)。工作流引擎维护的事件历史充当了操作日志。如果一个 worker 在执行中途崩溃,另一个 worker 会接手工作流并重播历史记录,在不重新执行已完成活动的情况下重建状态。
持久化执行与 Agent 编排之间的契合是自然的,因为两者都在处理同一个根本挑战:协调长耗时、多步骤的过程,其中单个步骤可能会失败、超时或产生不确定的结果。区别在于分布式系统工程师在十年前就解决了这个问题,而 Agent 社区正缓慢地重新发现同样的解决方案。
但持久化执行并非万能药。Agent 的 LLM 调用本身引入了工作流引擎并非为此设计的非确定性。如果你重播一个工作流,而 LLM 做出了与最初不同的决策,重播就会与历史记录发生偏离。生产系统通过将 LLM 的决策记录为工作流历史中的事件来处理这个问题,从而使重播无论模型再次调用时会做出什么,都是确定性的。
构建你的幂等性检查清单
如果你正准备将 Agent 交付到生产环境,请使用以下问题审计你工具箱中的每个工具:
- 此工具是否有副作用? 如果有,它需要幂等保护。如果没有,可以自由重试。
- 我能否查询该影响是否已经发生? 如果你的邮件 API 返回消息 ID,请在再次发送前检查该 ID。如果你的支付 API 支持幂等键,请使用它们。
- 重复操作的爆炸半径是多少? 重复的 Slack 消息很烦人,但重复的电汇则是场官司。根据后果的严重程度来衡量你的保护力度。
- 补偿动作是什么? 如果你无法撤回(如发送邮件、在社交媒体发布信息),幂等保护必须是万无一失的。如果你可以撤回(如创建数据库记录),你就有退路。
- 工具调用是否依赖于前序步骤的结果? 如果是,你需要操作日志,而不仅仅是针对单个调用的幂等键。
令人不安的事实是,大多数 Agent 框架都为演示 (Demo) 而优化——在演示中,一切都在第一次尝试时就成功,副作用也无关紧要——而不是为生产环境优化,而在生产环境中,重试路径才是关键路径。在 2026 年交付可靠 Agent 的团队,不会是那些拥有最复杂提示词工程 (Prompt Engineering) 的团队。而是那些将每次工具调用都视为分布式系统中潜在重复消息的团队,因为事实正是如此。
- https://dev.to/klement_gunndu/your-api-wasnt-designed-for-ai-agents-here-are-5-fixes-2oem
- https://dev.to/aloknecessary/idempotency-in-distributed-systems-design-patterns-beyond-retry-safely-k66
- https://aws.amazon.com/builders-library/making-retries-safe-with-idempotent-APIs/
- https://temporal.io/blog/from-ai-hype-to-durable-reality-why-agentic-flows-need-distributed-systems
- https://github.blog/ai-and-ml/generative-ai/multi-agent-workflows-often-fail-heres-how-to-engineer-ones-that-dont/
- https://composio.dev/content/outgrowing-make-zapier-n8n-ai-agents
- https://fast.io/resources/ai-agent-retry-patterns/
