内部 API 依赖于一种默契而存在:没人会写下契约,因为每个人都已经心知肚明。那些碰巧存在的字段、调用者在暗地里解析的报错、返回空列表而非 404 的 200 响应 —— 这些都是关键的承重行为。而维系这些行为的基础是,你可以叫出每个调用者的名字,并在做出任何更改之前通过 Slack 联系他们。这种安排一直有效,直到它失效的那天。
当你将一个智能体(Agent)连接到该 API 的那天起,这种默契就失效了。这并非因为智能体怀有恶意或粗心大意,而是因为智能体是一个你无法触及的调用者。它没有 Slack 账号。它没有阅读你的迁移说明。它依赖于从示例载荷或 Schema 快照中吸收的响应形态,并且在你早已更新版本后,它仍会长期依赖这些形态。
一个令人不安的事实是,“内部”从未是 API 本身的属性。它其实是调用者列表的属性。将列表缩减到你认识的人,API 就是内部的;一旦增加一个你无法协调的参与者,API 就是公共的 —— 这意味着你需要承担“公共”一词所暗示的所有严谨规范,尽管你并没有构建任何本应具备的基础设施。
海勒姆定律一直都是正确的 —— 只是智能体让它变得显而易见了
海勒姆定律(Hyrum's Law)指出,只要有足够多的用户,系统的每种可观测行为都会有人依赖,无论契约中承诺了什么。大多数内部团队都在悄悄违反这一定律并得以蒙混过关,因为他们的用户数量很少且触手可及。你更改了一个字段名,构建失败了,下午就会有人修复它。依赖关系是真实的,但反馈循环足够快,以至于人们从未觉得这是一种契约。
智能体调用者同时从两个方面打破了这一循环。首先,它扩大了被依赖的可观测行为的范围。人类集成者阅读你的文档并使用文档中描述的三个字段。而智能体则会吸收整个响应,并可能根据其中任何内容做出下一个决策 —— 数组的顺序、可为空字段的存在与否,或是状态字符串的精确措辞。那些你从未考虑作为接口一部分的行为,现在都成了接口的一部分。
其次,它消除了快速反馈。当人类集成出现故障时,会抛出异常,测试变红,或者触发警报。而当智能体集成出现故障时,智能体通常不会崩溃。它会做一些更糟糕的事情:它会自我调整。它看到一个不再符合预期的响应,然后临场发挥 —— 尝试使用不同的参数重试、选择不同的工具,或者幻觉出一个看似合理的路径。你发布的 API 变更不会产生错误。它会导致该智能体下游所有内容的质量出现缓慢且无声的下降,而且没有任何堆栈跟踪(stack trace)能追溯到你的那次提交。
这就是核心危险所在。你不仅仅是增加了一个新的调用者。你增加了一个会将你的破坏性变更从“大声报错”转变为“悄无声息的失败”的调用者。
无法触及的调用者改变了每一次变更的经济账
想想在内部 API 上,协调实际上为你带来了什么。它让你能以低廉的成本进行破坏性变更。你可以重命名字段、收紧类型或删除端点,因为这样做成本只是发个消息和占用别人一个下午的时间。非正式契约之所以可行,正是因为重新协商的成本很低。
失去了协调的能力,这种经济计算就会反转。每项破坏性变更现在都带有无穷且无法诊断的成本,因为你无法列举谁依赖这些行为,也无法提前告知他们。智能体对你响应形态的依赖是在它上次看到你的 Schema 时确定的 —— 可能是几周前,也可能是在一个缓存的工具定义中,而智能体自那以后就没刷新过。LLM 客户端会激进地缓存工具和 Schema 定义;即使在你的服务器更新之后,智能体也可能继续发送符合旧契约的参数,直到有某种机制强制其重新获取。
这就是为什么智能体调用者会对一个从未获得“公共”地位的服务强加公共 API 的严谨规范。这并非因为有人决定将该 API 公开,而是因为原本支撑你跳过这些规范的“低成本协调”假设已经不成立了。你现在需要的规范正是公共 API 一直以来所需的:
- 显式契约。 智能体依赖的行为必须是你写下来的行为,而不是碰巧存在的行为。如果不在 Schema 中,就不在承诺范围内 —— 并且你需要一种方法来检测智能体何时依赖了未承诺的内容。
- 版本控制。 破坏性变更必须是一个新版本,而不是对现有版本的变动。旧的调用者在迁移之前会一直使用旧的形态。
- 弃用窗口。 “我们改了它”不是智能体能响应的事件。“这已弃用,这是替代方案,它将在 N 个月后消失”才是一个足够长的窗口,足以让你无法控制的迁移得以完成。
- 稳定的错误语义。 智能体会根据你返回的错误来推理失败原因。如果你的 429 有时表现为 500,或者你的“未找到”有时表现为空的 200,智能体就无法在其上构建可靠的策略。
这些都不是新鲜事。这是标准的公共 API 策略。唯一的新鲜事是,你现在必须将这些策略应用到一个 README 中仍然写着“内部”的服务上。
工具 Schema 即契约 —— 请像对待契约一样对待它
对于智能体 (agent) 来说,API 契约不是你的 OpenAPI 文档,也不是你的团队默契。它是提供给智能体的工具 Schema:包括名称、描述、参数类型和枚举值,这些内容告诉模型它可以调用什么以及如何调用。该 Schema 是智能体进行推理的全部界面。这意味着直接引出了以下几点。
对工具描述的修改就是对契约的修改。描述不是智能体“可能”阅读的文档 —— 它是智能体执行的指令。将“返回用户的活跃订单”改写为“返回用户的订单”可能会在无形中改变智能体的行为,因为它改变了模型对工具功能的理解。这种类型的 Schema 漂移被准确地描述为新的“依赖地狱”:工具依然通过验证,依然返回 200,但在智能体手中却在悄无声息地做着错误的事情。
因此,请像对任何 API 契约进行版本控制一样,对工具 Schema 进行版本控制。以下是一些已经出现的实用模式:
- 默认增量 (Additive by default)。 添加可选字段或新工具很少会破坏现有的智能体。而删除工具、重命名参数、缩小类型范围或添加必填字段则总是会带来破坏。请让每一次变更都倾向于增量式。
- 过渡期间使用版本化的工具名称。 在引入
search_orders_v2 的同时保留 search_orders,在旧版本的描述中将其标记为弃用,并在迁移窗口结束后再将其删除 —— 这正是公共 API 处理版本化端点的模式。
- 对你的界面进行哈希处理 (Hash your surface area)。 对工具名称、描述和参数 Schema 计算校验和,并在发生变化时发出警报。描述的无声改写就是契约的变更;哈希值能让这种变更显形。
- 将黄金提示词 (Golden prompts) 作为契约测试。 保留一组具有代表性的提示词,并断言 Schema 的变更不会改变模型生成的工具调用。这是智能体时代等同于契约测试的方法,它能捕捉到类型检查无法发现的语义破坏。
重点不在于形式主义。而在于工具 Schema 现在是一个拥有“无协作消费者” (uncoordinated consumer) 的生产环境接口,而版本控制和契约测试正是为了应对无协作消费者的情况而发明的。
重试、扇出和幂等性现在是核心支撑
人类客户端总体上是礼貌的。他们调用一次端点,处理错误,然后继续。他们不会因为一个暂时的 500 错误感到焦虑,就在循环中调用同一个写端点四十次。但智能体会。
智能体会将工具调用视为计划中的一个步骤,当某一步失败时,它会做出局部合理的反应:再试一次。反思和重新规划循环会放大这一点。一个并发扇出二十个工具调用且其中几个触发限流的智能体通常会重试 —— 而且往往是重试全部二十个,而不仅仅是失败的那些,因为它会重新运行整个步骤。这是一种从单个客户端内部生成的“重试风暴”,而你那为了支撑少量已知服务调用量而设计的内部 API,从未针对这种情况进行过压力测试。
有两个属性不再是“锦上添花”,而是成为了核心支撑:
幂等性 (Idempotency)。 如果智能体可以重试写操作,那么该写操作必须是支持安全重试的。幂等键允许服务器识别出“这是同一个操作,返回原始结果,不要再次执行”,这是重试订单与重复订单之间的本质区别。在没有这种保证的情况下让模型盲目地重新发布动作,会导致重复收费和重复记录。动作层必须强制执行幂等性,而不是信任智能体不会重复自身。
智能体可读的限流和背压 (Backpressure)。 一个不知疲倦的非人类调用者会触及你服务的上限。你的限流需要是真实的,并且你的 429 错误需要携带足够的信号 —— 比如 Retry-After 和清晰的状态 —— 以便构建良好的智能体套件可以带抖动 (jitter) 地退避,而不是持续轰炸。模糊的过载响应会给智能体传达错误的信号。
你设计内部 API 时,假设调用者是有限的、礼貌的且协同的。智能体则完全不是。它在耐心上近乎无限,在礼貌上无动于衷,且无法协同。你因为调用者表现良好而略过的复原力属性,现在成了支撑系统的关键。
必须有人负责边界
这里最深层的问题不是技术问题。而是组织问题。内部 API 通常权责模糊 —— 一个团队维护服务,但没有人负责“契约”,因为契约从未作为一个独立的产物存在。从智能体成为调用者的那天起,这个边界就需要一个负责人,因为必须有人决定承诺什么、版本化什么、何时弃用以及错误语义是什么。
如果没人负责面向智能体的边界,默认的糟糕结果就会出现:服务团队继续将 API 视为内部的、可变的,智能体团队继续吸收他们最后观察到的任何形态,而这两种认知之间的差距将通过没人能追踪到的无声质量退化来偿付。失败之所以是不可见的,恰恰是因为边界无人负责。
所以,明确负责人。明确决定允许智能体调用哪些 API,并将这组 API 视为一个已发布的界面,拥有名称、版本、变更日志,并由专人负责不破坏它。这并不意味着要将其暴露给公共互联网。这意味着要接受一个事实:当你的调用者列表中包含一个你无法直接沟通的参与者时,“内部”一词就不再成立了 —— 而承认这一点成本最低的时机是在第一次无声退化发生之前,而不是之后。
智能体并没有破坏你的 API。它揭示了你一直以来认为隐含的契约其实是一项负债,而唯一保护你的是一个短到能存在你脑子里的调用者列表。那个列表刚刚以一种你无法撤销的方式变长了。请构建出你一直欠它的那份契约。
会员专享
余下内容仅对会员开放。
会员可读完整内容 —— 每一个公开发布的观点背后,那些框架、决策与推理的全貌。
- —完整文章,包含未公开存档的部分
- —可落地的工作框架,附带权衡与决策依据
- —新文章抢先看,先于公开发布
随时取消 · 一次订阅,畅读全部