Skip to content

10.4 评估自动化

前面几节我们建立了评估指标体系、标准测试集和自动评分工具。但如果每次改代码都得手动跑一遍评估,那和"手动测试"有什么区别?评估自动化就是把所有评估能力串联起来,嵌入 CI/CD 流水线,让质量把控变成"自动运转的流水线"。

生活类比:评估自动化就像汽车工厂的质检线——每辆车经过时,机器人自动检测刹车、排放、灯光,不合格的自动拦截。不需要质检员拿着检查表一辆一辆看。人工抽检只用于复核。

评估自动化的必要性

在 Agent 开发中,"改了 Prompt 会不会影响其他场景?"、"新模型上线后效果到底好没好?"——这些问题如果靠人工每次手动评估,不仅效率低下,还容易遗漏。

┌──────────────────────────────────────────────────────────────────┐
│              手动评估 vs 自动化评估                                │
├──────────────────────────────────────────────────────────────────┤
│                                                                  │
│  手动评估流程:                                                    │
│  开发者修改代码 -> 手动跑几个 case -> 肉眼判断 -> 上线                  │
│  ├─ 问题:覆盖不全、主观性强、不可复现、耗时                        │
│                                                                  │
│  自动化评估流程:                                                  │
│  开发者提交 PR -> 自动触发评估 -> 运行全部测试集 -> 生成报告           │
│  ├─ 优势:全面覆盖、客观量化、可复现、7×24 运行                    │
│                                                                  │
│  评估自动化 = 速度 × 覆盖度 × 一致性 × 可追溯性                    │
│                                                                  │
└──────────────────────────────────────────────────────────────────┘

CI/CD 集成评估流水线

评估自动化的核心是将评估嵌入持续集成/持续交付(CI/CD)流水线:

┌──────────────────────────────────────────────────────────────────┐
│                 CI/CD 评估流水线架构                               │
├──────────────────────────────────────────────────────────────────┤
│                                                                  │
│  ┌──────────┐    ┌──────────┐    ┌──────────┐    ┌────────────┐ │
│  │ 代码提交  │───►│ 单元测试  │───►│ 评估测试  │───►│ 报告生成   │ │
│  └──────────┘    └──────────┘    └──────────┘    └────────────┘ │
│                                       │                          │
│                                       ├─ 基础能力测试集            │
│                                       ├─ 回归测试集               │
│                                       ├─ 安全测试集               │
│                                       └─ 性能测试集               │
│                                            │                      │
│                                     ┌──────▼──────┐              │
│                                     │ 结果对比     │              │
│                                     │ vs 基线版本  │              │
│                                     └──────┬──────┘              │
│                                            │                      │
│                               ┌────────────┼────────────┐        │
│                          ┌────▼───┐   ┌────▼───┐   ┌────▼───┐   │
│                          │ PASS   │   │ WARN   │   │ FAIL   │   │
│                          │ 自动合并│   │ 人工审核│   │ 阻塞合并│   │
│                          └────────┘   └────────┘   └────────┘   │
│                                                                  │
└──────────────────────────────────────────────────────────────────┘

关键设计决策

决策点选项建议
触发时机PR 提交 / 定时 / 手动PR 提交 + 每日定时
测试集规模全量 / 采样 / 分层快速检查用采样,合并前用全量
阻塞策略严格阻塞 / 软阻塞 / 仅报告回归测试严格阻塞,新能力软阻塞
环境管理共享 / 隔离 / 容器化使用 Docker 保证环境一致性
成本控制全量 / 动态 / 限额设置每日预算上限

回归测试框架

回归测试确保新版本不在已有能力上"退化"——就像体检时不仅查新症状,还要复查之前的指标有没有变差。

python
import json                                      # JSON 处理
from typing import Dict, List, Callable          # 类型注解
from dataclasses import dataclass                # 数据类
from datetime import datetime


@dataclass
class RegressionTestResult:
    """回归测试结果——单个测试用例的对比结果"""
    test_id: str                             # 测试 ID
    test_name: str                           # 测试名称
    baseline_score: float                   # 基线版本得分
    current_score: float                    # 当前版本得分
    delta: float                            # 得分变化(正数=提升,负数=退化)
    passed: bool                            # 是否通过(退化不超过阈值)
    threshold: float                        # 允许的最大退化幅度


