跳到主要内容

AI 驱动端点的 API 设计:为不可预测性建立版本控制

· 阅读需 10 分钟
Tian Pan
Software Engineer

你的 /v1/summarize 端点在 18 个月里运行得非常完美。然后你升级了底层模型。输出格式没变,JSON schema 完全一致。但你的下游消费者开始提交 bug:摘要“太随意了”,要点“详细得诡异”,边界情况下的拒绝响应“变得不同”。从传统意义上讲,一切都没坏;但在 AI 的语境下,一切都坏了。

这是 REST 和 GraphQL 从未被设计用来解决的版本控制问题。传统的 API 合约假设确定性:相同的输入总是产生相同的输出。而 AI 端点的合约是概率性的——它包括语气、推理风格、输出长度分布以及拒绝阈值,当你更换或更新底层模型时,所有这些都可能发生漂移。对于以数据库为支撑的 API 有效的技术,对于以 AI 为支撑的 API 是必要但不充分的。

“破坏性变更”的扩展定义

当你更改传统的 API 时,破坏性变更很容易列举:删除字段、更改类型、重命名端点、修改身份验证。对于 AI 端点,这个列表更长且更模糊。

一个 AI 端点的破坏性变更包括:

  • Schema 变更:结构化输出中删除或重命名的字段(与传统 API 相同)
  • 格式漂移 (Format drift):模型格式化散文方式的变化——要点样式、句子长度、markdown 与纯文本的对比
  • 语气和语域转变 (Tone and register shifts):如果模型更新让你原本的“专业助手”听起来很随意,即使数据完全一致,也会破坏用户的预期
  • 拒绝边界变化 (Refusal boundary changes):如果你的模型之前对边缘情况的提示词会有响应,但现在拒绝了(或反之亦然),依赖该行为的调用方就会经历破坏性变更
  • 推理模式变化 (Reasoning pattern changes):对于解析或依据思维链 (chain-of-thought) 输出采取行动的应用,当模型的推理风格改变时,可能会发生静默失败

最后三个类别不会出现在你的 OpenAPI 规范中。它们也不可能出现——它们是分布属性,而不是结构属性。这就是为什么 AI API 的版本控制从根本上更难。

快照固定:务实的解决方案

主要的 AI 提供商都汇集到了同一个务实的解决方案上:在模型标识符中暴露显式的快照版本,并允许调用方固定到这些版本。

OpenAI 的模型标识符(如 gpt-4o-2024-08-06)直接编码了快照日期。固定到该标识符,你就能获得冻结的行为。使用别名 gpt-4o,你会得到最新版本,其行为可能与你上季度测试的情况不同。Anthropic 也遵循同样的模式,使用类似 claude-3-5-sonnet-20240620 的标识符——日期后缀就是稳定性的保证。

这种方法完全绕过了 REST 的版本控制问题。你不再需要 /v1/complete 对比 /v2/complete,而是 model: "gpt-4o-2024-08-06" 对比 model: "gpt-4o-2025-03-15"。API 表面保持不变;只有当你显式选择进入新快照时,行为合约才会改变。

权衡在于:快照会被废弃。OpenAI 为正式发布的模型提供至少 12 个月的支持期,然后为现有客户提供额外的 6 个月。你需要一套受控的快照迁移流程,而不是“一劳永逸”的固定策略。

结构化输出是你的合约层

快照固定处理了模型层面的稳定性。但即使在单个快照内,你仍然需要强制执行调用方在输出层所依赖的内容。

结构化输出——即你向模型传递 JSON Schema 并且模型保证符合该 schema——是 AI 领域最接近类型化 API 合约的东西。OpenAI 的 response_format: {type: "json_schema", json_schema: {...}} 以及其他提供商的类似机制限制了模型可以返回的内容。如果你的下游代码期望得到 {summary: string, confidence: number, tags: string[]},请声明该 schema 并强制执行。

这有两个好处。首先,它防止了格式漂移在快照内破坏调用方,因为模型无法偏离声明的 schema。其次,它为你提供了具体的版本化对象:当输出 schema 改变时,这就是具有清晰语义的传统破坏性变更。Schema 版本 2 增加了一个 sentiment 字段;Schema 版本 1 的调用方则继续得到他们预期的内容。

在实践中行之有效的模式:

  • 在基础设施层面固定快照
  • 在输出层面强制执行 JSON schema
  • 将 schema 变更视为传统的 API 版本
  • 将快照升级视为具有独立迁移路径的单独生命周期

行为包络:测试你无法规范的内容

结构化输出涵盖了结构。但它们不涵盖行为——摘要是否准确、语气是否合适、拒绝率是否在可接受的范围内。

对于行为属性,团队正在采用一种有时被称为“行为包络 (behavior envelope)”的方法:与其测试特定输出是否符合特定预期,不如测试输出是否落在可接受的分布范围内。

