跳到主要内容

你的 AI 功能灰度发布正沿着错误的轴线进行

· 阅读需 13 分钟
Tian Pan
Software Engineer

我上个月交流过的一个团队,在四个星期内将一项新的 Agent 功能从 1% 的用户灰度推广到了 50%。聚合质量指标保持在噪声范围内,延迟也保持在 SLA 之内。他们正在准备 100% 全量发布的备忘录时,支持队列突然“起火”了——一个拥有六工具研究工作流的客户,自 10% 灰度阶段以来就一直在接收静默损坏的输出。困难查询(Hard queries)一直存在,均匀地分布在每个分群中,被平均化到了底噪中。直到一个高频用户在大规模使用中撞上了这些问题,大家才发现。

这不是监控失败,而是灰度发布维度的失败。功能标志工具(Feature flag tooling)——包括 LaunchDarkly、Flagsmith、Unleash 和 Cloudflare-Flagship 等所有此类工具——都假设爆炸半径(blast radius)随接触到的人数成比例扩大。对于确定性软件,这在很大程度上是正确的:一个空指针异常(NullPointerException)要么影响所有人,要么谁都不影响,将其暴露给 1% 的用户会将可见的爆炸范围限制在 1%。但对于 AI 功能,爆炸半径并不在“人”这个维度上扩展,而是在“输入”维度上扩展。而几乎没有人会在输入维度上进行灰度发布。

这种思维转变虽小,但影响深远:不要再问“我的用户中有多少比例看到了这个功能”,而要开始问“我的分布中有多少比例的困难输入正在流经新路径”。这两个问题听起来很像,但它们产生的发布策略却迥然不同。

为什么基于用户 ID 的哈希对难度是“盲视”的

比例发布通过将稳定属性(用户 ID、账户 ID、会话 ID)哈希到一个桶(bucket)中,并根据桶的值来控制功能开关。就其预期用途而言,数学逻辑是合理的:哈希近似均匀,因此 5% 的用户获得 5% 的流量。关键在于,哈希与查询内容是相互独立的。这种独立性正是使发布对用户公平的原因,但也正是使其对查询难度盲视的原因。

生产环境的查询分布具有严重的长尾效应。像 Scale 的企业级工具使用排行榜(enterprise tool-use leaderboard)这样的公开基准测试显示,大约 85% 的提示词(prompt)至少需要三次工具调用,约 20% 需要七次或更多。经验表明,最难的 5% 的查询主导了失败模式——长上下文、模糊的意图、多跳推理、模型从未见过组合使用的工具。当你按用户进行哈希时,你会将这最难的 5% 均匀地分布在每个分群(cohort)中。在 1% 灰度时,1% 的用户看到困难查询;在 50% 灰度时,50% 的用户看到困难查询。困难查询失败的比率在整个灰度过程中保持不变。

这就是陷阱所在。每个晋级门槛都在评估一个增量(delta)——当前分群与对照组之间的质量差异——由于各分群在统计学上是相同的,这个增量大致是恒定的。50% 的阶段看起来和 10% 或 1% 的阶段没什么两样。你并没有从灰度发布中学到任何东西,你只是在消耗日历时间。

100% 发布阶段是困难查询失败的绝对数量首次超过阈值,使得单个高频租户或单个嘈杂的分群能够发现它们。或者——这是一种更常见的模式——在灰度过程中进行了模型升级,困难查询的失败率翻了一番,但聚合指标看起来仍然良好,因为困难查询仅占流量的 5%,而 5% 流量上的 100% 相对回归仅表现为 5 个百分点的变动,被吸收入了每周的方差中。

AI 失败真正扩展的维度

在输入侧有四个维度,AI 的爆炸半径会在这些维度上非线性复合。认真对待其中任何一个,都是比用户百分比更好的灰度发布维度。

任务难度是最通用的维度。这里的难度是经验性的,而非编辑性的:它是系统当前对该输入的条件失败率。根据历史通过率对离线评估进行分桶——简单(>95% 通过)、中等(75-95%)、困难(<75%)——你就拥有了难度分层,可以通过路由分类器或嵌入聚类查找(embedding-cluster lookup)将其应用于实时流量。

