跳到主要内容

LLM 输出即 API 契约:为下游消费者版本化结构化响应

· 阅读需 11 分钟
Tian Pan
Software Engineer

2023 年,斯坦福大学和加州大学伯克利分校的研究团队做了一项受控实验:他们在 3 月和 6 月分别向 GPT-4 提交了完全相同的提示词,任务非常基础——判断一个数字是否为质数。3 月时,GPT-4 的准确率为 84%。到了 6 月,使用完全相同的 API 端点和完全相同的模型别名,准确率已跌至 51%。没有变更日志,没有通知,没有传统意义上的破坏性变更。

这项实验清晰地揭示了一个在多服务架构中部署 LLM 的团队迟早都会遇到的问题:模型别名不是稳定的契约。当你的下游支付处理器、推荐引擎或合规系统依赖 LLM 生成的结构化 JSON 时,你就建立了一个隐式的 API 契约——而隐式契约会悄无声息地崩溃。

传统的 API 版本化方案在这里并不完全适用。REST 端点不会自发改变响应结构,但 LLM 会。模型提供商可以在不触发任何 API 兼容性保证的情况下更新权重、调整安全过滤器或改变采样行为。JSON 依然能正常解析,模式验证依然通过,日志看起来一切正常。然而三周后,在某个下游服务中,一个被污染的值悄悄渗入财务报告或用户推荐结果。

本文的目的,是将这个问题作为一个真正的工程问题来对待——将已在服务 API 领域行之有效的契约测试和版本化规范,应用到 LLM 输出这个非确定性的世界中。


两种不同的故障模式:模式漂移与行为漂移

在防御 LLM 输出破坏之前,你需要区分两种表面相似但需要不同检测策略的故障模式。

模式漂移改变的是输出的结构。字段被重命名,必填键被删除,类型从字符串变为整数。在一个有完善监控的系统中,模式漂移应该能被立即检测到——Pydantic 或 Zod 验证器会抛出错误。危险在于,大多数团队以防御性的方式连接验证:他们在边界处验证,但不将验证失败视为阻断构建的事件。故障出现在错误日志中,被默默地重试,而事故复盘时才追溯到三周前的一次提示词修改。

行为漂移更难检测,因为它不会导致验证失败。结构完整,模式满足,但语义已经发生了偏移。置信度分数的分布发生变化,实体提取模型开始返回完整的句子而非名词短语,原本返回三个不同值的分类字段开始有 80% 的时间只返回其中一个。一切都能正常解析,下游服务照常处理,问题最终以产品质量下降的形式浮出水面,却无人能追溯到根本原因。

这两种故障模式在生产环境中都是真实存在的。对金融工作流的研究发现,更大的模型往往更不一致:一个 1200 亿参数的模型在零温度下的输出一致性仅为 12.5%,而较小的 70-80 亿参数模型在相同条件下能达到 100% 的一致性。规模和能力并不意味着可靠性。


为什么模型别名是一个陷阱

斯坦福的漂移研究并非孤例,它只是对从业者早已心知肚明的事情进行了量化测量:gpt-4 今天和明天代表的不是同一个东西。

提供商会不断更新模型权重,以修复安全问题、改善指令遵循或优化计算效率。这些更新持续发布,鲜少公告。同一个模型标识符可能产生不同的输出,因为"同一个模型"不过是一种方便的说辞——它只是一个指向提供商当前认为该模型家族最优版本的指针。

在多步骤流水线中,影响会叠加放大。当你更新链条中的一个提示词时,所有下游提示词都会收到不同的输入。它们本身没有改变,但由于输入分布发生了偏移,行为随之改变。这种"依赖提示词"的故障模式对于单独评估提示词的任何测试策略都是不可见的。

实际应对措施很简单:明确锁定模型版本。使用 gpt-4-0613 而非 gpt-4,使用 claude-3-opus-20240229 而非 claude-3-opus。制定正式的升级窗口并配备回归测试套件,而不是接受提供商别名指向的任何版本。当 OpenAI 停用某个锁定版本时,将其视为破坏性的依赖项升级——与应用主要库版本升级时相同的流程。


模式设计是契约的一部分

在对 LLM 输出进行版本化或测试之前,你必须为稳定性而设计它们。看似无关紧要的模式选择,对输出可靠性有着巨大影响。

字段重命名可能导致模型性能下降 90 个百分点。在一项受控实验中,将输出字段名从 final_choice 改为 answer,在相同的底层任务上将准确率从 4.5% 提升到了 95%。模型是在"answer"是该概念自然词汇的数据上训练的;重命名破坏了模式与模型隐式先验之间的对齐。

字段顺序同样重要。如果你在模式中将答案字段放在推理字段之前,模型会在完成推理之前就锁定答案——这是一种结构性提示,优化了看起来自信但实际上并不可靠的输出。将推理字段放在答案字段之前,能持续提高准确率,因为模型的思维链会塑造最终值。

命名选择、字段顺序和类型特异性不是装饰性决定。它们是契约的一部分,改变它们是破坏性变更,必须被如此对待。


对完整执行上下文进行版本化

传统语义化版本管理自然适用于 LLM 输出契约:

  • 主版本号在输出格式重构、字段重命名、类型变更,或任务定义变化足以破坏任何未更新下游消费者时递增。
  • 次版本号在向后兼容的添加时递增——新的可选字段、格式不变的质量提升。
  • 修订号在错误修复时递增——错别字更正、措辞微调、边界情况处理。

