AI 评测系列(08):评测 CI/CD——持续质量门控

0 阅读6分钟

为什么需要评测 CI/CD

代码 CI 防止功能回退:有人改了 A,B 坏掉了,CI 立刻发现。

AI 系统同样需要这一层保护。以下三类变更都可能导致质量下降,而肉眼看不出来:

Prompt 变更:
  修改系统提示词以减少 Token 消耗
   可能导致 Answer Relevancy 下降 15%
   没有 CI,要等用户投诉才发现

知识库更新:
  新增了一批文档,更新了旧文档
   新文档的 chunking 策略不一致,导致 Context Recall 下降
   没有 CI,周报数据出来之前无人知晓

模型版本更新:
  LLM 提供商静默升级了模型权重
   输出风格变化,Faithfulness 下降
   没有 CI,只有当指标积累了足够多的坏样本才能发现

两级策略

第一级:快速评测(每次提交触发)

目标: 快速(< 3 分钟),发现明显的质量退化,不追求完整覆盖。

设计原则:

  • 用 10-20 个"黄金用例"(最重要、最有代表性的问题)
  • 每个场景类别至少覆盖 1-2 道题
  • 优先选择历史上曾经失败过的边界用例
# 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 配置:

# .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+ 用例),追踪质量趋势,作为发布决策依据。

# .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 经常失败会让团队开始跳过门控。

# 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 门控比绝对值门控更有价值:

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 评测注释格式:

## 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 变更是否影响了多步工具调用的触发逻辑。

基线管理

基线是评测系统的"上一个已知好状态"。设计不当的基线管理会让门控失效。

基线更新规则:

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 通过而更新基线("基线太严了,调低一点")
  • 基线版本没有对应的代码版本标签,无法回溯

整体评测工程架构

代码/Prompt/知识库变更
        ↓
Fast Eval(10 用例,< 3 min)
  通过 → PR 可合并
  失败 → 阻断 + PR 注释说明失败原因

每周定时
        ↓
Full Eval(100+ 用例,~1 hour)
  ↓ 结果与基线对比(Delta 报告)
  ↓ 存入历史记录(质量趋势数据库)
  ↓ 质量下降 → 通知负责人
  ↓ 质量持续提升 → 更新基线

每月
        ↓
Benchmark 审查
  ↓ 哪些用例一直通过?考虑加难度
  ↓ 哪些用例一直失败?检查是题目问题还是系统问题
  ↓ 有没有新的业务场景需要加入?
  ↓ 更新 eval_dataset.yaml,版本号 bump

总结

  1. 两级策略解决速度和覆盖的矛盾:快速评测(10 用例,3 分钟)每次提交都跑,完整评测(100+ 用例)定期跑,不同触发条件,不同目的
  2. Delta 门控比绝对值门控更敏感:当前分数 0.82 说明不了什么,本次变更导致下降 0.08 才是需要关注的信号
  3. PR 注释要展示变化量:让 Reviewer 看到"Tool Correctness 下降 0.13"而不是"Tool Correctness: 0.60",前者有行动指向,后者没有

欢迎访问 PrimeSkills —— 一个精心策划的 AI Agent 与技能市场,所有内容均经过真实企业级工作流验证。没有噱头,只有真正有效的东西。

更多实用知识和有趣产品,欢迎访问我的个人主页