AI 学习笔记(二十二):LLM 自动止血与恢复验收最小实践

上一篇我们把 burn-rate 告警和 runbook 自动化串了起来。
但线上真正更难的,不是“能不能先止血”,而是:

系统什么时候可以自动降级,什么时候可以恢复放量,以及什么时候才算真正恢复。

很多团队的事故处理会卡在这里:

  1. 告警来了,先关功能、切 fallback、冻结发布
  2. 指标看起来回落了,于是大家觉得“差不多恢复了”
  3. 结果 20 分钟后问题复发,或者质量指标继续慢性失血

这篇继续生产运维主线,补上一个更完整的闭环:
把自动止血、恢复流程和恢复验收阈值做成可执行状态机。

先说结论:

  1. 自动止血解决的是“先别继续扩大损失”,不是“已经恢复”
  2. 恢复必须分阶段推进,不能从 readonly 直接跳回 full traffic
  3. 只有恢复验收通过,系统才应该自动退出事故态

1. 为什么自动止血和恢复验收必须分开设计

很多团队已经会做自动止血,例如:

  1. 错误率飙高时自动切 provider fallback
  2. 高风险写操作自动转只读
  3. burn-rate 超阈值时自动冻结发布

这些动作都很有价值,但它们本质上只是 containment
也就是:先把事故扩散速度压下来。

真正的恢复还要回答另外几个问题:

  1. 核心用户路径是否已经重新稳定
  2. 质量指标是否回到可接受区间
  3. 高风险副作用是否已经停止
  4. 恢复动作本身有没有引入新的债务或新瓶颈

如果没有这层验收,系统很容易陷入两种假恢复:

  1. 表面恢复
    服务 200 了,但结构化输出质量、人工接管率、误调用率仍然失控
  2. 脆弱恢复
    只是靠极端降级把指标压住,一旦恢复流量或重新放开功能就再次恶化

所以自动化不能只设计“怎么止血”,还要定义“恢复完成”的硬门槛。

2. 最小状态机:把事故处理拆成 5 个明确阶段

一个适合 LLM 应用的最小状态机,可以先定义为 5 个阶段:

  1. healthy
    正常运行
  2. mitigating
    进入自动止血,冻结高风险动作
  3. degraded
    系统仍然可服务,但运行在受限模式
  4. recovering
    开始小流量恢复、逐项验证
  5. verified
    验收通过,退出事故态

核心不是名字,而是每个阶段的允许动作要不同。

例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
incident_state_machine:
healthy:
allow:
- normal_release
- full_tool_access
- full_traffic
mitigating:
allow:
- provider_fallback
- readonly_mode
- release_freeze
deny:
- risky_write_tools
- new_prompt_rollout
degraded:
allow:
- limited_traffic
- manual_approval_write
deny:
- auto_resume_full_traffic
recovering:
allow:
- canary_restore
- recovery_verification
verified:
allow:
- gradual_resume

这样做的价值是:

  1. 自动化系统知道当前应该做什么
  2. 值班同学知道哪些动作仍然被禁止
  3. 事故复盘能明确卡在哪个阶段,而不是笼统说“恢复慢”

3. 自动止血先做什么:优先控制副作用和失血速度

进入 mitigating 阶段时,建议优先做下面几件事:

  1. 冻结高风险写操作
    例如外呼、发消息、写工单、改数据库、执行 shell
  2. 切 provider fallback
    把异常 provider 的流量切走,或者收紧到稳定模型
  3. 收缩上下文和工具范围
    减少长上下文和大工具集带来的额外不确定性
  4. 冻结发布
    暂停 prompt / workflow / routing 配置变更
  5. 生成 incident 记录
    留下自动动作、触发原因和开始时间,避免后续无证据复盘

最常见的错误,是自动止血一上来就做“太聪明”的修复。
更稳的原则是:

先把副作用和继续失血的风险压住,再谈恢复。

这对 LLM 系统尤其重要,因为很多事故不是“服务彻底挂了”,而是:

  1. 能回答,但质量明显漂移
  2. 能调用工具,但副作用变多
  3. 能跑流程,但人工接管率突然飙升

这些情况最怕的不是短时不可用,而是静默继续扩大损失。

4. 恢复验收看什么:至少做 4 组验证

进入 recovering 后,不要只盯一个成功率图。
最小恢复验收,建议至少做下面 4 组验证。

1) 服务路径验证

确认核心链路已经能稳定完成:

  1. 主用户路径成功率回到阈值上方
  2. P95 / P99 延迟恢复到可接受区间
  3. provider fallback 不再频繁触发

2) 质量路径验证

