构建黄金数据集:让评测从感觉变成证据
“这个模型感觉比上一个好。”这是 AI 项目里最常听到、也最不可靠的一句话。开发者拿几个熟悉的问题试一下,回答顺了,就觉得优化成功;换一批用户问题,模型可能已经开始漏字段、乱引用,或者在没有答案时编造内容。
我很长时间也没有认真对待评测数据。大家把线上问题临时复制到一个表格里,谁有空谁补一个答案,过几个月表格里有几百行,却没人知道哪些题真的有代表性,哪些答案已经过时,更不知道一次 Prompt 修改到底改善了什么。
后来我才明白,黄金数据集不是“多收集一些题目”,而是给系统建立一把稳定的尺子。尺子不一定完美,但必须有刻度、有版本、有使用规则。否则每次评测都换一把尺子,结果再漂亮也没有可比性。
一、先定义数据集要回答什么问题
一个数据集不可能同时完美衡量所有能力。先写清楚它服务的任务:
type DatasetPurpose = {
name: string
system: 'chat' | 'rag' | 'agent' | 'extraction'
targetBehavior: string[]
excludedBehavior: string[]
decisionItSupports: string
}
例如“客服 RAG 黄金集”要回答的是:系统能否根据授权知识回答常见问题,能否在没有证据时拒答,能否正确引用来源。它不需要拿来衡量诗歌创作,也不能被一句总体满意度替代。
目标越具体,样本和指标越容易设计。否则你会得到一套什么都有、却没有任何一项测得可靠的数据。
二、样本要覆盖真实分布和危险边界
黄金集不能只收集最常见的问题,也不能只收集最刁钻的问题。可以按几类组织:
- 高频任务:真实流量里最常出现的问题。
- 长尾任务:少见但业务重要的表达。
- 边界任务:相似意图、缺字段、歧义和冲突信息。
- 无答案任务:知识库没有足够证据的问题。
- 对抗任务:Prompt Injection、越权和恶意输入。
- 回归任务:历史上已经修复过的线上错误。
type GoldenCase = {
id: string
category: string
input: string
context?: string
expected: string
evidence?: string[]
forbidden?: string[]
risk: 'low' | 'medium' | 'high'
source: 'production' | 'expert' | 'synthetic' | 'adversarial'
}
真实流量能反映用户怎么说话,但必须脱敏和授权;专家编写的样本能覆盖边界,但可能过于整洁;合成样本可以快速扩充,却可能继承生成模型的表达偏差。几种来源混合,才能既像真实用户,又能主动测试危险情况。
三、期望答案要写成可判断的标准
“回答正确”太模糊。对于事实问题,写出核心事实和允许的表达范围;对于 RAG,写出必须引用的片段;对于 Agent,写出允许调用的工具和最终状态;对于抽取,写出字段、类型和必填条件。
type AcceptanceCriteria = {
requiredFacts?: string[]
requiredEvidenceIds?: string[]
requiredToolCalls?: string[]
forbiddenClaims?: string[]
schema?: string
humanReviewRequired?: boolean
}
一个好标准不是要求模型说出唯一答案,而是定义什么不能错、什么可以有多种说法。允许范围写得太窄,评测会把正确答案误判为失败;写得太宽,又会让错误答案轻易通过。
四、标注需要规则,也需要人
标注前先写标注指南,包含概念定义、正反例、边界情况和冲突处理顺序。比如“退款”和“售后”在业务里可能很接近,如果不事先约定,两个标注员会根据自己的理解给出不同答案。
同一批关键样本最好由至少两个人独立标注,再计算一致性并讨论分歧:
问题 → 标注员 A:退款
→ 标注员 B:售后
→ 进入复核,更新标注指南
分歧不是噪音,它经常暴露了产品规则本身不清楚。不要简单地投票取多数,把少数意见丢掉;应该记录为什么分歧、最终采用什么规则,以及未来遇到同类问题如何处理。
自动标注可以降低成本,但高风险样本和关键测试集不能全部依赖模型。模型给出的答案需要人工抽查,尤其要关注它是否把自己的幻觉写进了标准答案。
五、时间切分和客户切分防止污染
黄金集最怕“看起来很高,实际上提前看过答案”。如果训练数据、Prompt 优化数据和测试数据包含相同或高度相似的内容,模型会记住样本而不是学会能力。
可以按时间、客户、文档来源或任务模板做隔离。线上新问题优先进入候选区,经过一段时间和人工确认后再进入正式回归集;不要因为某个版本在候选题上表现好,就立刻把它当作独立测试。
原始样本 → 脱敏 → 候选集 → 标注复核 → 冻结测试集
↓
仅在版本发布时使用
正式测试集的访问要受限,答案不能随意暴露给所有 Prompt 调试流程。数据集越重要,越要防止它被优化过程反复“看见”。
六、版本化和变更记录不能省
黄金集会变化:产品规则更新,知识库过期,新增线上错误,旧样本失去意义。每次变化都要有版本、原因和影响范围:
type DatasetRevision = {
version: string
parentVersion?: string
added: string[]
changed: string[]
removed: string[]
reason: string
reviewedBy: string[]
createdAt: string
}
不要为了让新模型通过而随意删除失败题。先判断是标准错了、数据过时了,还是模型确实失败;如果删除,必须保留原因。数据集也应该像代码一样走审查,避免一个人悄悄改变评测标准。
七、指标要和数据集结构对应
总体准确率只是一个起点。按类别、风险、来源和语言切片,观察关键子集:
总体有效率:94%
高风险样本:98%
无答案拒答:91%
多跳问题:83%
历史回归集:100%
如果总体分数上升,但多跳问题下降,团队应该知道;如果高风险样本变差,不能被平均数掩盖。小样本切片还要注意统计波动,报告数量和置信区间,避免把三道题的变化当成确定结论。
对生成任务,可以结合规则、语义评审和人工复核。自动评审适合扩大覆盖,人工复核适合校准标准和发现评审偏差。评测模型也要有版本,不能换了裁判却继续比较旧分数。
八、黄金集要服务模型、RAG 和 Agent
同一套基础样本可以扩展成不同测试:
- 模型测试:答案事实和格式是否正确。
- RAG 测试:是否召回正确证据,是否引用过期或越权内容。
- Agent 测试:工具顺序、参数、幂等和最终状态是否正确。
- 系统测试:延迟、成本、失败重试和降级是否符合预算。
type CaseResult = {
caseId: string
modelVersion: string
promptVersion: string
retrievalVersion?: string
toolTrace?: string[]
quality: number
latencyMs: number
cost: number
passed: boolean
}
这样,某个版本失败时,不会只得到“答案不好”,而能知道是模型、检索、工具还是系统约束出了问题。
九、数据集不是建完就结束
定期回顾数据集中的通过率。如果所有版本都接近 100%,可能说明样本太容易;如果失败样本从不被修复,可能说明标准不可执行。黄金集要随着真实系统变化,但每次变化都要留下证据。
可以设一个数据集健康检查:样本类别是否失衡,重复率是否上升,过期文档是否仍在引用,人工分歧是否集中在某个标签,线上高频失败是否还没有进入回归集。
总结:黄金数据集是一种工程记忆
我现在把黄金数据集看成团队给系统留下的错题本。它记录的不只是“正确答案”,还记录我们曾经在哪里失败,为什么认为某种行为不可接受,以及下一次改动必须守住什么。
一套好的黄金集不追求数量最多,而追求问题明确、证据可靠、边界完整、版本可追溯。它让模型升级、Prompt 修改、RAG 调整和 Agent 重构拥有共同的比较基线。
当有人说“这次感觉更好了”,你可以拿出数据问:哪类问题更好了?代价是什么?有没有新的失败?当有人说“这个版本已经可以上线”,你可以继续问:高风险样本通过了吗?无答案时会拒答吗?工具会不会重复执行?
这就是黄金数据集的意义。它不是为了给团队增加一张报表,而是把模糊的争论变成可以复核的证据。AI 系统会不断变化,只有把过去的失败认真保存下来,下一次进步才不会只是又一次感觉。