Postel 法则在工具边界是一种负担
1980 年,Jon Postel 在 TCP 规范中写下了一句话,这成为了互联网的奠基原则:“对自己发送的内容要保守,对接收的内容要宽容。”四十年来,工程师们将其应用到各个角落——容忍尾随逗号的解析器、将 "10" 强制转换为 10 的 API、静默修复错误标记的 HTML 渲染器。可以说,万维网之所以存在,是因为浏览器宽容了所有人的错误。
然而,当调用者不再是人类时,这条建议就反转了。当一个 AI Agent 调用你的工具并传入字符串类型的数字、嵌套错误的 JSON 对象,或者一个 几乎 正确的枚举值时,那个原本能节省人类开发人员 20 分钟调试时间的宽容解析器,对 Agent 来说却造成了更糟糕的后果:它确认了这个草率的调用是正确的。Agent 在循环中唯一的训练信号就是你的工具返回的反馈。接受垃圾数据,你就在教导模型——就在此时此刻,在这个会话片段中——垃圾数据是行得通的。
IETF 本身已经收回了 Postel 的主张。RFC 9413《维护稳健的协议》(Maintaining Robust Protocols)指出,健壮性原则(Robustness Principle)随着时间的推移实际上会损害协议生态系统:每一个被 容忍的偏差都会成为事实上的规范,各个实现会围绕着彼此的 Bug 僵化,而你为了进化所保留的灵活性会被累积的放任所消耗。这种批评在网络协议中花了数十年才显现出来。但在 Agent 循环的工具边界上,同样的衰退在几分钟内就会发生——因为“对等实现”(peer implementation)是一个会根据你发送给它的每一条响应来更新其行为的模型。
宽容是你无意中发送的负面反馈
想想当模型发出一个“差一点就成功”的工具调用时到底发生了什么。假设你的工具期望接收 {"amount": 1050, "currency": "USD"},而模型发送了 {"amount": "1,050.00", "currency": "usd"}。你有两种选择。
严格路径拒绝该调用:amount 必须是整型的分(收到的是字符串 "1,050.00");currency 必须是大写的 ISO 4217(收到的是 "usd")。模型读取这条信息,纠正自己,而且——关键在于——纠正后的格式现在存在于该会话片段后续每一个调用的上下文中。一次失败的往返为你换来了该会话剩余部分的收敛。
宽容路径静默地去除逗号、解析浮点数、乘以 100、将货币转换为大写,并返回成功。模型通过那个畸形的调用完全得到了它想要的结果,因此畸形的调用反而得到了强化。循环中的每一个后续调用都会携带同样的草率,因为没有任何东西告诉模型这样做是不对的。而你的强制转换逻辑现在成了“关键承载”:哪天它遇到一个正 在处理欧洲发票的模型发来的 "1.050,00" 时,它会静默地猜错,而代价是之前的 100 倍。
这就是核心的反转。对于人类调用者,验证错误是摩擦,而自动转换是一种恩惠。对于模型调用者,验证错误是 循环内的训练信号,而自动转换则是关于契约实际位置的谎言。事实证明,当 Agent 获得可操作的错误信息时,它们能够自我修正——整个现代 Agent 循环设计的实践都依赖于此。像 LangChain 这样的框架围绕这一机制构建了重试解析器:将失败的输出连同错误信息反馈给模型,第二次尝试的成功率就会飙升。而静默转换让你完全退出了这种机制。
草率会累积,然后蔓延
单个被强制转换的参数听起来无害。问题在于 Agent 循环会迭代,在人类工作流中只是一次性烦恼的错误,在 Agent 集群中会变成分布。
考虑债务是如何积累的:
- 在会话片段内:模型成功但草率的调用作为示例存在于其自身的上下文中。模型会模仿自己最近的输出;一个被接受的“差一点”会招致另外十个。
- 在你的 Schema 之间:一旦模型了解到你的解析器会宽容某个字段的类型错误,它就没有理由相信任何字段是严格的。对
amount的放任会侵蚀对account_id的规范。 - 跨工具:Agent 会泛化。一个了解到“这个平台会进行自动转换”的集群,在调用平台上的每个工具时都会变得更加草率,包括那些 严格 的工具——而这些工 具现在会以模型预料之外的方式失败。
- 进入你的数据:最坏的情况不是调用失败,而是错误的调用成功了。经典的单位 Bug——在存储分的地方存入了元——正是这种宽容边界会放过的错误,因为这两个数量级孤立来看都显得合理。
这种结局有一个名字:海勒姆定律(Hyrum's Law)。随着调用者足够多,你系统的每一个可观察行为都会变成一种依赖,无论你承诺了什么。当调用者是每小时进行数千次调用的模型时,“足够多的调用者”瞬间就会到来。你对错误嵌套 JSON 的偶然容忍,现在成了一种 API 承诺,并以机器速度运行,你永远无法在不破坏那些你从未听说过的集群的情况下将其移除。
现实世界的基础设施 Bug 也呈现出同样的形态。LiteLLM 曾遇到过一个问题,模型发出的畸形工具调用参数被静默丢弃而不是显现出来——Agent 永远看不到失败,因此无法纠正,而损坏会级联到后续请求中,直到整个对话失败。边界上的沉默并不能遏制错误。它是在洗白错误。
- https://en.wikipedia.org/wiki/Robustness_principle
- https://datatracker.ietf.org/doc/html/rfc9413
- https://www.anthropic.com/engineering/writing-tools-for-agents
- https://modelcontextprotocol.io/specification/2025-11-25/server/tools
- https://thenewstack.io/15-best-practices-for-building-mcp-servers-in-production/
- https://nordicapis.com/meet-hyrum-and-postel/
- https://ajv.js.org/coercion.html
- https://github.com/BerriAI/litellm/issues/18667