class RegressionTestSuite:
    """
    回归测试套件——对比当前版本与基线版本的表现
    
    工作原理:
    1. 加载基线版本的评估结果(之前保存的)
    2. 用当前版本运行相同的测试集
    3. 逐个对比得分变化
    4. 如果退化超过阈值,标记为失败
    """

    def __init__(
        self,
        baseline_results_path: str,          # 基线结果文件路径
        degradation_threshold: float = 0.05,  # 退化容忍度(默认 5%)
    ):
        """
        初始化回归测试套件
        
        Args:
            baseline_results_path: 基线结果 JSON 文件路径
            degradation_threshold: 允许的最大得分下降幅度(0.05 = 5%)
        """
        self.threshold = degradation_threshold
        self.baseline = self._load_baseline(baseline_results_path)
        self.results: List[RegressionTestResult] = []

    def _load_baseline(self, path: str) -> Dict:
        """加载基线结果——从 JSON 文件读取之前的评估得分"""
        with open(path, "r") as f:
            return json.load(f)

    def run(
        self,
        agent_fn: Callable,                 # Agent 执行函数
        test_cases: List[Dict],              # 测试用例列表
    ) -> Dict:
        """运行回归测试——对比当前版本与基线"""
        self.results = []                     # 清空上次结果
        regressions = []                     # 退化列表
        improvements = []                     # 提升列表

        for case in test_cases:
            test_id = case["id"]             # 测试 ID
            baseline_score = self.baseline.get(test_id, 0.0)  # 基线得分

            # 运行当前版本 Agent
            current_output = agent_fn(case["input"])
            current_score = self._evaluate(case, current_output)  # 评估当前输出

            delta = current_score - baseline_score  # 计算变化
            passed = delta >= -self.threshold       # 退化不超过阈值才算通过

            result = RegressionTestResult(
                test_id=test_id,
                test_name=case.get("name", test_id),
                baseline_score=baseline_score,
                current_score=current_score,
                delta=delta,
                passed=passed,
                threshold=self.threshold,
            )
            self.results.append(result)

            if not passed:                   # 退化超过阈值
                regressions.append({
                    "test_id": test_id,
                    "name": case.get("name"),
                    "baseline": baseline_score,
                    "current": current_score,
                    "delta": delta,
                })
            elif delta > 0.05:               # 显著提升
                improvements.append({
                    "test_id": test_id,
                    "name": case.get("name"),
                    "delta": delta,
                })

        total = len(self.results)
        passed_count = sum(1 for r in self.results if r.passed)

        return {
            "summary": {
                "total": total,
                "passed": passed_count,
                "failed": total - passed_count,
                "pass_rate": f"{passed_count/total*100:.1f}%",
                "regressions": len(regressions),
                "improvements": len(improvements),
                "avg_delta": sum(r.delta for r in self.results) / total,
            },
            "regressions": regressions,       # 退化详情
            "improvements": improvements,     # 提升详情
            "details": [                       # 逐条详情
                {
                    "test_id": r.test_id,
                    "baseline": r.baseline_score,
                    "current": r.current_score,
                    "delta": r.delta,
                    "passed": r.passed,
                }
                for r in self.results
            ],
        }

    def _evaluate(self, case: Dict, output: str) -> float:
        """
        评估单个输出(简化版)
        
        生产环境应使用 LLM-as-Judge 进行语义级评估。
        这里使用文本相似度作为演示。
        """
        expected = case.get("expected", "")
        if not expected:
            return 1.0                         # 无期望答案时默认满分
        from difflib import SequenceMatcher
        return SequenceMatcher(None, expected, output).ratio()


def save_baseline(
    path: str,                                # 保存路径
    agent_fn: Callable,                       # Agent 执行函数
    test_cases: List[Dict],                   # 测试用例
):
    """
    保存基线结果——用于后续回归测试对比
    
    首次建立回归测试时调用此函数,它会运行当前版本的所有测试,
    将得分保存为基线。之后每次评估都与这个基线对比。
    """
    baseline = {}                             # {test_id: score}
    for case in test_cases:
        output = agent_fn(case["input"])     # 运行 Agent
        from difflib import SequenceMatcher
        score = SequenceMatcher(
            None, case.get("expected", ""), output
        ).ratio()
        baseline[case["id"]] = score          # 记录得分

    with open(path, "w") as f:
        json.dump(baseline, f, indent=2)
    print(f"Baseline saved: {len(baseline)} test cases")

A/B 实验框架

A/B 实验对比两个版本的 Agent 在同一测试集上的表现——就像盲测:把两种口味的可乐倒进没标签的杯子里,让人喝了打分,看哪种更好。

python
import numpy as np                               # 数值计算
from scipy import stats                           # 统计检验
from typing import Dict, List


