为什么需要评测 CI/CD
代码 CI 防止功能回退:有人改了 A,B 坏掉了,CI 立刻发现。
AI 系统同样需要这一层保护。以下三类变更都可能导致质量下降,而肉眼看不出来:
swift
Prompt 变更:
修改系统提示词以减少 Token 消耗
→ 可能导致 Answer Relevancy 下降 15%
→ 没有 CI,要等用户投诉才发现
知识库更新:
新增了一批文档,更新了旧文档
→ 新文档的 chunking 策略不一致,导致 Context Recall 下降
→ 没有 CI,周报数据出来之前无人知晓
模型版本更新:
LLM 提供商静默升级了模型权重
→ 输出风格变化,Faithfulness 下降
→ 没有 CI,只有当指标积累了足够多的坏样本才能发现
两级策略
第一级:快速评测(每次提交触发)
目标: 快速(< 3 分钟),发现明显的质量退化,不追求完整覆盖。
设计原则:
- 用 10-20 个"黄金用例"(最重要、最有代表性的问题)
- 每个场景类别至少覆盖 1-2 道题
- 优先选择历史上曾经失败过的边界用例
python
# fast_eval_cases.yaml --- 10 个核心用例
FAST_EVAL_CASES = [
# 每个场景类别各 2-3 道
{"id": "FE01", "scenario": "factual", "question": "退款政策是什么?", ...},
{"id": "FE02", "scenario": "factual", "question": "质保期多长?", ...},
{"id": "FE03", "scenario": "procedure", "question": "怎么申请发票?", ...},
{"id": "FE04", "scenario": "procedure", "question": "如何修改收货地址?", ...},
{"id": "FE05", "scenario": "comparison", "question": "黄金会员和铂金会员区别?", ...},
{"id": "FE06", "scenario": "multi_hop", "question": "ORD-004 能退多少钱?", ...},
{"id": "FE07", "scenario": "edge", "question": "你们接受比特币支付吗?", ...},
# 以前失败过的经典难题
{"id": "FE08", "scenario": "ambiguous", "question": "...", ...},
{"id": "FE09", "scenario": "multi_doc", "question": "...", ...},
{"id": "FE10", "scenario": "adversarial", "question": "...", ...},
]
CI 配置:
yaml
# .github/workflows/fast_eval.yml
name: Fast Quality Gate
on:
pull_request:
paths:
- "prompts/**" # Prompt 变更
- "rag/**" # RAG 流水线变更
- "knowledge_base/**" # 知识库更新
jobs:
fast-eval:
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@v4
- run: pip install ragas deepeval
- name: Run fast evaluation
run: python eval/run_fast_eval.py --output fast_eval_result.json
- name: Check quality gates
run: python eval/check_gates.py fast_eval_result.json
# 门控逻辑在 check_gates.py 里,不通过则 exit(1)
- name: Comment on PR
if: always()
run: python eval/post_pr_comment.py fast_eval_result.json
第二级:完整评测(定期或大变更触发)
目标: 完整覆盖(100+ 用例),追踪质量趋势,作为发布决策依据。
yaml
# .github/workflows/full_eval.yml
name: Full Quality Evaluation
on:
schedule:
- cron: "0 2 * * 1" # 每周一凌晨 2 点
workflow_dispatch: # 支持手动触发
push:
branches: [main]
paths:
- "knowledge_base/**" # 知识库大更新时也触发完整评测
jobs:
full-eval:
runs-on: ubuntu-latest
timeout-minutes: 60
steps:
- uses: actions/checkout@v4
- run: pip install ragas deepeval
- name: Run full evaluation
run: python eval/run_full_eval.py --output full_eval_result.json
- name: Compare with baseline
run: python eval/compare_baseline.py full_eval_result.json baselines/latest.json
- name: Update baseline if approved
if: github.ref == 'refs/heads/main'
run: python eval/update_baseline.py full_eval_result.json
质量门控设计
什么应该阻断 PR,什么只告警
不是所有指标下降都应该阻断 PR,否则 CI 经常失败会让团队开始跳过门控。
yaml
# eval_gates.yaml
gates:
# 阻断 PR(critical gates)
- metric: faithfulness
threshold: 0.70
mode: block
reason: "幻觉风险,直接影响用户信任"
- metric: answer_relevancy
threshold: 0.65
mode: block
reason: "答案跑题,核心功能失效"
- metric: context_recall
threshold: 0.60
mode: block
reason: "检索遗漏关键信息,答案不完整"
# 告警但不阻断(warning gates)
- metric: context_precision
threshold: 0.70
mode: warn
reason: "检索引入噪音,影响质量但不崩溃"
- metric: tool_correctness
threshold: 0.60
mode: warn
reason: "Agent 工具调用问题,需要关注但可接受一定偏差"
# Delta 门控(基于变化量,不是绝对值)
- metric: faithfulness
max_delta: -0.05
mode: block
reason: "本次变更导致幻觉率显著上升"
Delta 门控比绝对值门控更有价值:
python
def check_gates(current: dict, baseline: dict, gates: list) -> list[str]:
failures = []
for gate in gates:
metric = gate["metric"]
current_score = current[metric]
baseline_score = baseline.get(metric)
# 绝对值门控
if "threshold" in gate and current_score < gate["threshold"]:
failures.append(f"[{gate['mode'].upper()}] {metric}: {current_score:.3f} < {gate['threshold']}")
# Delta 门控
if "max_delta" in gate and baseline_score is not None:
delta = current_score - baseline_score
if delta < gate["max_delta"]:
failures.append(
f"[{gate['mode'].upper()}] {metric} dropped {delta:+.3f} "
f"(threshold: {gate['max_delta']:+.3f})"
)
return failures
Delta 报告:让 Reviewer 看到变化量
PR 的评测注释不应该只是"Faithfulness: 0.82"。Reviewer 看到这个数字没有上下文------上次是多少?是变好了还是变差了?
好的 PR 评测注释格式:
markdown
## AI 质量评测报告
评测时间:2026-07-16 14:23 | 用例数:10 | 耗时:2.1 分钟
| 指标 | 当前 | 基线 | 变化 | 状态 |
|------|------|------|------|------|
| Faithfulness | 0.87 | 0.82 | **+0.05** | ✅ 提升 |
| Answer Relevancy | 0.71 | 0.79 | **-0.08** | ⚠️ 下降(告警)|
| Context Recall | 0.88 | 0.84 | **+0.04** | ✅ 提升 |
| Context Precision | 0.79 | 0.81 | -0.02 | ✅ 正常范围 |
| Tool Correctness | 0.60 | 0.73 | **-0.13** | 🚫 阻断 |
**结论:Tool Correctness 下降 0.13,超过告警阈值(-0.05),PR 被阻断。**
失败案例:
- FE06(订单退款查询):期望调用 get_order_status + calculate_refund,实际只调用了 calculate_refund
Agent 直接用了问题中的金额,没有先查订单确认金额
修复建议:检查本次 Prompt 变更是否影响了多步工具调用的触发逻辑。
基线管理
基线是评测系统的"上一个已知好状态"。设计不当的基线管理会让门控失效。
基线更新规则:
python
class BaselineManager:
def should_update_baseline(self, current: dict, previous_baseline: dict) -> bool:
"""决定是否将当前评测结果设为新基线"""
# 规则 1:当前结果比基线明显更好(各指标平均提升 > 0.02)
avg_delta = sum(
current[k] - previous_baseline[k]
for k in current
) / len(current)
if avg_delta > 0.02:
return True
# 规则 2:基线太旧(超过 30 天),更新以反映当前系统状态
if baseline_age_days > 30:
return True
# 规则 3:有明显版本变更(大版本发布)
# 由人工触发,不在自动规则里
return False
def update_baseline(self, current: dict) -> None:
# 保存历史基线,不直接覆盖
archive_current_baseline()
save_as_new_baseline(current)
不要做的事:
- 评测失败后为了让 CI 通过而更新基线("基线太严了,调低一点")
- 基线版本没有对应的代码版本标签,无法回溯
整体评测工程架构
markdown
代码/Prompt/知识库变更
↓
Fast Eval(10 用例,< 3 min)
通过 → PR 可合并
失败 → 阻断 + PR 注释说明失败原因
每周定时
↓
Full Eval(100+ 用例,~1 hour)
↓ 结果与基线对比(Delta 报告)
↓ 存入历史记录(质量趋势数据库)
↓ 质量下降 → 通知负责人
↓ 质量持续提升 → 更新基线
每月
↓
Benchmark 审查
↓ 哪些用例一直通过?考虑加难度
↓ 哪些用例一直失败?检查是题目问题还是系统问题
↓ 有没有新的业务场景需要加入?
↓ 更新 eval_dataset.yaml,版本号 bump
总结
- 两级策略解决速度和覆盖的矛盾:快速评测(10 用例,3 分钟)每次提交都跑,完整评测(100+ 用例)定期跑,不同触发条件,不同目的
- Delta 门控比绝对值门控更敏感:当前分数 0.82 说明不了什么,本次变更导致下降 0.08 才是需要关注的信号
- PR 注释要展示变化量:让 Reviewer 看到"Tool Correctness 下降 0.13"而不是"Tool Correctness: 0.60",前者有行动指向,后者没有
欢迎访问 PrimeSkills ------ 一个精心策划的 AI Agent 与技能市场,所有内容均经过真实企业级工作流验证。没有噱头,只有真正有效的东西。
更多实用知识和有趣产品,欢迎访问我的个人主页