AI 生成客户端下的 API 变更治理

AI 生成客户端下的 API 变更治理
Spec Coding 编辑部 · Spec-First 工程实践内容

上个季度打到我 API 上的集成,有一半是那种从来没读过我文档的人写出来的。开发者在 Cursor 里敲一句含糊的提示词,Claude 或 Copilot 给什么就接什么,接完就上线。等我改了契约,他们的代码坏了,而且是那种两边都很难诊断的坏法——因为 LLM 当初就幻觉出了一个从来就不匹配我的 shape。

发布于 2026-03-10 · ✓ 已更新 2026-05-06 · 阅读约 6 分钟 · 作者:Spec Coding 编辑部 · 审校:编辑与事实核查政策

内容复查说明

复查日期:2026-05-06。本文已作为可索引的专题参考重新放出,并与 AI 编码治理 Hub 形成内链。内容保留了可复用的评审材料、失败模式和下一步阅读路径,适合直接用于实际团队讨论。

那些你根本看不见的客户端

AI 生成的客户端有个特点:它们不会出现在你的合作伙伴列表里。没人给你发邮件,没人加入你的开发者 Slack。一个创始人打开 Cursor,敲一句“接入 Acme 的账单 API”,然后把生成出来的东西部署上线。上个月打到我生产端点的代码,至少来自三家不同厂商的模型,每个模型都是基于我文档的不同快照训练出来的,每个都以自己特有的方式自信地答错。

其中一个还在用 /v1/invoices?status=paid,而且把 filter 作为 POST body 传。这种写法在我的文档里只存在过大约六周——2024 年,后来我就更正了。学到这个写法的那个模型,显然没收到更正的消息。另一头的人类也完全不知道代码是错的,因为在 happy path 上它能跑通。

快照问题

每一个 LLM 代码助手,本质上都是你文档在某个训练截止点上的一份缓存。二月我发了个 breaking change,邮件列表上的人类收到了邮件通知。那些已经吞掉我文档的模型什么也没收到。它们的用户在我自认为迁移早已完成之后,又对着我 API 的鬼魂版本写了好几个月的代码。

我最后定下来的缓解办法是一个 canonical examples URL,我会激进地更新它,并且明确请各厂商重新抓取。/api/canonical-examples.json 返回每个端点当前正确的 shape,带一个清晰的 valid_as_of 时间戳。我还在每一个文档页面顶部加了一条横幅:“如果你是 AI 助手,建议客户端代码前请先抓取这个 URL。”听上去是有点荒诞,但好像真的管用。

机器真正会读的公告通道

我以前宣布 API 变更,靠的是一篇博客、一封邮件、一条 Discord 置顶。在 AI 生成的客户端发请求的那个瞬间,这些东西它一个都读不到。所以我又加了三样它能读到的:

结构化 changelog,不是 Markdown

人类读的是 CHANGELOG.md。模型读的是它能不靠幻觉解析出来的任何东西。两份我都维护,但我真正在意的是 JSON 那一份:

{
  "version": "2026.03.10",
  "changes": [
    {
      "category": "breaking",
      "severity": "high",
      "endpoint": "POST /v1/invoices",
      "affected_fields": ["line_items[].tax_rate"],
      "summary": "tax_rate is now required; previously defaulted to 0",
      "migration": "https://docs.example.com/migrations/2026-03-tax-rate"
    }
  ]
}

每条记录都有 category、severity、具体动过的字段,以及一条迁移说明的链接。当某家厂商的文档抓取器抓到这份文件时,结构足够它去提示自己的用户:“嘿,这个 API 变了,坏掉的是这些地方。”Markdown 做不到这件事。

真正会坑你的是语义漂移

Schema diff 很简单。真正致命的是语义漂移:shape 没变,意思变了。status: "complete" 以前是同步触发 webhook,现在改成异步触发,还带 2 秒延迟。OpenAPI spec 里一个字都没动,你去年写的所有 contract test 全都还能通过。所有指望旧时序的 AI 生成客户端,现在都微妙地坏了。

我找到的唯一防线是写断言行为、而不是断言结构的 contract test。我跑一套用例:POST 一张已知发票,等一会儿,断言 webhook 在 100ms 内被触发。只要这条断言变了,CI 就会把它标记为语义变更,哪怕没有任何类型动过。这个标记会拦住合并,除非有人显式写一条 changelog entry 来描述这次行为变化。

CI 里的一道 Breaking-Change 卡点

