AI功能测试的"黄金数据集"构建指南:从0到1搭建质量评估体系
这是AI功能测试系列的第5篇,整个系列会更60篇
没有黄金数据集的 AI测试,就像没有标尺的裁缝------你永远不知道自己的衣服做得合不合身。
0. 写在前面:测试通过率下降了,用户却说更好用了
2024 年 6 月,我们团队给一个智能写作助手做质量保障。系统基于 GPT-4o,帮助用户写文章、邮件、报告。
上线前,测试团队跑了一遍回归测试------几百条测试用例,通过率 92%。上线。
一个月后,模型从 GPT-4 升级到 GPT-4o。测试团队跑了同样的测试,通过率从 92% 降到了 85%。
开发团队紧张了:"是不是升级出问题了?"
但用户反馈群里,大家说"新版本更好用了"、"回答更自然了"。
测试团队懵了。测试通过率下降了,但用户满意度上升了。到底哪个是对的?
后来逐条排查那 7% 的失败用例,发现根因:测试用例的设计偏向"格式合规"(JSON 格式、字数限制),但忽略了"内容质量"(准确性、相关性、有用性)。 GPT-4o 在格式合规上略逊于 GPT-4(因为更灵活),但在内容质量上明显更好。
测试用例的权重设计错了。
那次之后,我们彻底重构了测试体系,从"测试用例集合"升级为"黄金数据集"。每条用例不再只有一个预期输出,而是包含多个评判维度、每个维度有独立的阈值。
今天这篇文章,就是那次重构的完整复盘。
1. 概念讲解
黄金数据集(Golden Set)是 AI测试体系的核心基础设施。它是一组经过人工验证的高质量测试用例,每个用例包含输入、评判标准和通过阈值。每次模型更新、Prompt 调整、代码修改后,你跑一遍黄金数据集,就知道这次变更是"变强了"还是"变傻了"。
很多团队跳过这一步,直接开始写测试用例。结果就是:测试用例没有基线、回归测试没有标准、质量趋势没有参照。每次模型更新后,你只能说"好像变好了"或"好像变差了",但说不出具体好在哪、差在哪。
黄金数据集不是"测试用例的集合",而是质量标尺。它定义了"什么是好的回答",让你的测试有据可依。
1.5. 测试用例 vs 黄金数据集:8 个维度对比
光讲概念不够直观,下面这张表格把两者的核心差异列清楚。
| 对比维度 | 传统测试用例 | 黄金数据集 |
|---|---|---|
| 核心定位 | 验证功能是否正常工作 | 定义"什么是好的回答"的质量标尺 |
| 每条内容 | 输入 + 唯一预期输出 | 输入 + 多维度评判标准 + 阈值 |
| 通过判定 | 输出与预期一致则通过 | 各维度分别评分,全部达标才通过 |
| 覆盖方式 | 覆盖所有功能点 | 覆盖核心场景 + 高频意图 + 高风险场景 |
| 数量 | 越多越好(500-2000 条) | 少而精(150-500 条) |
| 维护频率 | 功能变更时更新 | 每月补充 + 每季度审查 + 每年重构 |
| 自动化程度 | 可 100% 自动化 | 硬性标准 100% 自动化 + 软性标准 60-80% |
| 回归用途 | 发现 Bug | 衡量质量趋势(变强了还是变傻了) |
关键结论: 黄金数据集不是"更好的测试用例",而是完全不同的质量基础设施。测试用例回答"功能对不对",黄金数据集回答"回答好不好"。
1.6. 黄金数据集的合理规模与构成
黄金数据集不是越大越好,也不是越小越好。下面是不同规模系统的经验值:
| 系统规模 | 用例数量 | 标注人员 | 构建周期 | 回归耗时 |
|---|---|---|---|---|
| 小型系统(单一场景,如简单问答) | 100-150 条 | 2-3 人 | 1-2 周 | 5-10 分钟 |
| 中型系统(多场景,如客服+写作) | 150-300 条 | 3-5 人 | 2-3 周 | 10-20 分钟 |
| 大型系统(全场景,如综合 AI 助手) | 300-500 条 | 5-8 人 | 3-4 周 | 20-40 分钟 |
黄金数据集的典型构成比例:
| 场景类型 | 占比 | 来源 | 说明 |
|---|---|---|---|
| 高频意图 | 40-50% | 生产日志分析 | 用户最常问的问题,覆盖 80% 的日常使用 |
| 高风险场景 | 20-30% | 历史投诉/差评 | 出错后果严重的场景(金融、医疗、法律) |
| 边缘场景 | 10-15% | 测试团队编写 | 低频但重要的场景(多轮对话、否定指令) |
| 竞品对比 | 5-10% | 竞品分析 | 竞品能做好但你做不好的场景 |
关键结论: 黄金数据集的构成应该从数据出发,不是从想象出发。40-50% 的用例应该来自生产日志,因为那才是用户真正在用的场景。
2. 核心方法论
黄金数据集的构建分五步:
- 用例收集。 从四个来源收集原始用例:生产日志(真实用户提问)、测试团队编写(覆盖边缘场景)、用户反馈(差评/投诉中的典型案例)、竞品分析(竞品能做好但你做不好的场景)。
- 人工标注。 为每个用例定义评判标准。不是写"预期输出",而是写"评判维度 + 阈值"。比如准确性 ≥ 0.85、格式合规 = 必须通过、相关性 ≥ 0.80。
- 基线测试。 用当前稳定版本跑一遍,确认用例有效。通过率应在 80-95% 之间。通过率 < 80% 说明用例可能不合理;通过率 > 95% 说明用例可能太简单。
- 纳入自动化。 将黄金数据集集成到 CI/CD 中,每次变更自动回归。回归失败自动告警。
- 定期维护。 每月补充新场景,每季度审查过时用例,每年全面重构。
用一张图来看五步构建流程:

