2810 字
约 9 分钟
21
S22-A07 · AI 功能的灰度与回滚:从上线到止损

AI 摘要上线第三天,投诉进来:同一个按钮,有时把工单压成三行要点,有时复述用户的脏话,还带出了内部工单号。日志里没有 5xx,错误率曲线是平的,看板全绿。这类事故没有"抛异常"的入口,它的暴露方式是分布漂移,不是断崖。

灰度与回滚在 AI 功能上不是发布流程的一个环节,而是唯一能在不改代码的前提下改变输出分布的机制。判据按"差别 → 维度 → 通道 → 窗口 → 止损 → 不可逆"排。


1. 与传统功能的三个差别

差别 传统功能 AI 功能 直接后果
输出确定性 同输入同输出,断言可验 同输入多次调用可能不同 "回归通过"不等于行为正确
效果度量 单点用例通过/失败 只能按分布看比率 需要样本量与统计窗口
回滚动作 退回上一个构建即可 退代码不一定退行为 提示、模型、索引必须各自回退

第三行最常漏:代码回到 v1.4,提示仓库已推到 pr-9f2,模型别名也从 a 换到 b——行为不会回来。

2. 灰度维度:六个轴,只按用户分不够

轴 擅长 失效点
用户/账号 体验类功能,同一用户稳定命中 长尾用户少,统计力弱
流量比例 快速验错、量小 同一用户来回跳,指标失真
租户 B 端可分客户试点、单客户回退 租户数少时无法比较
场景/入口 高风险入口先关 依赖路由键,常缺
模型版本 比较模型差异 与提示版本混杂时无法归因
提示版本 比较措辞差异 需独立发布通道(§3)

一句话:体验按用户,风控按场景,模型与提示对比按请求并打版本指纹。

// 分流键只用稳定 ID:租户 > 用户 > 场景,版本指纹随日志落盘
String scene  = classify(req);                     // high_risk | normal | internal
int    bucket = Math.floorMod(murmur3(tenantId + ":" + userId), 100);
boolean on    = !"high_risk".equals(scene) && bucket < rolloutPct;
log.info("route ver={} bucket={} scene={} req={}", sha, bucket, scene, reqId);

分流键里出现会话 ID、时间戳或随机数,用户就会在版本之间来回跳:体验抖动,指标全废。

3. 模型与提示要走独立发布通道

变更对象 通道 回退单位 不独立会怎样
应用代码 常规构建流水线 构建号 提示回退被代码回滚覆盖
提示模板 提示仓库,内容 sha 化 模板 sha 无法只回退措辞
模型/别名 网关别名指向 别名版本 端点切换无审计
检索索引 索引快照 快照 ID 内容问题被当成模型问题
安全过滤 策略版本号 策略版本 拦截规则混在代码里
# 一次"行为版本"由三元组唯一确定,任何一项变动都算一次发布
behavior:
  prompt:    pr-2026-11-03-sha9f2
  model:     gw-alias/summarize-v3      # 写别名,不写具体端点
  retrieval: idx-ticket-20261105
rollout:
  channel: prompt-release               # 与 app-release 分离
  guard:   [quality_gate, cost_gate]    # 两个门禁都过才放量

4. 检测窗口:不能等用户投诉

指标 窗口 触发 备注
硬失败(5xx/超时/解析失败) 分钟级 超基线 1.5 倍 可自动止损
拒答/空答率 小时级 相对基线漂移 该答不答
重问率 小时到天 上升即异常 最像"答了等于没答"
格式违规率 小时级 超阈值比例 结构化输出被破坏
敏感/越界命中 分钟级 任何一次人工确认 见 §7
成本与延迟 小时级 超预算线 另见成本控制篇

窗口长度必须匹配样本量。日活几百的功能,小时窗口里"拒答率翻倍"可能只是三个请求。判据写成"连续 N 个窗口同向漂移",而不是单窗口阈值。

5. 止损四级与生效时间

级别 动作 生效 代价 适用
L1 切模型 别名回退到上一模型 秒到分钟 质量降、成本变 怀疑端点变更
L2 降模板 固定模板/规则输出 秒级 体验明显下降但不空缺 输出不可控
L3 关功能 入口隐藏 + 后端关闭 秒级,两级须一致 功能缺失 风险不可估
L4 回滚提示 模板 sha 回退 分钟级,含缓存失效 需重跑回归 措辞/指令类
// 顺序固定:切模型 → 降模板 → 关功能,每级都要打标
SummaryResult r = tryModel(primary);
if (r == null)                    r = tryModel(previousAlias);      // L1
if (r == null || r.unsafe())      r = templateSummary(ticket);      // L2
if (featureOff())                 return SummaryResult.hidden();    // L3
trace.tag("degrade.level", r.level);                                // 级别进日志

L1 与 L4 的实际生效时间几乎全花在缓存上:结果缓存、提示编译缓存、前缀缓存各清一遍才真的变了。

