AI代理的PRG模式:一个25年前的修复方案在新纪元中走向成熟 - DEV社区

Dev.to AI 2026-06-25T09:11:19.401483

AI代理的PRG模式:一个25年前的修复方案在新纪元中走向成熟 - DEV社区

AI代理PRG模式封面图片:一个25年前的修复方案在新纪元中走向成熟

自90年代以来,一个经典bug一直困扰着Web表单。你可能见过——浏览器警告说“重新提交此表单将重复该操作。” 你的用户下了单,点击刷新,结果现在有两个订单。或者两封邮件。或者两次扣费。

两个订单。两次扣费。一位沮丧的客户。

解决方案是Post/Redirect/Get(PRG)模式。优雅、简单,却几乎被遗忘。它作为重定向辅助工具被吸收进服务端框架,以至于新开发者从未有意识地使用过它。然后客户端JavaScript彻底封闭了这个循环——XHR回调、jQuery deferred、以及后续每个框架中的async/await。可变操作变成了自包含的过程。加载动画、乐观更新、加载指示器移除了“执行操作”与“显示结果”之间最后可见的缝隙。当没有页面可重放时,你不再思考POST重放问题。

这个模式并没有因为问题被解决而消失。它消失是因为技术栈让它变得不可见。

AI代理刚刚在全新的层面引入了一模一样的bug。而大多数基于A2AMCP或自定义代理管道的团队,即将第一次遇到它。


PRG到底是什么

没有PRG,Web表单的工作方式如下:

  1. 用户填写表单,点击提交
  2. 浏览器向服务器发送POST请求
  3. 服务器扣款、发送邮件、创建记录
  4. 服务器响应一个成功页面
  5. 用户点击刷新
  6. 浏览器重放该POST——一切重新执行

修复方案很简单:永远不要用页面来响应POST。相反:

  1. 用户提交 → 浏览器发送POST
  2. 服务器执行操作
  3. 服务器用302重定向响应一个结果URL
  4. 浏览器跟随重定向 → 发送GET请求
  5. 服务器返回结果页面
  6. 用户点击刷新 → 浏览器重放GET,这是无害的

危险的POST现在通过刷新无法到达。重定向是“执行操作”与“显示操作结果”之间的门。

三个因素让它生效。POST精确执行一次——重定向立即将用户带离POST环境。重定向携带一个稳定ID,如订单号或交易ID,作为结果的锚点。而GET是幂等的——点击一百次返回同一页面,不会产生任何新操作。

HTTP/1.1甚至为此定义了一个状态码——303 See Other,意思是“使用GET检索此请求的响应”。大多数人在早期使用302,因为早期浏览器对303支持不一致,但这一意图早在1999年就写入了规范。REST在次年补充了理论:GET是安全且幂等的,POST两者都不是。一旦理解了这些术语,修复方案就显而易见了。规范、状态码和理论在代理框架出现之前早已存在。

Web流程


代理有完全相同的bug

一个典型的代理循环:

  1. 用户要求代理下单
  2. 代理通过MCP或A2A调用create_order
  3. 响应到达前网络中断
  4. 代理不知道操作是否成功
  5. 代理重试create_order
  6. 两个订单。两次扣费。一位愤怒的客户。

这不是理论上的极端情况。每当工具调用中途发生网络超时、长时间任务期间容器重启、速率限制触发导致代理退避再重试,或LLM(大语言模型)重新采样并重新运行已调用的工具时,生产环境中就会出现这种情况。

代理,就像之前的浏览器一样,不知道它上一个操作是否成功。于是它再次尝试。

代理流程


映射关系

修复方案是一样的。名称变了,层次变了,但问题没变。

PRG-代理映射

PRG 的边界:代理的挑战升级

PRG(Post-Redirect-Get)只保护了入口。从用户视角看,Web 表单是原子的——要么提交成功,要么失败。但一个智能体(agent)任务可能运行二十分钟,调用十个不同工具,并在中途崩溃。此时入口处的幂等键(idempotency key)毫无用处。你需要的是检查点(checkpointing)——在每一步存储状态,这样重启时就能从中断处继续,而非从头开始。

可以将其视为在每一步递归应用 PRG:每个步骤拥有自己的幂等键,每个步骤的结果在执行后立即存储。重启时,会重新读取已完成步骤的结果,而非重新执行。这正是 Temporal 的持久执行模型 的机械原理:它在重启时重放事件历史,但跳过任何已记录的步骤,从而避免副作用执行两次。一旦每个步骤被提交,整个执行过程就变成了一系列安全的 GET 请求。

对于持久化智能体,完整的图景如下:

  1. 任务入口的幂等键 —— 防止重复创建任务
  2. 步骤级检查点 —— 防止任务中途重新执行已完成步骤
  3. 每个变更性工具调用上的幂等键 —— 最内层的保护层

如果只能实现一个,那就做第三个。很多生产环境的痛点都可以避免,只要每个写入外部状态的工具都能安全地调用两次。


为什么这一点总被忽略

服务器端框架最先吸收了 PRG——重定向-后-POST 成为默认路径,没人需要思考。接着客户端 JavaScript 让它显得无关紧要。无论是 XHR 回调、jQuery deferred,还是你偏好的任何框架中的 async/await,每个突变都变成了自包含的操作,从不触及浏览器的导航历史。当没有页面可以重放时,你就不会再考虑 POST 重放。

智能体框架尚未吸收这一点。MCP 没有内置的幂等键概念,A2A 的任务生命周期也不强制步骤级检查点。这些框架还很年轻,模式仍在生产环境中被发现——有时是痛苦地——通常是在用户被重复收费、邮件被发送三次、或数据库被无法解释的重复记录填满之后。


这个模式不断被重新发现

有趣的是,没有人计划过这些会汇聚在一起。

Web 开发者在 2000 年代初期遇到了 PRG,因为浏览器强迫了这个问题——表单重放是一个用户可见的 bug,除了重构请求流程外没有变通方法。

Temporal 是从分布式系统的角度切入的。它们的持久执行模型在重启时重放工作流历史,但跳过已记录的步骤。框架不同,但底层保证是一样的:已经发生的副作用不会再次发生。

而 2026 年的一篇论文——Agent-First Tool APIs——来自在实际生产 SaaS 系统运行 85 个工具的工程师,从代理可靠性方向得出了相同结论。他们在每个工具的描述符中增加了 idempotency_key_fields 作为声明的字段——不是推荐,而是契约的必需部分——这样密钥如何派生的问题就在设计时得到了回答,而不是在事故期间。

三个不同的社区,解决不同的问题,却得出了相同的结构答案。这种模式通常值得关注。


在发布之前

对于任何修改外部状态的智能体工具:

重定向就是幂等键。GET 就是存储的结果。相同的模式,不同的层次。

查看原文