系列《AI 应用生产化手册》第 13 篇(共 30 篇)|配套开源项目:github.com/ChenYingbo/... 本篇是模块三(上线治理)收尾:提示词版本 → 灰度 → A/B → 回滚降级,四件套齐了
一、先看问题:出事了怎么办
灰度、A/B 都就位了,但"出事了怎么办"还是老三样:
- 主力模型挂了 = 用户全挂------网关或主力模型故障,所有请求 502,只能干等恢复。
- 回滚 = 重启发版------改坏想回退,要重新部署旧版本,几分钟到几十分钟。
- 没有开关------有些功能想"立刻关掉",但没有开关,只能改代码。
解法:降级链(主力→备用→规则兜底)+ 功能开关------模型挂了自动切备用,全挂了给规则兜底,功能可一键开关。
核心认知:恢复能力 = 上线能力。没有"1 分钟止血"手段的上线,是裸奔上线。降级不是"质量变差",是"在故障期保住基本可用"。
二、原理
降级链设计(主力 → 备用 → 规则兜底)
objectivec
主力模型(chat-strong)
│ 失败
▼
备用模型(chat-cheap) ← 便宜模型顶上,功能可用但质量略降
│ 失败
▼
规则兜底(rule-fallback) ← 固定话术,保住"有响应"而非 502
| 级 | 保住什么 | 代价 |
|---|---|---|
| 主力 | 完整质量 | 贵 |
| 备用 | 基本功能 | 质量略降、成本低 |
| 规则 | "有响应"(不 502) | 无智能,需向用户明示降级 |
三原则 :① 备用模型要定期验证 (没验证过的备用 = 心理安慰);② 降级必须可观测 (degraded 标记 + 日志);③ 规则兜底是最后防线,不能当常规路径。
功能开关(Feature Flag)
ini
FEATURE_BACKUP_MODEL=1 开/关备用模型降级
FEATURE_RULE_FALLBACK=1 开/关规则兜底
CANARY_ROLLOUT=10 灰度放量(R02)
开关设计要点:走配置不走代码(改开关不用发版);每开一个功能先想"怎么关掉它";开关状态要可观测。
止血三件套
| 手段 | 时机 | 效果 | 成本 |
|---|---|---|---|
| 重试 | 瞬时故障(网络抖动) | 自动恢复 | 低 |
| 降级 | 持续性故障(模型挂了) | 保住基本可用 | 低(切备用) |
| 回滚 | 变更导致的故障(改坏了) | 回到上一稳定版本 | 中(重部署/切版本) |
决策顺序:先重试(自动)→ 再降级(配置)→ 最后回滚(发版)。能配置解决的别发版。
回滚决策流程
告警/用户反馈
↓ 分级:S1 全挂/数据泄露?
↓ 立即止血(降级链自动生效 + 手动关开关)
↓ 判断根因:变更 or 外部故障
↓ 变更 → 回滚(提示词版本/放量归零/切备用)
↓ 外部 → 降级链等恢复 + 通知供应商
↓ 恢复后 → 复盘归档
三、动手:验证降级链三种路径
bash
git clone https://github.com/ChenYingbo/ai-prod-demo.git && cd ai-prod-demo
source .venv/bin/activate
# 1. 单元验证(fake client,无网络)
pytest tests/test_fallback.py -q
# 6 个用例:主力成功 / 主力失败备用成功 / 全挂规则兜底 / 规则关闭抛错 / 备用关闭直落规则
# 2. 真实演练:网关停掉,请求应自动降级
LLM_BACKUP_MODEL=chat-cheap uvicorn app.main:app --port 8000
# 另开终端(网关已停):
curl -X POST http://localhost:8000/api/chat -H "Content-Type: application/json" \
-d '{"question":"什么是 RAG?"}'
真实返回(没有降级链之前这里是 502):
json
{"answer":"抱歉,模型服务暂时不可用,请稍后重试。","model":"rule-fallback","degraded":true}
开关演练:让错误"看得见"
bash
# 关闭规则兜底 → 全挂时恢复 502
FEATURE_RULE_FALLBACK=0 LLM_BACKUP_MODEL=chat-cheap uvicorn app.main:app --port 8000
curl -X POST http://localhost:8000/api/chat -H "Content-Type: application/json" -d '{"question":"hi"}'
# 预期:502(降级链打穿,错误不再被兜底掩盖)
规则兜底是双刃剑:救用户但也掩盖错误------生产上"全挂 502"本身就是该告警的信号,兜底话术 + 告警要同时配。
降级证据链(真实日志)
ini
event=llm_fallback from_model=chat-strong to_model=(next) error="Error code: 502"
event=llm_fallback from_model=chat-cheap to_model=(next) error="Error code: 502"
event=llm_rule_fallback reason="Error code: 502"
event=chat_response latency_ms=3188 degraded=true
按 llm_rule_fallback 计数即可告警"降级风暴"。
快速恢复的补充件(配套项目已实现)
- drain :
scripts/drain.sh优雅停机注释版(SIGTERM → 等在途请求 → 上限) - 停机上限 :
uvicorn ... --timeout-graceful-shutdown 30 - SSE 断线续传 :
/api/chat/stream支持resume_from/Last-Event-ID------掐断后前端自动重连"接上继续"
四、真实踩坑
- 备用模型从不验证:配置了备用但没真实跑过------故障时才发现备用也挂了;定期用评测集跑备用
- 规则兜底掩盖错误:全挂也返回 200,告警看不见------兜底 + 计数告警必须同时配
- 降级不标记 :
degraded不返回、日志不打------用户和监控都不知道在降级 - 开关太多没人管:每开一个功能留一个开关------开关要有 owner 和说明
- 降级和熔断混淆:降级是"主动切备用",熔断是"检测故障后自动断流"------先有降级链,再上熔断器
- 把降级当常规路径:一直走规则兜底还美其名"降级中"------降级要有告警、要有时限
五、模块三小结(上线治理 4 篇收官)
| 篇 | 解决的问题 | 核心交付 |
|---|---|---|
| R01 | 提示词裸奔 | 提示词即代码(版本/评审/回滚) |
| R02 | 全量冒险 | 灰度分桶 + 放量节奏 |
| R03 | 感觉 ≠ 结论 | A/B + z 检验 + 双维度 |
| R04 | 出事了怎么办 | 降级链 + 功能开关 + 止血三件套 |
一条主线:把"变更"从"赌博"变成"有版本、有灰度、有实验、能止血的工程流程"。
明天(C01):进入模块四《Token 成本模型》------把"花了多少钱"算清楚(含真实 50 次调用 0.001 元/请求的实测)。 收藏 + 关注,每天一篇,30 天把 AI 应用送上生产。