第十章:LLM CI/CD 与灰度、Canary、A/B 发布
10.1 LLM CI/CD 流水线长什么样
传统 CI/CD 的"测试"阶段是确定性断言,LLM 应用的流水线在这一阶段替换成第 7 章的离线评测门禁,发布阶段则需要比传统蓝绿部署更谨慎的灰度策略——因为质量退化不像应用崩溃那样会立刻报错,而是悄悄发生。
flowchart LR
A["提交 Prompt/模型/路由变更"] --> B["自动化离线评测
(黄金测试集 + 切片门禁)"]
B -->|不通过| A
B -->|通过| C["Shadow 影子流量测试"]
C --> D["小流量灰度 5%-10%"]
D --> E{"线上指标是否达标?"}
E -->|是| F["逐步放量 25% → 50% → 100%"]
E -->|否| G["自动回滚"]
F --> H["全量发布"]
G --> A
style B fill:#fff3cd
style E fill:#fff3cd
style G fill:#fce8e6
10.2 Shadow 测试:让新版本"看见"流量但不影响用户
在真正切流量之前,可以让新版本(新 Prompt/新模型)接收生产流量的复制,产出结果但不返回给用户,只用于离线对比:
def handle_request(request):
response = production_pipeline(request) # 真实返回给用户
if shadow_enabled():
async_run(shadow_pipeline, request) # 异步执行,结果只记录不返回
return response
Shadow 测试的价值在于用真实流量分布验证新版本,而不承担任何用户体验风险,是离线评测(固定测试集)和线上灰度(真实承接流量)之间的中间地带。局限是它只能对比"输出内容",无法验证端到端的用户交互体验(如多轮追问)。
10.3 灰度发布:按比例放量,而不是一步切换
rollout_plan:
- stage: canary
traffic_percent: 5
duration_minutes: 60
guard_metrics:
error_rate_max: 0.02
contract_violation_rate_max: 0.01
p99_latency_ms_max: 8000
- stage: ramp_25
traffic_percent: 25
duration_minutes: 120
- stage: full
traffic_percent: 100
每一阶段都设置护栏指标(guard metrics),这些指标来自第 8 章的可观测性聚合数据。任意护栏指标越界,自动暂停放量或回滚到上一阶段,而不是等人工发现问题。
10.4 A/B 测试:回答"哪个版本更好",而不只是"新版本有没有崩"
灰度发布关注的是"新版本是否安全",A/B 测试关注的是"两个版本哪个业务效果更好",两者可以结合但目的不同:
| 灰度发布 | A/B 测试 | |
|---|---|---|
| 核心问题 | 新版本会不会引发事故 | 新旧版本哪个业务指标更好 |
| 流量分配 | 单调递增(5%→25%→100%) | 长期保持固定比例对照(如 50/50) |
| 判断依据 | 错误率、延迟、契约违反率等护栏指标 | 用户满意度、任务完成率等业务指标 |
| 典型时长 | 数小时到几天 | 数天到数周,需要统计显著性 |
10.4.1 A/B 测试对样本量和统计显著性有明确要求
LLM 输出的业务指标(如用户满意度)方差通常比传统 A/B 测试(如按钮点击率)更大,需要更大的样本量才能得出有统计意义的结论。没有做显著性检验就下结论"新版本更好",是最容易踩的坑——观察到的差异很可能只是噪声。
from scipy import stats
def is_significant(control_scores: list[float], treatment_scores: list[float], alpha=0.05) -> bool:
_, p_value = stats.ttest_ind(control_scores, treatment_scores)
return p_value < alpha
10.5 回滚要快且要有明确触发条件
回滚不应该依赖人工盯着仪表盘做判断,应该把 10.3 节的护栏指标接入自动化回滚:
def check_rollout_health(current_metrics: dict, guard_metrics: dict) -> bool:
for metric_name, max_value in guard_metrics.items():
if current_metrics.get(metric_name, 0) > max_value:
trigger_rollback(reason=f"{metric_name} 超过阈值")
return False
return True
回滚目标应该是第 9 章版本注册表里"上一个已知良好版本"的完整快照——Prompt、模型快照、路由策略三者一起回滚,而不是只回滚其中一项,否则可能出现版本组合不一致导致的新问题。
10.6 常见错误
10.6.1 评测通过就直接全量发布
离线评测无法覆盖生产环境全部输入分布,必须经过 Shadow 测试和灰度放量的验证,才能全量。
10.6.2 灰度阶段不设护栏指标,靠人工盯着看
人工监控响应慢且容易疏漏,护栏指标应该接入自动化的暂停/回滚机制。
10.6.3 把灰度发布和 A/B 测试混为一谈
灰度发布关注"安全性",A/B 测试关注"业务效果哪个更优",两者的流量策略和判断依据都不同,不能用同一套流程处理。
10.6.4 A/B 测试样本量不足就下结论
LLM 业务指标方差大,没做统计显著性检验就宣布"新版本更好",很可能被噪声误导。
10.6.5 回滚只回滚模型,不回滚配套的 Prompt 和路由策略
三者应作为一个整体版本快照一起回滚,否则可能出现新旧配置不匹配引发的次生问题。
10.7 本章总结
- LLM CI/CD 用评测门禁替代传统的确定性测试断言,发布环节需要比传统应用更谨慎的灰度策略;
- Shadow 测试用真实流量验证新版本,但不影响用户,是离线评测和线上灰度之间的中间地带;
- 灰度发布按比例递增放量,每阶段设自动化护栏指标,越界自动暂停或回滚;
- A/B 测试和灰度发布目的不同:前者比较业务效果,后者验证安全性,需要统计显著性支撑结论;
- 回滚要快且自动化,回滚目标是版本注册表里的完整版本快照,而非单一组件。