五步不是线性的,而是闭环循环。定期维护阶段发现的问题会反馈到用例收集阶段,持续迭代优化。
3. 实战案例
场景:某智能写作助手的黄金数据集构建
前置条件: 系统是一个基于 GPT-4o 的智能写作助手,帮助用户写文章、邮件、报告。团队有 2 名测试工程师。
问题: 团队上线前用了几百条测试用例,但没有黄金数据集。上线后模型从 GPT-4 升级到 GPT-4o,测试团队跑了同样的测试,通过率从 92% 降到 85%。但用户反馈说"新版本更好用了"。测试团队困惑了------测试通过率下降了,但用户满意度上升了,到底哪个是对的?
根因分析: 测试用例的设计偏向"格式合规"(JSON 格式、字数限制),但忽略了"内容质量"(准确性、相关性、有用性)。GPT-4o 在格式合规上略逊于 GPT-4(因为更灵活),但在内容质量上明显更好。测试用例的权重设计错了。
解决方案:
- 从生产日志中抽取 200 条真实用户提问,覆盖写作、翻译、总结、代码四大场景。
- 邀请 3 名内容编辑对每条用例进行人工评分(1-5 分),评分维度包括:准确性、相关性、完整性、清晰度、有用性。
- 计算评分一致性(Kappa 系数),剔除 Kappa < 0.7 的用例(评分标准不一致)。
- 最终得到 150 条高质量用例,每条包含:输入、评判维度、阈值、优先级(P0/P1/P2)。
- 用新黄金数据集重新测试 GPT-4 → GPT-4o 升级,结果:格式合规从 92% 降到 88%,但内容质量从 3.8 升到 4.3,综合评分从 85 升到 89。和用户反馈一致。
4. 代码示例
下面是黄金数据集的结构定义和自动化回归核心代码。需要 openai 和 bert-score 依赖:
import json
import numpy as np
from typing import List, Dict
from openai import OpenAI
from bert_score import BERTScorer
client = OpenAI(api_key="your-api-key")
scorer = BERTScorer(lang="zh", rescale_with_baseline=True)
# ===<span class="wx-em-red"> 黄金数据集结构 </span>===
GOLDEN_CASE_SCHEMA = {
"id": "str", # 用例唯一标识
"category": "str", # 场景分类
"difficulty": "str", # 难度等级
"input": "str", # 测试输入
"criteria": { # 评判标准
"format_valid_json": { # 硬性标准:必须通过
"type": "hard",
"expected": True
},
"accuracy": { # 软性标准:评分 ≥ 阈值
"type": "soft",
"method": "llm_judge",
"threshold": 0.85
},
"relevance": {
"type": "soft",
"method": "bert_score",
"threshold": 0.80
}
},
"priority": "str", # P0(核心)/ P1(重要)/ P2(一般)
"tags": "list" # 标签,用于筛选
}
# ===<span class="wx-em-red"> 评判函数 </span>===
def verify_hard_criterion(response: str, criterion: Dict) -> float:
"""硬性标准验证:格式/类型/结构检查"""
if criterion.get("expected") is True:
try:
json.loads(response)
return 1.0 # 通过
except json.JSONDecodeError:
return 0.0 # 失败
return 1.0
def evaluate_soft_criterion(response: str, method: str, reference: str = None) -> float:
"""软性标准评分:LLM Judge 或 BERTScore"""
if method <span class="wx-em-red"> "bert_score" and reference:
_, _, F1 = scorer.score([response], [reference])
return F1.item()
elif method </span> "llm_judge":
# 简化版 LLM Judge
eval_prompt = f"""请评价以下 AI 回答的质量(0-1 分):
参考要点:{reference}
AI 回答:{response}
只返回一个数字,不要解释。"""
result = client.chat.completions.create(
model="gpt-4o",
messages=[{"role": "user", "content": eval_prompt}],
temperature=0,
)
try:
return float(result.choices[0].message.content.strip())
except ValueError:
return 0.5 # 解析失败,给中间分
# ===<span class="wx-em-red"> 黄金数据集回归测试 </span>===
def run_golden_regression(golden_set: List[Dict]) -> Dict:
"""
黄金数据集回归测试
流程:
1. 遍历每条用例
2. 调用 AI 系统获得回答
3. 按评判标准评分
4. 统计通过率、各维度得分
"""
results = {
"passed": 0, "failed": 0,
"p0_passed": 0, "p0_failed": 0,
"dimension_scores": {},
"details": []
}
for case in golden_set:
# 调用 AI 系统
response = client.chat.completions.create(
model="gpt-4o",
messages=[{"role": "user", "content": case["input"]}],
temperature=0.3,
).choices[0].message.content
# 逐维度评分
case_results = {}
all_pass = True
for criterion_name, criterion in case["criteria"].items():
if criterion["type"] <span class="wx-em-red"> "hard":
score = verify_hard_criterion(response, criterion)
else:
score = evaluate_soft_criterion(
response, criterion["method"], case.get("reference")
)
case_results[criterion_name] = score
if score < criterion.get("threshold", 0):
all_pass = False
# 记录维度分数
for dim, score in case_results.items():
if dim not in results["dimension_scores"]:
results["dimension_scores"][dim] = []
results["dimension_scores"][dim].append(score)
if all_pass:
results["passed"] += 1
else:
results["failed"] += 1
# P0 用例单独统计
if case.get("priority") </span> "P0":
if all_pass:
results["p0_passed"] += 1
else:
results["p0_failed"] += 1
results["details"].append({
"case_id": case["id"],
"passed": all_pass,
"scores": case_results,
})
# 计算通过率
results["pass_rate"] = results["passed"] / len(golden_set) if golden_set else 0
# 计算各维度平均分
results["dimension_avg"] = {
dim: np.mean(scores) for dim, scores in results["dimension_scores"].items()
}
return results
# ===<span class="wx-em-red"> 使用示例 </span>===
if __name__ == "__main__":
# 示例黄金数据集(实际应从 JSON 文件加载)
golden_set = [
{
"id": "GS001",
"category": "写作",
"difficulty": "中等",
"input": "帮我写一封拒绝邀请的邮件",
"reference": "礼貌拒绝、表达感谢、说明原因、保持关系",
"criteria": {
"format_valid_json": {"type": "hard", "expected": False},
"accuracy": {"type": "soft", "method": "bert_score", "threshold": 0.80},
"relevance": {"type": "soft", "method": "llm_judge", "threshold": 0.85}
},
"priority": "P0",
"tags": ["写作", "邮件"],
},
]
results = run_golden_regression(golden_set)
print(f"通过率: {results['pass_rate']:.1%}")
print(f"P0 通过率: {results['p0_passed']}/{results['p0_passed'] + results['p0_failed']}")
print(f"各维度平均分: {results['dimension_avg']}")
代码说明:
- 硬性标准:格式/类型/结构检查,精确验证,通过=1.0,失败=0.0
- 软性标准:LLM Judge 或 BERTScore 评分,0-1 之间,与阈值比较
- P0 用例单独统计,P0 失败直接阻断发布
- 返回各维度平均分,用于衡量质量趋势
5. 注意事项和常见坑
- 黄金数据集不是越大越好。 150-500 条是经验值。太少(<50)覆盖不够,太多(>1000)维护成本高、回归时间长。关键是质量,不是数量。
- 评判标准要具体,不能模糊。 别写"回答要好"这种标准。要写"准确性 ≥ 0.85"、"必须包含关键词 XXX"、"JSON 格式必须合法"。模糊的标准 = 无法自动化。
- P0 用例必须 100% 通过。 P0 是核心功能,任何失败都阻断发布。P1 允许 95% 通过,P2 允许 90% 通过。但 P0 不行。
- 定期去重。 黄金数据集中经常有语义重复的用例(比如"北京人口多少?"和"北京有多少人口?")。每季度审查一次,合并重复用例,保持数据集精简。
- 标注一致性很重要。 多人标注时,计算 Kappa 系数(>0.7 才算一致)。如果标注标准不一致,黄金数据集本身就是错的。
- 别用生产数据直接当黄金数据。 生产数据需要人工审核和标注后才能加入黄金数据集。直接拿生产日志当测试标准,等于让 AI 自己评判自己。
- 黄金数据集要版本化。 每次更新黄金数据集都要打版本标签(v1.0、v1.1)。模型升级时用最新版本回归,历史版本保留用于对比分析。
5.5. 常用工具一览
| 工具 | 用途 | 适用场景 |
|---|---|---|
| bert-score | 语义相似度评分 | 准确性/相关性软性标准评估 |
| openai | LLM API 客户端 | LLM Judge 评分、模型回答生成 |
| scikit-learn | 统计工具 | 计算 Kappa 系数(标注一致性) |
| pytest | 测试框架 | 将黄金数据集集成到 pytest 测试套件 |
| JSON Schema | 数据验证 | 硬性标准验证(格式/类型/结构) |
| MLflow | 实验追踪 | 记录每次回归结果,绘制质量趋势图 |
工具选择原则:语义评估用 BERTScore,标注一致性用 Kappa,趋势追踪用 MLflow。把贵的资源用在刀刃上。
6. 总结与思考
黄金数据集不是测试用例的集合,而是质量标尺。它定义了"什么是好的回答",让你的测试有据可依、回归有尺可量。
【思考题】 你的团队有黄金数据集吗?如果有,大概多少条?维护频率是怎样的?
关键词: 黄金数据集、AI测试、质量评估、LLM测试、测试框架、BERTScore、LLM Judge、回归测试