Skip to content

第 13 章 · A/B 测试与对照实验

本章目标:

  • 理解科学验证的重要性
  • 掌握实验设计方法
  • 学会样本量计算和统计显著性检验
  • 建立回归测试集管理机制

13.1 为什么需要 A/B 测试

Agent 系统的改进往往难以量化:

  • Prompt 优化效果?
  • 新工具是否提升准确率?
  • 多步 vs 单步架构哪个更好?

原则:没有测量的改进只是观点。

13.2 实验设计

python
import random
from dataclasses import dataclass
from typing import List, Callable, Any

@dataclass
class ExperimentConfig:
    name: str
    baseline: Callable[[str], str]  # 对照组函数
    variant: Callable[[str], str]   # 实验组函数
    metric: Callable[[str, str], float]  # 评估函数

class ABTest:
    def __init__(self, config: ExperimentConfig):
        self.config = config
        self.results = {"A": [], "B": []}
    
    def run(self, test_cases: List[str], n_iterations: int = 3):
        """运行 A/B 测试
        
        Args:
            test_cases: 测试用例列表
            n_iterations: 每个用例运行次数(减少随机性)
        """
        for case in test_cases:
            for _ in range(n_iterations):
                # 随机分配
                group = random.choice(["A", "B"])
                
                # 执行对应版本
                if group == "A":
                    result = self.config.baseline(case)
                else:
                    result = self.config.variant(case)
                
                # 评估
                score = self.config.metric(case, result)
                self.results[group].append(score)
    
    def analyze(self) -> dict:
        """分析结果"""
        import statistics
        from scipy import stats
        
        a_scores = self.results["A"]
        b_scores = self.results["B"]
        
        # 描述性统计
        stats_a = {
            "mean": statistics.mean(a_scores),
            "median": statistics.median(a_scores),
            "stdev": statistics.stdev(a_scores) if len(a_scores) > 1 else 0
        }
        stats_b = {
            "mean": statistics.mean(b_scores),
            "median": statistics.median(b_scores),
            "stdev": statistics.stdev(b_scores) if len(b_scores) > 1 else 0
        }
        
        # 显著性检验(t 检验)
        t_stat, p_value = stats.ttest_ind(a_scores, b_scores)
        
        return {
            "baseline": stats_a,
            "variant": stats_b,
            "p_value": p_value,
            "significant": p_value < 0.05,
            "recommendation": "_variant" if stats_b["mean"] > stats_a["mean"] else "baseline"
        }

13.3 样本量计算

python
import math
from scipy.stats import norm

def calculate_sample_size(
    effect_size: float,  # 预期提升幅度
    alpha: float = 0.05,  # 显著性水平
    power: float = 0.8,  # 统计功效
    baseline_rate: float = 0.5  # 基准准确率
) -> int:
    """计算所需样本量
    
    Args:
        effect_size: 预期的相对提升(如 0.1 表示 10% 提升)
        alpha: 第一类错误概率
        power: 统计功效(1 - 第二类错误)
        baseline_rate: 对照组基准率
    
    Returns:
        每组所需最小样本量
    """
    z_alpha = norm.ppf(1 - alpha/2)
    z_beta = norm.ppf(power)
    
    # 两组比较的样本量公式
    p1 = baseline_rate
    p2 = baseline_rate * (1 + effect_size)
    
    n = ((z_alpha + z_beta) ** 2 * 
         (p1 * (1 - p1) + p2 * (1 - p2))) / (p2 - p1) ** 2
    
    return max(int(math.ceil(n)), 30)  # 至少 30 个样本

# 示例:检测 10% 提升,需要多少样本?
n = calculate_sample_size(effect_size=0.1, baseline_rate=0.7)
print(f"每组需要 {n} 个样本")  # 约 623 个

13.4 评估口径设计

python
def evaluate_rag_answer(
    question: str,
    answer: str,
    ground_truth: str
) -> float:
    """评估 RAG 回答质量"""
    from ai import createGateway
    gateway = createGateway({"apiKey": process.env.AI_GATEWAY_API_KEY})
    judge = gateway("openai/gpt-4o")
    
    prompt = f"""评估以下 RAG 回答的质量。

问题:{question}
参考答案:{ground_truth}
模型回答:{answer}

请从以下维度评分(1-5分):
1. 准确性:回答是否与参考答案一致
2. 完整性:是否涵盖了关键点
3. 相关性:是否与问题相关

只输出一个综合分数(1-5整数)。"""
    
    result = judge.generate([{"role": "user", "content": prompt}])
    return int(result.content.strip())

# 批量评估
async def batch_evaluate(test_set: list) -> dict:
    scores = []
    for item in test_set:
        answer = await call_agent(item["question"])
        score = evaluate_rag_answer(
            item["question"], 
            answer, 
            item["ground_truth"]
        )
        scores.append(score)
    
    return {
        "mean_score": sum(scores) / len(scores),
        "distribution": {i: scores.count(i) for i in range(1, 6)}
    }

13.5 回归测试集管理

python
import json
from pathlib import Path
from datetime import datetime

class RegressionSet:
    def __init__(self, path: str = "regression_tests.json"):
        self.path = Path(path)
        self.tests = self._load()
    
    def _load(self) -> list:
        if self.path.exists():
            return json.loads(self.path.read_text())
        return []
    
    def add(self, question: str, expected: str, category: str = "general"):
        """添加测试用例"""
        self.tests.append({
            "id": len(self.tests) + 1,
            "question": question,
            "expected": expected,
            "category": category,
            "added_at": datetime.now().isoformat()
        })
        self._save()
    
    def _save(self):
        self.path.write_text(json.dumps(self.tests, indent=2, ensure_ascii=False))
    
    def run_all(self, agent_fn) -> dict:
        """运行全部回归测试"""
        results = []
        for test in self.tests:
            answer = agent_fn(test["question"])
            passed = answer.strip() == test["expected"].strip()
            results.append({
                "id": test["id"],
                "passed": passed,
                "actual": answer
            })
        
        passed_count = sum(1 for r in results if r["passed"])
        return {
            "total": len(results),
            "passed": passed_count,
            "failed": len(results) - passed_count,
            "pass_rate": passed_count / len(results) if results else 0,
            "details": results
        }

# 使用示例
reg = RegressionSet()
reg.add("Python 的异步关键字是什么?", "async", "programming")
reg.add("解释 Transformer 架构", "transformer", "ml_concepts")

results = reg.run_all(my_agent)
print(f"通过率: {results['pass_rate']:.1%}")

本章小结

  • A/B 测试需要明确的假设和可测量的指标
  • 样本量由效应大小、alpha 和 power 共同决定
  • LLM-as-judge 可自动化评估流程
  • 回归测试集是质量保证的基础

🛠️ 动手实践

  1. 设计并运行一个 Prompt A/B 测试,测量准确率提升
  2. 实现样本量计算器,输入预期提升自动算出所需样本
  3. 建立回归测试集,每次发布前自动运行

🧪 随堂测验

点击你认为正确的选项。答错时会展示正确答案与原因解析。

1. A/B 测试中 alpha=0.05 表示什么?

2. 统计功效(power)通常设为多少?

3. LLM-as-judge 评估的优势是?

4. 回归测试集的主要用途是?