关键洞察是:版本必须覆盖整个执行上下文,而不仅仅是提示词文本。即使提示词完全相同,将模型标识符从 gpt-4-0613 更改为 gpt-4-turbo 也是主版本递增,因为行为漂移是必然的。调整采样温度至少是次版本递增。更改 RAG 系统中的检索配置是主版本递增。

具体而言,版本化的制品应将以下内容打包在一起:

  • 提示词文本(所有轮次,包括系统提示和用户提示)
  • 模型标识符(锁定的,非别名)
  • 温度和采样参数
  • 工具或函数模式定义
  • RAG 系统的检索配置

版本一旦创建,必须是不可变的。任何修改——无论多么微小——都会创建新版本。这与不可变 Docker 镜像标签的原则相同:保证六个月后部署 v1.2.3 与今天部署时产生相同的行为。

在每个 LLM 输出事件中嵌入 schema_versionprompt_version 字段。这使每个输出都成为可审计的记录,未来的调试可以追溯到确切的执行上下文。当合规审计询问为何在第三季度做出某个特定分类时,你有明确的答案。


LLM 输出契约的测试金字塔

LLM 输出的契约测试反映了标准测试金字塔,每一层捕获不同的故障模式。

确定性模式验证是基础。每个 LLM 输出在被使用之前都通过 Pydantic 模型或 Zod 模式验证。验证失败不会被默默重试——它们会作为构建失败或告警事件浮现。这能立即捕获模式漂移。Instructor 库(Python 生态系统中每月下载量超过 300 万次)通过自动重试逻辑实现了这一模式:当验证失败时,它会将错误信息包含在内重新提示模型,最多重试可配置的次数。

属性断言能捕获模式验证遗漏的行为漂移。这些是不变量检查:置信度分数必须在 0.0 到 1.0 之间,状态字段必须是声明枚举的成员,必填字段不得为空。基于属性的测试框架(Python 的 Hypothesis 或 TypeScript 的 fast-check)可以生成多样化的输入,大规模地对这些不变量进行压力测试。

黄金数据集回归是正式的契约测试。一组精心策划的 50-200 个输入/输出对,涵盖核心用例、边界情况和对抗性输入,在每次提示词变更、模型版本变更或依赖项更新时运行。这是本可以捕获质数准确率下降的测试——在每次模型升级前后运行基准测试,将超过阈值的回归视为阻断性失败。

LLM 即评判者评估添加了纯模式验证无法覆盖的语义质量评估。第二个模型评估输出是否满足真实但无法通过结构检查量化的要求——完整性、连贯性、任务对齐。这一层在 CI 预合并时运行,而非每次提交。

影子和金丝雀部署是生产层面的契约。将 5-10% 的流量路由到新的模型或提示词版本,在完全推出之前比较输出分布。影子测试在不向用户提供输出的情况下并行运行新版本——它记录比较结果,捕获真实流量上的回归,并在测试窗口期间使 API 成本翻倍。将这部分成本纳入模型升级规划的预算中。


服务边界的消费者驱动契约

当多个下游服务消费 LLM 输出时,协调问题会成倍增加。一个服务依赖 entity_id 字段,另一个依赖 classification.confidence 子字段,第三个依赖两者加上前两个忽略的 reasoning 字段。对一个消费者看似向后兼容的模式变更,对另一个来说是破坏性变更。

消费者驱动的契约测试——Pact 在微服务 API 领域普及的同一模式——可以直接解决这个问题。每个消费者服务声明它所依赖的模式元素。LLM 网关(或中间服务)在每次模型或提示词更新时运行所有消费者契约。如果任何消费者的契约失败,更新将被阻止。

将其应用于 LLM 输出需要一个模式注册中心:一个消费者团队注册其依赖项的共享存储。任何主版本模式变更都会触发消费者通知和迁移窗口。注册中心使契约可见,而这是维护它的首要前提。

对于需要同时服务多个模式版本的团队——通常在迁移窗口期间——在网关层公开版本化命名空间(/v1/classify/v2/classify),而非通过 LLM 输出本身协商版本化。这将版本化逻辑保留在可观测和可控的基础设施层。


建立运营规范

工具已经具备;运营规范是缺失的一环。做到这一点的团队将少数行为视为不可妥协的:

  • 提示词变更即生产代码。 它们与应用代码变更遵循相同的审查和 CI/CD 流水线。未经黄金数据集回归套件把关的提示词编辑,等同于未经测试部署的热修复。
  • 模型升级即依赖项升级。 制定计划,在路由任何生产流量之前运行完整的回归套件,保留回滚到前一个锁定版本的路径,永远不要在周五升级。
  • 验证层是信任边界,而非尽力而为的检查。 模型返回的一切内容都是不可信的,直到通过验证。经过验证的输出是可审计的事件。当验证失败时,它是一个有回滚路径的已处理错误——而不是在下游数据中发酵的静默损坏。

回报是可量化的:75% 的企业报告称,在没有适当监控的情况下,AI 性能会随时间下降,超过半数报告了 AI 错误带来的营收影响。避免这些统计数据的团队,是那些从一开始就将 LLM 输出视为不稳定契约的团队——而非将其视为碰巧具有非确定性的稳定 API。

模型不是可靠的契约伙伴。你的版本化和测试规范才是。

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