class ABExperiment:
    """
    A/B 实验框架——对比对照组和实验组的统计差异
    
    核心流程:
    1. 收集两组评分数据(对照组 A 和实验组 B)
    2. 计算描述统计(均值、标准差)
    3. 进行统计显著性检验(Welch's t-test)
    4. 计算效应量(Cohen's d)
    5. 给出结论(是否显著优于/差于对照组)
    """

    def __init__(self, name: str, significance_level: float = 0.05):
        """
        Args:
            name: 实验名称,如 "prompt_v2_vs_v1"
            significance_level: 统计显著性水平(默认 0.05,即 95% 置信度)
        """
        self.name = name
        self.alpha = significance_level         # 显著性水平
        self.results_a: List[float] = []        # 对照组得分列表
        self.results_b: List[float] = []        # 实验组得分列表

    def add_result_a(self, score: float):
        """添加对照组得分"""
        self.results_a.append(score)

    def add_result_b(self, score: float):
        """添加实验组得分"""
        self.results_b.append(score)

    def analyze(self) -> Dict:
        """分析 A/B 实验结果——统计检验 + 效应量"""
        if len(self.results_a) < 2 or len(self.results_b) < 2:
            return {"error": "Not enough data", "min_samples": 2}

        arr_a = np.array(self.results_a)       # 转为 numpy 数组
        arr_b = np.array(self.results_b)

        # 基本统计量
        mean_a = np.mean(arr_a)                 # 对照组均值
        mean_b = np.mean(arr_b)                 # 实验组均值
        std_a = np.std(arr_a, ddof=1)           # 标准差(ddof=1 表示样本标准差)
        std_b = np.std(arr_b, ddof=1)

        # 效应量(Cohen's d)——衡量差异的"大小"而非只是"有无"
        pooled_std = np.sqrt((std_a**2 + std_b**2) / 2)  # 合并标准差
        cohens_d = (mean_b - mean_a) / pooled_std if pooled_std > 0 else 0

        # Welch's t-test——不假设两组方差相等的 t 检验
        t_stat, p_value = stats.ttest_ind(arr_b, arr_a, equal_var=False)

        # 95% 置信区间
        diff = mean_b - mean_a                  # 均值差异
        se = np.sqrt(std_a**2 / len(arr_a) + std_b**2 / len(arr_b))  # 标准误
        ci_lower = diff - 1.96 * se            # 置信区间下限
        ci_upper = diff + 1.96 * se            # 置信区间上限

        # 结论判断
        if p_value < self.alpha:               # p 值小于显著性水平
            if diff > 0:
                conclusion = f"✅ 实验组显著优于对照组(p={p_value:.4f})"
            else:
                conclusion = f"❌ 实验组显著差于对照组(p={p_value:.4f})"
        else:
            conclusion = f"➖ 无显著差异(p={p_value:.4f}),样本量可能不足"

        return {
            "experiment": self.name,
            "sample_size": {
                "control": len(self.results_a),
                "treatment": len(self.results_b),
            },
            "statistics": {
                "control_mean": round(mean_a, 4),
                "treatment_mean": round(mean_b, 4),
                "mean_difference": round(diff, 4),
                "relative_change": f"{diff/mean_a*100:.1f}%" if mean_a != 0 else "N/A",
            },
            "statistical_test": {
                "method": "Welch's t-test",
                "t_statistic": round(t_stat, 4),
                "p_value": round(p_value, 4),
                "significant": p_value < self.alpha,
                "cohens_d": round(cohens_d, 4),
                "effect_size": self._interpret_cohens_d(cohens_d),
                "confidence_interval_95": [round(ci_lower, 4), round(ci_upper, 4)],
            },
            "conclusion": conclusion,
        }

    def _interpret_cohens_d(self, d: float) -> str:
        """解释 Cohen's d 效应量——衡量差异的实际意义大小"""
        d = abs(d)
        if d < 0.2:                            # 可忽略
            return "可忽略"
        elif d < 0.5:                          # 小效应
            return "小"
        elif d < 0.8:                          # 中效应
            return "中"
        else:                                  # 大效应
            return "大"


