你的模型更新是一次破坏性变更:你欠集成商的“行为变更日志”
某家厂商在周二下午向模型别名推送了一个“小幅更新”。到了周四,四家客户公司正在进行事件响应。他们本周都没有部署代码。他们的仪表板上没有任何关于延迟、错误率或任何其他基础设施维度的指标退化。改变的是,在他们固定的别名背后的模型开始返回略有不同的句子、略有不同的 JSON 以及略有不同的拒绝——而他们的团队针对旧行为编写的每一个提示词(Prompt)现在都成了一份没人履行的合约。
这种不对称性就是问题的核心。供应商将这次发布视为一次部署:经过内部测试,通过了一些聚合评估,并在维护窗口内逐步推向 100%。而消费端将其视为一次语义化版本(semver)违规:一个依赖项在生产环境中自动升级,却没有更改其版本字符串,随后最终用户的错误报告接踵而至,主题还带着轻快的“我们这边什么都没改”。
“行为变更日志”(Behavioral changelog)是这两个世界之间不存在的文档。我们有库的变更日志、SaaS API 的发布说明、云服务的弃用时间表以及安全 领域的 CVE 动态。但我们没有任何东西可以告诉集成商:你昨天依赖的模型从今天起将产生统计学上不同的输出,而且这里是差异的具体表现。这篇文章探讨了该文档应该包含什么,谁应该负责它,以及在等待供应商成熟的过程中,处于接收端的集成商应该衡量什么。
为什么模型是你技术栈中类型定义最糟糕的依赖项
软件依赖已经演变为几个易于理解的类别。带有语义化版本(semver)的固定库版本为你提供了一份强有力的合约:补丁版本不会以用户可见的方式改变行为,次要版本将是增量更新,主要版本可能会带来破坏,但那是你主动选择的。托管的 SaaS API 提供了一份较弱但已发布的合约:响应格式有文档记录,弃用时间表已列入日程,当出现变动时,供应商会发布状态页面。黑盒依赖——比如你无法查看其内部的网络——则根本不提供合约,你会通过重试、超时和断路器来补偿。
通过别名访问的基础模型不属于以上任何一类。它在形式上看起来像 SaaS API(你 POST 一个 JSON,返回一个 JSON),但响应并未明确定义。供应商发布一个层级名称(Sonnet, Opus, GPT-4o),保留因任何原因更新底层权重的权利,并在文档中告诉你,响应在设计上就是非确定性的。固定的快照——gpt-4-0613,claude-3-5-sonnet-20241022——缩小了变动范围,但并未完全关闭它;对齐补丁、安全更新和推理栈(serving-stack)的更改仍然在同一个标识符下发布,且没有相应的版本升级。
结果是,你的提示词是一份写给从未同意受其约束的交易对手的合同。你写下“仅返回有效的 JSON,不要前言”,是因为今天的模型偶尔会添加前言。你写下“如果你不知道,就说‘我不知道’”,是因为今天的模型倾向于用长篇大论来规避风险。你写下“将回复限制在 200 字以内”,是因为今天的模型是针对特定的字数校准的。每一条指令都是针对特定行为特征的承重式权宜之计(load-bearing workaround),一旦该特征发生变化——哪怕是在同一个别名下——权宜之计就会开始产生意外。
最适合模型这种依赖项类别的词,我们还没有一个清晰的定义:一个行为表面(Behavioral surface)。它不是 API(形状松散),不是库(你无法在本地固定它),也不是服务(SLA 是运营层面的,而非语义层面的)。在供应商和消费者就这究竟是哪种依赖达成一致之前,关于合同的争论就失去了锚点。
行为变更日志实际上包含什么
一份有用的行为变更日志绝非营销性质的发布说明。供应商喜欢的“指令遵循能力提升了 12%”之类的辞令完全是错误的类型——它告诉消费者某些东西正朝着供应商喜欢的方向改变,却隐瞒了消费者预测其集成是否会崩溃所需的信息。为集成商编写的变更日志具有不同的结构。
行为增量,而非权重增量。 消费者不在乎添加了哪些微调数据或应用了哪些安全训练。他们在乎的是:边缘提示词的拒绝率是增加了还是减少了?响应长度的中位数是否增加了?在受架构约束的提示词中,JSON 遵循率是否发生了变化?语气是变得更正式还是更随意了?这些都是可以在固定评估套件上测量的,可以用前后对比的数字表示,并且对下游团队有意义。变更日志条目写着“评估套件上的响应长度中位数增长了 14%”,这是集成商可以采取行动的依据;而“改进了冗长校准”则不是。
分布形状,而非单点指标。 聚合准确率掩盖了伤害最大的退化。一个平均准确率提高 2% 的模型,在某个恰好是你的全部用户群的长尾子群体上,仍然可能退化 30%。行为变更日志应发布按聚类划分的指标——按主题划分的拒绝率、按架构划分的格式遵循度、按提示词长度划分的延迟——以便消费者能够发现他们业务负载所在的细分领域。
针对前一版本的兼容性报告。 Apple 的 MUSCLE 研究引入了“负向反转”(negative flips)——即之前的模型是正确的而新模型是错误的情况——作为一等公民的兼容性指标,区别于聚合准确率。他们在 Llama 1 到 Llama 2 更新上的结果显示,通过兼容性感知更新策略,负向反转最多可减少 40%。无论供应商是否采用该策略,他们都可以发布该指标:在预留的评估套件中,之前正确的响应现在有多少比例是错误的?这个数字告诉集成商,他们现有的提示词是否可能开始退化。
在故障切换中存续的命名弃用窗口。 固定快照只有在存在时间足够长以便完成迁移时才有用。变更日志应承诺任何命名别名的最短生命周期,并承诺在该生命周期内的行为稳定性——补丁可以发布,但它们在已发布的评估套件上的增量(delta)将保持在数字阈值以下。如果没有这种承诺,“固 定到快照”就是一场表演;快照只是推理栈本周恰好在运行的东西。
未发生变化的部分。 一份好的变更日志一半关于新功能,一半关于保留下来的东西。如果已发布架构的 JSON 遵循率没有变化,请说明。如果关于医疗问题的拒绝行为保持不变,请说明。集成商正面临着供应商无意触及的事物发生无声变化的风险;明确的不变量与明确的增量同样有用。
无人负责的升级模式
当一个静默更新发布时,通常会有四家客户公司同时发现问题,且每一家都认为问题出在自己身上。他们的 on-call 会因为用户投诉激增而收到报警。他们的工程团队会回滚最近的部署,结果一无所获,然后开始盯着遥测数据发呆。一位资深工程师最终会大声质疑“模型是不是变了”,经过一番测试——重新运行经典提示词,将输出与上周的截图对比,询问支持团队是否有人见过类似情况——团队得出结论:是的,可能变了,但由于版本字符串没变,他们无法证明这一点。
此时,另外三家公司也在并行运行相同的循环。供应商的支持渠道收到了四张看起来像是四个不同问题的工单,因为每个客户都将症状局限在自己的业务面上:“摘要变得啰嗦了”,“JSON 解析器在 0.3% 的响应中崩溃”,“之前正常的类别拒绝率翻倍了”,“工具调用参数现在的字符串引号过多了”。这种信号在模型层表现为协同回归,而在支持工单上则表现为四个互不相关的应用层故障。
这种模式是缺失契约在运维上的体现。如果有真正的变更日志,第一张工单在一段话内就能解决:是的,我们昨天发布了一个次要修正版,在无 Schema 提示词上的冗长度实测增加了 9%;这是回滚别名,这是迁移窗口。 如果没有日志,解决过程就是一场在全世界每个集成商那里重复上演的耗时数日的取证演习。
解决方案不是等待供应商出于慷慨而发布变更日志。消费端的对策是像对待不稳定依赖项一样对待模型,并进行相应的仪表化:
- 针对你所依赖的模型别名,每天运行一套差异探测套件(diff-probe suite)——一组具有稳定预期输出的经典提示词。该套件运行成本低廉,其产生的显著差异是你感知底层权重变动的最早信号。
- 保留一个金丝雀队列,调用两个版本前的固定快照,并针对一组代表性提示词将其输出与当前别名进行对比。当金丝雀在非微不足道的案例比例上与别名不一致时,你就发现了行为差异。
- 将你的提示词仓库视为与模型快照绑定的版本化制品。提示词更改和模型更改是同一种事件:行为扰动。两者都应生成 eval 运行,都应是可评审的,且都应是可回滚的。
- 在每个请求中记录模型别名和解析后的底层模型标识符(当供应商公开时)。当出现回归时,第一个问题是“模型变了吗”,而回答这个问题的唯一方法就是拥有数据。
无人愿意回答的契约面问题
最终,供应商和集成商必须进行一次对话:这到底是一种什么样的依赖关系?诚实的答案对双方来说都是令人不安的。
如果模型是遵循语义化版本(semver)的库,供应商就欠下一份“主版本-次版本-补丁版本”的契约:次版本内的行为变化保证是有界的,任何更大的变动都要提升主版本并提供弃用时间表。这是消费者想要的,但供应商无法给出可信的承诺,因为他们无法证明边界;行为面的维度太高,无法进行穷尽验证。
如果模型是带有 SLA 的服务,供应商欠下的是运维保证(正常运行时间、延迟、吞吐量),而非语义保证——消费者将行为风险视为集成的正常成本。这大致是现状,也是不对称性的来源:每个消费者在模型发生偏移时都在承担一次“未列入预算”的契约违规,因为他们以为自己拥有比实际更强的保证。
如果模型是无法固定的依赖项,供应商欠下的是彻底的透明度,以换取完全不承担契约责任——频繁发布行为差异对比、公开评测套件、邀请制早期访问计划,以及针对集成商提供的提示词套件运行供应商侧的回归测试。这是目前最诚实的框架,但也是最需要供应商进行文化变革的框架。
大多数供应商想住在 (b) 里,表现得却像住在 (a) 里。大多数消费者在集成时表现得像在 (a) 里,却在事故发生时发现自己在 (b) 里。行为变更日志是迫使双方坦诚面对自己真实处境的文件。它还揭示了深层更难的问题:供应商组织内谁负责消费者界面?工程团队负责模型。研究团队负责训练。产品团队负责 API。没人负责行为契约——这就是为什么没人发布它的原因。