对 LLM 应用来说,这往往比服务 200 更关键:

  1. 结构化输出成功率恢复
  2. 关键任务通过率恢复
  3. 人工接管率显著回落

3) 副作用验证

恢复期间要重点确认“坏事有没有继续发生”:

  1. 高风险写操作误触发为 0
  2. 敏感外呼、通知、工单、自动提交没有继续误发
  3. 回滚或降级动作没有留下脏状态

4) 稳定窗口验证

不要看到 5 分钟恢复就宣布结束。
建议至少要求:

  1. 在一个连续观察窗口内维持稳定
  2. 没有新的 P1/P2 告警
  3. 没有新的 burn-rate 升高信号

可以先从一个很实用的阈值表开始:

1
2
3
4
5
6
7
8
9
10
11
12
recovery_gate:
service:
success_rate_gte: 99.5
p95_latency_ms_lte: 3500
quality:
structured_output_success_gte: 97
human_handoff_rate_lte: 8
side_effect:
risky_write_incidents_eq: 0
stability:
no_p1_p2_alert_for_minutes: 30
burn_rate_lte: 1.5

这里的数值不一定通用,但结构上必须完整。

5. 可直接接入服务层的最小恢复决策示例

下面这段 Node.js 示例演示一个最小恢复判定器:
它会区分“继续降级”“可以小流量恢复”“恢复验收通过”。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
export function evaluateRecovery(snapshot) {
const {
successRate,
p95LatencyMs,
structuredOutputSuccess,
humanHandoffRate,
riskyWriteIncidents,
burnRate,
noMajorAlertMinutes,
} = snapshot;

if (riskyWriteIncidents > 0) {
return { nextState: "mitigating", reason: "side-effect-not-cleared" };
}

if (successRate < 99.5 || p95LatencyMs > 3500) {
return { nextState: "degraded", reason: "service-gate-failed" };
}

if (structuredOutputSuccess < 97 || humanHandoffRate > 8) {
return { nextState: "degraded", reason: "quality-gate-failed" };
}

if (burnRate > 1.5 || noMajorAlertMinutes < 30) {
return { nextState: "recovering", reason: "needs-stability-window" };
}

return { nextState: "verified", reason: "all-gates-passed" };
}

再配一个恢复动作策略:

1
2
3
4
5
6
7
8
9
10
11
12
13
recovery_profiles:
degraded:
- keep_release_frozen
- keep_risky_tools_disabled
- run_smoke_checks_every_5m
recovering:
- restore_10_percent_traffic
- run_regression_eval
- require_manual_ack_for_writes
verified:
- restore_full_traffic_gradually
- unfreeze_release_after_checklist
- close_incident_with_evidence

这样就能避免“指标一恢复就全开”的粗暴切换。

6. 恢复不是一个开关,而是一条放量曲线

很多事故二次爆发,不是因为第一次没止住,而是恢复太快。
更稳的方式是把恢复做成放量曲线:

  1. 10% 流量恢复
  2. 跑一次关键路径验证
  3. 30% 流量恢复
  4. 再看一轮 burn-rate / 质量指标 / 副作用
  5. 最后才恢复到 100%

如果你的系统还涉及工具写操作、自动审批、工作流编排,恢复顺序建议比普通 Web 服务更保守:

  1. 先恢复只读查询类能力
  2. 再恢复低风险工具调用
  3. 最后恢复高风险写操作

因为 LLM 事故里最贵的,往往不是“服务短时不可用”,而是恢复过快后重新触发错误动作。

7. 一周落地建议:先把“恢复完成”写成硬规则

如果你想一周内落地,建议按这个节奏做:

  1. Day 1
    为现有事故流程补上状态机:mitigating / degraded / recovering / verified
  2. Day 2
    明确 4 组恢复验收指标和阈值
  3. Day 3
    接入最小恢复决策器,只控制只读 / fallback / release freeze
  4. Day 4
    做一次假恢复演练,验证“恢复过快会不会再次触发 burn-rate”
  5. Day 5
    把恢复证据自动写入 incident 模板,形成复盘输入

先把“什么时候不算恢复”说清楚,通常比继续堆自动化动作更重要。

总结

自动止血解决的是先别继续出血,恢复验收解决的是别把假恢复当成真恢复。
如果没有恢复验收门槛,自动化越强,越可能把错误更快地重新放大。

如果这周你只做一件事,我建议优先完成:

给事故 runbook 增加一个 verified recovery 门槛,并要求系统只有在验收通过后才能自动退出降级态。

参考资料

本文永久链接: https://www.mulianju.com/learning-notes/ai-learning-notes-llm-auto-mitigation-recovery-verification-minimal-practice/