# 使用示例
def run_ab_experiment():
    """运行 A/B 实验示例"""
    experiment = ABExperiment(name="prompt_v2_vs_v1")

    # 模拟测试结果(实际中从 Agent 执行获取)
    # 对照组:原始 Prompt 的得分
    control_scores = [0.82, 0.78, 0.85, 0.90, 0.76, 0.88, 0.81, 0.79,
                      0.84, 0.87, 0.75, 0.83, 0.80, 0.86, 0.77]
    # 实验组:新 Prompt 的得分
    treatment_scores = [0.85, 0.88, 0.87, 0.92, 0.84, 0.90, 0.86, 0.83,
                        0.89, 0.91, 0.82, 0.87, 0.85, 0.90, 0.84]

    for s in control_scores:
        experiment.add_result_a(s)             # 添加对照组数据
    for s in treatment_scores:
        experiment.add_result_b(s)             # 添加实验组数据

    result = experiment.analyze()              # 分析结果

    print("=" * 60)
    print(f"A/B 实验: {result['experiment']}")
    print("=" * 60)
    print(f"\n样本量: 对照组 {result['sample_size']['control']}, "
          f"实验组 {result['sample_size']['treatment']}")
    print(f"\n描述统计:")
    print(f"  对照组均值: {result['statistics']['control_mean']}")
    print(f"  实验组均值: {result['statistics']['treatment_mean']}")
    print(f"  均值差异:   {result['statistics']['mean_difference']}")
    print(f"  相对变化:   {result['statistics']['relative_change']}")
    print(f"\n统计检验:")
    print(f"  p 值: {result['statistical_test']['p_value']}")
    print(f"  效应量: {result['statistical_test']['cohens_d']} "
          f"({result['statistical_test']['effect_size']})")
    print(f"  95% 置信区间: {result['statistical_test']['confidence_interval_95']}")
    print(f"\n结论: {result['conclusion']}")


if __name__ == "__main__":
    run_ab_experiment()

GitHub Actions 评估流水线

将评估嵌入 GitHub Actions,实现"代码提交即触发评估":

python
# 评估执行脚本——可被 GitHub Actions 调用
import argparse                                 # 命令行参数解析
import json                                     # JSON 处理
import sys                                      # 系统操作(退出码控制)
from pathlib import Path                        # 路径处理


def run_evaluation(
    test_suite: str,                           # 测试集名称
    sample_size: int = None,                   # 采样数量(可选)
    output: str = "results.json",              # 输出文件路径
    parallel: int = 1,                         # 并行度
):
    """
    执行评估的核心逻辑
    
    这个函数会被 CI/CD 流水线调用,执行步骤:
    1. 加载测试集
    2. (可选)随机采样
    3. 逐个执行 Agent 并评估
    4. 保存结果
    5. 根据得分决定退出码(0=通过,1=失败)
    """
    test_cases = load_test_suite(test_suite)   # 加载测试集

    # 采样(用于快速检查模式)
    if sample_size and sample_size < len(test_cases):
        import random
        random.seed(42)                         # 固定随机种子,保证可复现
        test_cases = random.sample(test_cases, sample_size)

    print(f"Running evaluation: {len(test_cases)} test cases")

    results = []
    for i, case in enumerate(test_cases):
        output = execute_agent(case["input"])  # 执行 Agent
        score = evaluate_output(case, output)  # 评估输出
        results.append({
            "id": case["id"],
            "score": score,
            "output": output,
        })

        if (i + 1) % 10 == 0:                  # 每 10 个打印进度
            print(f"  Progress: {i+1}/{len(test_cases)}")

    # 保存结果
    summary = {
        "test_suite": test_suite,
        "total": len(results),
        "avg_score": sum(r["score"] for r in results) / len(results),
        "results": results,
    }

    Path(output).parent.mkdir(parents=True, exist_ok=True)
    with open(output, "w") as f:
        json.dump(summary, f, indent=2)

    print(f"Results saved to {output}")
    print(f"Average score: {summary['avg_score']:.3f}")

    # 根据得分决定 CI 退出码
    if summary["avg_score"] < 0.7:              # 低于阈值则失败
        print("❌ Evaluation score below threshold!")
        sys.exit(1)                             # 退出码 1 = CI 失败
    else:
        print("✅ Evaluation passed!")
        # sys.exit(0) 默认退出码 0 = CI 通过


def load_test_suite(name: str):
    """加载测试集——从 JSON 文件读取"""
    path = Path(f"eval-results/test_suites/{name}.json")
    if not path.exists():
        print(f"Test suite not found: {path}")
        sys.exit(1)
    with open(path) as f:
        return json.load(f)["cases"]


def execute_agent(input_text: str) -> str:
    """执行 Agent(实际实现中调用 Agent 框架)"""
    return f"Mock response for: {input_text[:50]}"