6. 回滚必须幂等可重入

回滚脚本最常见的两种事故:执行到一半失败,重跑时把已回退的版本又推回去一次;只改了配置中心,没清本地缓存,部分实例继续旧行为。

# 幂等:先读目标态,再决定是否动作;重复执行结果一致
want=pr-2026-11-01-sha7c1
have=$(curl -s $CFG/api/prompt/active | jq -r .sha)
if [ "$have" != "$want" ]; then
  curl -sX PUT $CFG/api/prompt/active \
       -d "{\"sha\":\"$want\",\"reason\":\"rollback $INC\"}"
fi
curl -sX POST $CACHE/purge -d 'prefix=summary:'   # 失效动作本身也要幂等

判据:连跑两次,第二次不产生变更记录(或只留 no-op 记录);任一步失败后可从任意位置重入。

7. 线上开关的最小设计

要求 最小实现 缺了会怎样
谁有权 值班可 L1/L2,负责人可 L3,L4 需两人 事故中找不到能拍板的人
变更留痕 who/when/why/inc 写独立审计流 复盘还原不出时间线
默认值 默认关;配置中心不可用时用代码内默认 配置中心故障放大成全站故障
粒度 按场景 + 租户可独立关闭 只能整站关,损失放大
生效确认 变更后回读生效值,不一致即告警 "以为关了其实没关"
flag:
  key: ai.summary.enabled
  scope: [global, tenant, scene]     # 三级覆盖,窄的优先
  default: false                     # 配置中心不可达时的兜底
  owners: [oncall, feature-owner]
  audit: true

灰度期除错误率外,还要看质量代理指标:编辑率、采纳率、人工接管率、重问率、输出长度分布。前四个需要埋点,最后一个服务端就有,且突变通常早于投诉。

8. 人工抽检:比例与抽样方式

参数 典型量级 理由
每日抽检量 几十到几百条,按风险分档 低于此看不出比率问题
抽样方式 分层随机:场景 × 版本 × 结果类型 纯随机被高频场景淹没
必抽样本 全部敏感命中 + 全部点踩 高信息量样本
复核 高风险场景双人 单人标注漂移大
WITH ranked AS (
  SELECT id, scene, ver,
         ROW_NUMBER() OVER (PARTITION BY scene, ver ORDER BY RAND()) rn
  FROM summary_log WHERE ts >= now() - interval '1 day'
)
SELECT * FROM ranked WHERE rn <= 20;   -- 每 场景×版本 各 20 条

用户告知策略与风险等级绑定:对外事实陈述用显式标记("AI 生成,可能有误"),内部低风险润色可静默但必须留反馈入口;静默场景出错时,用户措辞通常更激烈。

9. 数据侧的不可逆

不可逆项 为什么 处置
已送达的内容 用户已读,撤不回 只能事后更正并标注
已写入的记忆/偏好 影响后续全部请求 记忆带来源与版本,可定点清除
已产生的成本 已计费 预算与配额前置
入过训练/微调的数据 影响面不可枚举 入库前过滤
已发出的外部副作用 工单已建、已通知 幂等键 + 补偿

10. 实验平台的必要性判据

同时进行的实验 ≥3 个、需要统计显著性、变更频率按周算——三条占一条就该上平台;单功能、月级变更、用户数十人的内部工具,手写开关加每日抽检就够。

11. 排错

现象 原因 处置
关开关后仍见旧行为 多级缓存或实例配置未同步 回读生效值 + 按前缀失效
灰度与全量指标矛盾 分流键含会话/随机/时间 只用稳定 ID 取模
回滚提示后效果没变 模型别名或索引也变了 三元组一起回退并逐一核对
抽检结论与投诉不一致 样本未覆盖投诉场景 把投诉场景加进必抽
回滚脚本重跑后版本跳回 未做目标态比对 先读后写

12. 边界

情形 灰度价值 说明
内部工具、用户数十人 低 开关 + 每日抽检更划算
日调用百次以下 低 统计窗口拉不开
效果本身无法度量(创意类) 低 只能测硬失败与成本
高风险对外内容 高 显式标记 + 人工复核
涉及资金与权限的动作 不该由灰度承担 需要确定性校验,不是概率放量

13. 自测

  1. 代码回滚到上一构建后行为没恢复,先核哪三项?
  2. 只按用户比例灰度,在哪类需求上会失效?
  3. 四级止损里,哪一级不配缓存失效就等于没做?
  4. 为什么"拒答率翻倍"不能在小时窗口直接触发止损?
  5. 回滚脚本的幂等性用什么动作验证?
  6. 哪些数据一旦产生就只能事前拦截?
S22-A07 · AI 功能的灰度与回滚:从上线到止损
https://www.clxhxhhr.top/posts/6172/
作者
clxstart
发布于
2024-12-31
许可协议
CC BY-NC-SA 4.0
评论
0 条
还没有评论,先写一条吧。