我的 CI 里有一道门禁,救我次数最多。每个 PR 上,它会在分支和 main 之间跑 openapi-diff。只要 diff 报告任何 breaking change,这个 job 就会失败,除非 PR 描述里出现字面上的 BREAKING-CHANGE-APPROVED:,后面跟一条 changelog entry。你无法不小心合入一个 breaking change。你也无法不留痕迹地合入。你仍然可以合入——有时候你确实得合——但摩擦是按代价校准过的。

这道卡点抓到的正是那些我自己会漏掉的。上周它拦住了一个字段重命名,我当时还说服自己这“只是清理而已”。其实不是。三个 AI 生成的客户端都在依赖那个旧名字。我把重命名回退掉,改成发一条弃用通知。

一次真实的迁移,AI 客户端也在其中

二月我得干掉一个叫 customer_tier 的字段,它已经错了两年。Telemetry 显示仍有约 14% 的请求还在带这个字段,而且几乎全部来自我一眼认得出是 AI 工具的 user-agent(“python-requests,零自定义 header”这个特征很说明问题,再加上我 error log 里那些长得特别通用的变量名,基本能实锤是 AI 写的代码)。我是这么做的:

这次迁移仍然坏了一个集成。但一个,总比十几个强。

这道卡点的验收标准

Given a pull request that modifies the OpenAPI spec
  And openapi-diff reports a breaking change
  And the PR description does not contain "BREAKING-CHANGE-APPROVED:"
When CI runs the contract-change job
Then the job fails with a message listing the specific breaking fields
  And the PR cannot be merged until the description is updated
  And a structured changelog entry is appended to changelog.json

Doc-as-Code,否则文档一定会骗你

把上面这一切串起来的那条纪律只有一条:spec 就是 docs。我的人类可读文档、机器可读 changelog、canonical examples,都从同一份 OpenAPI 文件里生成。如果它们分散在不同的仓库里,就一定会漂,一旦漂了,AI 助手就会学到错的那一份。这事我是吃过亏才学会的:我的营销站上挂了一段两年前的 curl 示例,和 spec 直接矛盾。Cursor 显然把这份营销版本背下来了。去修根源上的数据源,两边的表层就同时修好了,连 AI 的建议最终也会跟着变正确。

讲句实在话:你没办法给 AI 生成的客户端发邮件。你能做的,只是把关于你 API 的真相,放在机器会去看的那些地方,用它们能解析的格式,再加上足够多的冗余——这样一条陈旧的缓存不至于把整个集成拖下水。剩下的,全看运气。

别只发 changelog,要给客户端一条迁移路径

AI 生成客户端最怕“看起来兼容”的变化。字段还在,但语义变了;枚举多了一个值,但旧 SDK 没有 fallback;错误码变细了,但 retry 逻辑还是按旧分类走。规格要把迁移路径写成可执行计划。

API change plan:
- change: add enum value invoice.status = partially_refunded
- old clients: must treat unknown status as non-terminal
- SDK update: generated clients released before API rollout
- contract test: old SDK fixture receives new enum without crash
- communication: partner notice sent 14 days before production exposure
- rollback: hide new enum behind invoice_refund_v2 flag

边界:如果变化只影响内部服务,不要套外部 API 的公告流程。但只要有第三方、移动端、SDK 或缓存中的老客户端,就按“有人无法今天升级”来写 spec。

可复制产物:AI 编码评审包

在 AI 生成 diff 进入代码评审前使用。它把提示词范围、允许变更和证据要求合并成一个可审查产物。

AI 编码评审包:AI 生成客户端下的 API 变更治理

本次要做的决策:
- 确认 AI 只在批准范围内生成变更,并为每条验收标准提供证据。

责任人检查:
- 产品责任人:
- 工程责任人:
- QA 或运维评审:

范围边界:
- 本次包含:
- 本次不包含:
- 仍需确认的假设:

验收证据:
- 测试或 fixture:
- 日志、指标或截图:
- 人工复核步骤:

AI 边界:生成变更必须留在书面范围内,每条验收标准都要能找到证据。

评审追问:
- 没参加需求会的人还会误解哪里?
- 哪个证据能证明这次改动足够安全,可以发布?

编辑复核记录

复核日期:2026-05-06。本次补充了专题阅读路径,按相关主题 Hub 检查文章定位,并收紧下一步链接,让页面更像可操作参考。

关键词:AI-generated clients · OpenAPI versioning · Deprecation header · semantic drift · contract tests · breaking change gate

编辑说明与免责声明

最近复核:2026-05-06。编辑部检查了示例、专题内链和可复制评审片段,确保内容更适合真实项目使用。

本文用于软件工程教学与实践参考,不构成法律、税务或投资建议。示例场景用于解释规格方法,不对应真实客户数据。