def evaluate_output(case: dict, output: str) -> float:
    """评估输出(实际实现中使用 LLM-as-Judge)"""
    from difflib import SequenceMatcher
    return SequenceMatcher(None, case.get("expected", ""), output).ratio()


if __name__ == "__main__":
    parser = argparse.ArgumentParser()
    parser.add_argument("--test-suite", required=True)  # 测试集名称
    parser.add_argument("--sample-size", type=int)      # 采样数量
    parser.add_argument("--output", default="results.json")  # 输出路径
    parser.add_argument("--parallel", type=int, default=1)   # 并行度
    args = parser.parse_args()

    run_evaluation(args.test_suite, args.sample_size, args.output, args.parallel)

对应的 GitHub Actions 配置(YAML):

yaml
# 文件路径:.github/workflows/agent-eval.yml
name: Agent Evaluation Pipeline

on:
  pull_request:
    branches: [main]
    paths:
      - 'agent/**'
      - 'prompts/**'
      - 'config/**'
  schedule:
    - cron: '0 2 * * *'  # 每天凌晨 2 点运行全量评估
  workflow_dispatch:  # 允许手动触发

jobs:
  quick-eval:
    name: Quick Evaluation (Sampling)
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Setup Python
        uses: actions/setup-python@v5
        with:
          python-version: '3.11'
      - name: Install dependencies
        run: |
          pip install -r requirements.txt
          pip install -r requirements-eval.txt
      - name: Run quick evaluation
        env:
          OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
        run: |
          python -m eval.run \
            --test-suite quick \
            --sample-size 50 \
            --output eval-results/quick-eval.json
      - name: Check regression
        run: |
          python -m eval.check_regression \
            --current eval-results/quick-eval.json \
            --baseline eval-results/baseline.json \
            --threshold 0.05
      - name: Upload results
        uses: actions/upload-artifact@v4
        with:
          name: quick-eval-results
          path: eval-results/

评估结果可视化

可视化是评估系统的"最后一公里"——好的可视化让团队能在 30 秒内判断版本质量。

┌──────────────────────────────────────────────────────────────────┐
│              评估仪表盘核心面板                                    │
├──────────────────────────────────────────────────────────────────┤
│                                                                  │
│  ┌─────────────────────┐  ┌─────────────────────┐               │
│  │   整体评分趋势图      │  │   各维度雷达图        │               │
│  │   (折线图,按时间)    │  │   (多维度对比)        │               │
│  └─────────────────────┘  └─────────────────────┘               │
│                                                                  │
│  ┌─────────────────────┐  ┌─────────────────────┐               │
│  │   回归测试热力图      │  │   A/B 实验对比       │               │
│  │   (版本 × 用例)       │  │   (箱线图/小提琴图)    │               │
│  └─────────────────────┘  └─────────────────────┘               │
│                                                                  │
│  ┌─────────────────────┐  ┌─────────────────────┐               │
│  │   延迟分布直方图      │  │   成本趋势图          │               │
│  │   (P50/P95/P99)      │  │   (Token/费用)       │               │
│  └─────────────────────┘  └─────────────────────┘               │
│                                                                  │
└──────────────────────────────────────────────────────────────────┘

下面是可视化仪表盘的实现,代码逐行注释:

python
import json                                      # JSON 处理
import matplotlib.pyplot as plt                  # 绘图库
import numpy as np                               # 数值计算
from pathlib import Path                        # 路径处理