Prompt Token 数量是一个较粗略的代理指标,但实现起来要容易得多,因为它不需要分类器——只需要一个计数器。质量回归往往集中在长上下文的一端,那里位置脆弱性(positional fragility)、注意力稀释和提示词缓存驱逐(prompt-cache eviction)相互作用。先按 p50 Token 桶进行灰度,最后按 p99 进行,这迫使团队在受控流量下触及分布中的危险部分。

按意图或主题分布的查询是具备领域意识的团队已经在其分析中拥有的切片。如果你的支持助手处理计费、技术和账户恢复流程,这些切片的失败特征截然不同。先发布计费功能,最后发布账户恢复,这不是“黑科技”——而是承认在账户恢复中,模型必须自信地拒绝,那里的失误最为显眼,且会投诉的用户群最集中。

工具调用深度是 Agent 维度。失败率随深度几何倍增:如果每次工具调用的单步成功率为 95%,那么 7 步轨迹的成功率仅为 70%。在任何用户比例下允许无限深度,都是在将分布中最糟糕的部分推广给那部分用户。限制深度——从单次工具调用开始,然后是 3 步链条,然后是 5 步,最后是无限制——是唯一尊重实际失败曲线的灰度方式。

这些维度可以组合使用。我见过最强大的发布方案是在主维度(通常是难度桶或工具深度)上进行灰度,并将用户百分比作为次要关卡来限制绝对流量。用户百分比变成了限流器,而不是质量控制手段。

实践中的难度感知分桶

怀疑论者的观点是“但在生产流量上,我没有标注好的难度信号”。有道理。以下三种近似方法可以帮你解决大部分问题:

  1. 嵌入聚类查找 (Embedding-cluster lookup)。对评估集中采样出的几千个查询运行 k-means 聚类。根据离线评估为每个簇分配一个历史通过率。在运行时,对进入的查询进行嵌入(embedding),找到最近的簇,并根据其难度桶进行路由。基础设施成本是一次嵌入调用和一次最近邻查找——使用托管向量数据库,耗时远低于 50ms。

  2. 轻量级路由模型 (Lightweight router model)。训练一个小型分类器(微调 BERT 类模型虽然是大材小用,但成本极低),根据 prompt 预测难度桶。训练数据是带有通过率标签的评估集。路由模型与主要功能开关(feature flag)检查并行运行。

  3. 启发式分层 (Heuristic strata)。从廉价的代理指标开始——token 长度、是否存在结构化输入、连词数量、语言代码、系统提示词模板 ID。这些可能不如学习型路由器准确,但成本为零,且能捕获最重要的粗粒度难度信号。

核心属性:拦截点是在 输入 上触发,而不是在 用户 上触发。同一个用户在同一个会话中可能会触发简单和困难的查询,而每个查询都是独立路由的。这并非 bug,而是整套方案的核心价值。你正在缩小真正存在风险的维度上的爆炸半径(blast radius)。

分布感知的晋升门控

修复灰度的另一半工作是修复用于门控晋升(promotion)的指标。对全量用户(cohort)进行聚合质量评估是错误的指标,因为该群体在统计上与对照组(control)是相同的。正确的指标是 长尾分布切片上的 p99 质量

具体来说:在运行时为每个请求打上其难度桶、意图切片和工具深度的标签。然后这样评估门控:“对于困难切片,质量是否在对照组的 X% 以内?对于长上下文切片,质量是否在 X% 以内?对于深度工具调用切片,质量是否在 X% 以内?”任何切片的性能回退都会导致门控失败,即使聚合指标看起来是持平的。

这种方法在机器学习出现之前就被统计学家称为分层分析(stratified analysis)。它之所以没有出现在功能开关的仪表板中,是因为这些仪表板是围绕均匀群体的假设构建的。构建分层门控意味着仪表板必须了解你的切片。这是工程工作。这也是将安全上线 AI 功能的团队与在 80% 流量阶段收到报警电话(paged)的团队区别开来的工程工作。