具体而言,这意味着定义边界:

  • 评估集上的准确率必须保持在 85% 以上
  • 平均响应长度必须保持在 80 到 400 个 token 之间
  • 针对对抗性输入的毒性得分必须保持在 0.05 以下
  • 歧义提示词的拒绝率必须保持在基准线的 ±3% 以内

这些不是对单个输出的通过/失败断言——它们是对聚合行为的统计关卡。在发布快照升级之前,你会针对代表性样本(你的黄金测试集加上最近的生产流量)运行这些测试。

实际的意义在于,在制定版本控制策略之前,你需要一套评估工具链 (eval harness)。如果没有行为基准,你就无法知道模型升级是否可以安全发布。这就是为什么早期投资评估基础设施的团队,在管理模型升级时比那些将模型视为黑盒并寄希望于运气好的团队要顺利得多。

影子模式与金丝雀发布

即使有了快照锁定、结构化输出和行为边界,你仍然无法在测试环境中完全模拟生产流量。用户输入比你的评估集更杂乱,真实流量中的分布偏移可能会暴露你未预料到的行为。

被证明可靠的部署模式是两阶段法:

第一阶段:影子测试。 将 100% 的生产流量同时路由到两个模型。在线模型负责响应;候选模型处理相同的请求,但其输出仅记录日志,绝不返回给用户。在任何人看到新模型的输出之前,你需要通过几天的真实流量收集响应分布、延迟画像、成本数据以及任何错误情况。

第二阶段:金丝雀发布。 一旦影子数据看起来没问题,逐步迁移流量:1% 到新模型,然后是 5%,再到 20%,如果关键指标退化超过定义的阈值,则触发自动回滚。需要监控的阈值包括:延迟分位数、实时样本的评估得分、单次请求成本和错误率。

影子测试对于拒绝行为尤为重要。拒绝在正常流量中很少见,因此你的评估集可能无法很好地捕捉到它们。通过在生产流量中运行一周的影子测试,比任何静态测试集都能更可靠地发现拒绝模式的变化。

剩余层面的版本化

一旦你拥有了快照锁定、结构化输出架构、行为评估以及影子/金丝雀部署流程,剩下的版本化层面看起来更像是传统的 API 版本化:

系统提示词版本化:你的系统提示词是行为合约的一部分。明确对其进行版本化,将其存储在配置管理系统中,并像对待代码更改一样严谨对待其变更。系统提示词从 你是一个专业的助手 变为 你是一个乐于助人且友好的助手 是一种行为变更,理应通过你的评估和金丝雀流程。

响应格式版本化:在结构化输出中包含 format_version 字段。当你需要添加新字段或重新组织响应结构时,增加格式版本。调用方可以在过渡期间使用该字段优雅地处理多个版本。

弃用信号:当底层快照接近生命周期终点时,在响应头或 Body 元数据中添加 model_deprecated_at 字段。调用方可以由此显示警告,而无需自己跟踪弃用计划。

实践中的样子

一个版本化良好的 AI 端点具有多个层级:

  • 模型标识符被锁定(claude-3-5-sonnet-20240620),为你提供行为稳定性
  • 声明并强制执行响应架构,为你提供结构稳定性
  • 系统提示词在配置中进行版本化,而非硬编码
  • 在任何模型或提示词变更发布前运行行为评估套件
  • 影子测试和金丝雀发布是发布过程中的标准步骤
  • 响应元数据向下游调用方暴露格式版本和模型弃用信号

这些都不是什么玄学。与盲目调用模型 API 并直接返回结果相比,这确实需要额外的工作。但这些额外工作是可回收的 —— 当模型升级微妙地改变了生产环境中的行为,而你能在调用方发现之前捕捉到它时,这一切就有了回报。

底层原则

核心见解是,AI 端点有两个截然不同的合约:结构合约(存在哪些字段,它们是什么类型)和行为合约(输出意味着什么,它们有多一致,它们在哪里失败)。传统的 API 版本化能很好地处理结构合约,但没有针对行为合约的工具。

解决方案不是假装行为合约不存在 —— 而是通过评估使其显性化,通过分阶段发布来强制执行,并通过快照标识符和版本元数据进行沟通。那些在静默中破坏调用方的端点,是那些将模型升级视为实现细节并将其隐藏在稳定别名背后的端点。而那些保持值得信赖的端点,是向调用方暴露模型版本化,并在行为改变时提供结构化迁移路径的端点。

你的调用方不需要每次都有完全相同的输出。他们需要知道他们依赖的输出何时即将发生变化,并且他们需要时间来适应。这才是值得构建的版本化保证。

References:Let's stay in touch and Follow me for more thoughts and updates