class EvalDashboard:
    """评估结果可视化仪表盘——生成评分趋势、雷达图、延迟分布等可视化"""

    def __init__(self, results_dir: str = "eval-results"):
        """
        初始化仪表盘
        
        Args:
            results_dir: 评估结果文件目录
        """
        self.results_dir = Path(results_dir)
        # 设置中文字体(支持中文显示)
        plt.rcParams['font.sans-serif'] = ['Arial Unicode MS', 'SimHei', 'DejaVu Sans']
        plt.rcParams['axes.unicode_minus'] = False  # 正确显示负号

    def plot_score_trend(self, history: list, save_path: str = None):
        """绘制评分趋势图——展示 Agent 得分随版本变化"""
        fig, ax = plt.subplots(figsize=(12, 6))

        dates = [h.get("date", f"v{i}") for i, h in enumerate(history)]
        scores = [h["avg_score"] for h in history]

        ax.plot(dates, scores, 'o-', linewidth=2, markersize=8, color='#2E86AB')
        ax.axhline(y=0.8, color='green', linestyle='--', alpha=0.5, label='目标线')  # 目标线
        ax.axhline(y=0.7, color='orange', linestyle='--', alpha=0.5, label='警戒线')  # 警戒线
        ax.fill_between(range(len(scores)), scores, alpha=0.1, color='#2E86AB')
        ax.set_ylim(0, 1.0)
        ax.set_xlabel('版本')
        ax.set_ylabel('平均分')
        ax.set_title('Agent 评估得分趋势')
        ax.legend()
        ax.grid(True, alpha=0.3)

        if save_path:
            plt.savefig(save_path, dpi=150, bbox_inches='tight')
        plt.show()

    def plot_radar(self, dimensions: dict, save_path: str = None):
        """绘制能力雷达图——多维度能力可视化"""
        categories = list(dimensions.keys())     # 维度名称列表
        values = list(dimensions.values())      # 各维度得分
        N = len(categories)

        # 计算雷达图角度
        angles = [n / float(N) * 2 * np.pi for n in range(N)]
        values += values[:1]                    # 闭合雷达图
        angles += angles[:1]

        fig, ax = plt.subplots(figsize=(8, 8), subplot_kw=dict(polar=True))
        ax.plot(angles, values, 'o-', linewidth=2, color='#D64933')
        ax.fill(angles, values, alpha=0.25, color='#D64933')
        ax.set_xticks(angles[:-1])
        ax.set_xticklabels(categories)
        ax.set_ylim(0, 5)
        ax.set_title('Agent 能力雷达图', pad=20)
        ax.grid(True)

        if save_path:
            plt.savefig(save_path, dpi=150, bbox_inches='tight')
        plt.show()


# 使用示例
def demo_dashboard():
    """仪表盘演示"""
    dashboard = EvalDashboard()

    # 评分趋势示例数据
    history = [
        {"date": "2026-07-01", "avg_score": 0.72},
        {"date": "2026-07-05", "avg_score": 0.75},
        {"date": "2026-07-10", "avg_score": 0.78},
        {"date": "2026-07-15", "avg_score": 0.82},
        {"date": "2026-07-20", "avg_score": 0.85},
    ]
    dashboard.plot_score_trend(history)

    # 能力雷达图示例数据
    dimensions = {
        "任务完成率": 4.2,
        "回答准确率": 4.5,
        "响应速度": 3.8,
        "工具调用": 4.0,
        "安全性": 4.8,
        "用户满意度": 4.3,
    }
    dashboard.plot_radar(dimensions)

    print("可视化仪表盘演示完成!")


if __name__ == "__main__":
    demo_dashboard()

常见误区

误区一:评估只在上线前跑一次

评估不是"一次性考试",而是"持续体检"。每次代码变更(改 Prompt、换模型、加工具)都应该触发评估。只在上线前跑一次,就像只在出事前才做体检——太晚了。

误区二:A/B 实验样本量不够就下结论

"新 Prompt 在 5 个 case 上表现更好"——这可能是随机波动。统计显著性检验需要足够的样本量,建议每组至少 30 个样本。样本量太少时,p 值不可靠。

误区三:CI 中评估不设阻塞策略

如果评估只生成报告但不阻塞合并,那和"不评估"差不多——开发者看了报告觉得"差不多就合了吧"。回归测试必须设置严格阻塞:退化超过阈值就拒绝合并。

误区四:基线永远不更新

基线是"上一版的评估结果",应该随着版本迭代而更新。如果基线一直是半年前的版本,那对比意义就不大了。建议每次发布后自动更新基线。

本节小结

环节关键实践工具/方法
CI/CD 集成PR 触发 + 定时全量GitHub Actions / GitLab CI
回归测试基线对比 + 退化阈值自定义 RegressionTestSuite
A/B 实验统计显著性检验 + 效应量Welch's t-test + Cohen's d
结果可视化趋势图 + 雷达图 + 分布图Matplotlib / Grafana / LangSmith
告警机制阈值告警 + 趋势告警Slack / PagerDuty / 邮件

关键原则

  • 评估自动化是 Agent 工程化的基石,没有自动化评估就无法持续迭代
  • 测试集应分层:快速检查(采样)-> 回归测试(全量)-> 专项测试(安全/性能)
  • A/B 实验需要足够的样本量才有统计意义,建议至少每组 30+ 样本
  • 可视化不是装饰——好的可视化让团队在 30 秒内判断版本质量
  • 保持基线文件的版本管理,每次通过评估后更新基线

有了自动化评估流水线,下一步需要从用户反馈中学习——这正是 10.5 节反馈闭环要解决的问题。