这里的方差问题不可回避。LLM 的输出是随机的;即使输入固定,在 temperature > 0 时,同样的 prompt 也会产生不同的输出。一个常见的陷阱是将单次采样质量评估作为门控值。防止这种情况的纪律是在门控评估时进行每个输入的 n-of-k 采样:对每个输入运行多次,报告均值和方差,并根据最差情况(worst-case bound)而非均值来进行门控。这在门控评估时成本更高,但总比在全量 80% 时回滚的代价小。

工具深度绑定的灰度发布

专门针对 Agent 类功能,深度绑定是杠杆率最高的灰度原语。为每次轨迹(trajectory)设置 max_tool_calls 的硬上限,并逐渐提高这个上限,而不是用户比例。

机制如下:在 depth=1 时,系统表现得像是一个包裹模型原生工具调用能力的薄层。错误是可见的、可恢复的,且范围有限。在 depth=3 时,你正在测试规划器(planner)和恢复逻辑,但轨迹仍然短到足以调试。在 depth=5+ 时,你进入了会出现复合故障、失控循环和预算耗尽模式的机制。爆炸半径问题——即在安全机制干预之前,该功能对单个用户造成的最大损害——是深度的函数,而不是用户数量的函数。

一个我见过有效的实践阶梯:depth=1 在 100% 流量下运行两天,depth=3 在 100% 流量下运行三天,depth=5 在 100% 流量下运行五天,然后取消上限。用户比例自始至终都是满额的。被灰度的是 Agent 的 失败能力。这对于那些已经内化了“用户百分比就是安全旋钮”的功能开关团队来说是离经叛道的,但它符合风险实际存在的领域。

成本及其被抵制的原因

诚实地核算,在正确的轴线上进行灰度上线需要更多的前期成本。你需要一个按难度分层的评估集、一个了解切片的路由层、标记每个请求的可观测性,以及按切片而非群体进行评估的门控逻辑。这些都不是 LaunchDarkly 自带的功能。

团队抵制这种做法是因为用户百分比灰度是 易于理解 的。产品经理(PM)理解“我们现在灰度到了 50%”。领导层评审理解“如果指标保持稳定,我们下周推进到 100%”。切片感知版本更难沟通:“我们在简单任务上达到了 100%,中等任务 50%,困难任务 0%,当长上下文切片的 p99 质量达到对照组 5% 以内时,我们将放开困难任务的门控。”这听起来像是工程借口。但事实上,这正是随机系统控制下真实灰度应有的样子。

为切片感知版本辩护的最有力论据是另一种方案的失败模式。那些将用户百分比推到 100% 然后在困难查询的长尾中收到报警的团队,最终不得不在 100% 流量下回滚,白白浪费一个季度的势头,并在接下来的一个月里向公司解释为什么当初指标看起来是好的。这种代价远超分层灰度的基础设施成本。这是团队选择了错误的轴线所必须付出的代价。

思维框架的转变

功能标志文化伴随着确定性服务而发展,其结果是双模态的:要么成功,要么失败。AI 功能则不是双模态的。它们是质量分布,而方差存在于尾部。为双模态世界设计的发布策略——假设均匀的用户分群、基于聚合指标进行准入、按用户比例逐步放量——在每一步中都掩盖了尾部风险。

解决方案并非一个新工具,而是在现有工具之上提出的一个新问题:故障集中在哪些输入端分布上,我的发布过程是否意识到了这一点?在 2026 年,这个问题至少有三个具体的答案——难度分桶 (difficulty bucketing)、查询分布准入 (query-distribution gates)、工具深度限制 (tool-depth bounding)——如果你愿意进行切片数据的对接,实现这些策略的发布原语 (rollout primitives) 在每个主流功能标志平台中都已存在。

提出问题并构建对接机制的团队,正以证据驱动的速度发布 AI 功能。而不这样做的团队,则在尾部风险未察觉地累积时,将时间线压力称之为“发布”。那张 80% 的页面并非意外,而是审计终于追上了一个从一开始就没有在正确轴线上进行的发布。

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