你更新了一个提示词,或者某个提供商在同一个模型 ID 下推出了新的检查点,然后生产环境里出现了回归。最后一周的用户投诉看起来和上一周略有不同。像MMLU这样的公开基准测试抓不到这种情况。它衡量的是跨学科的一般能力,而不是模型如何处理你产品的流量。

黄金评估数据集填补了这一空白。它是一组经过整理的、生产输入与经过审核的预期输出配对的数据集,在 Git 中进行版本管理,并在每次部署前运行。它回答了基准测试无法回答的问题:这个改动对你所服务的流量是有帮助还是有损害?

本指南涵盖什么是黄金集、为什么生产数据比合成数据是更好的基础,以及从实时流量构建黄金集的五步流程。它还涵盖如何通过一个 API 对多个候选模型运行同一数据集,这样你就可以依据来自自己流量的证据来选择下一个模型,而不是依据排行榜排名。

简要总结

  • 黄金评估数据集是一组经过整理的、生产输入与经过审核的预期输出配对的数据集,用作每次重要改动前的回归测试。
  • 一旦数据集存在,你就可以通过一个 API 在多个模型上运行它,并依据来自自己流量的证据来选择下一个模型。
  • 让规模与任务相匹配。大约 10 条用于探究单个问题,100 到 1,000 条用于完整的回归集。合适的规模取决于指标、方差,以及你需要检测出的最小差异。
  • 对失败模式的覆盖比数量更重要。生产示例承载的是你的用户实际遇到的失败。合成示例承载的是某人想象出来的失败。
  • 将数据集、评分标准和基线一起进行版本管理。否则你无法判断回归是来自模型改动、提示词改动,还是数据集改动。

什么是黄金评估数据集

黄金评估数据集是一组生产示例,配有经过审核的预期输出,从训练中留出,并在每个发布候选版本上进行评估。具有领域知识的人确认了每个预期输出,或者判定该条目不需要预期输出,因为像格式、安全性或语气这样的无参考检查就是通过标准。

正是这种审查让这个集合变得有用。没有它,当分数下降出现时,你无法判断是这次变更导致了质量回退,还是第 23 条标注错了。

把它当作针对行为而非代码的回归测试套件。它在 CI 中运行,有已知正确的预期输出,并能捕捉部署之间的漂移。与单元测试的区别在于,预期输出是一种判断,因此测试框架要做的不仅仅是比较字符串。

黄金集不是基准测试、训练集或 A/B 测试。基准测试衡量通用能力。黄金集衡量在你的流量上的能力。训练集教会模型。黄金集在模型回退时捕捉到它。A/B 测试衡量真实用户结果。黄金集衡量一次变更是否足够安全,以至于可以对其运行 A/B 测试。

为什么生产数据是更好的基础

合成评估测试的是模型回答某人想象中用户可能会问的问题的能力。你需要测试的是你的用户实际问过的问题,以及他们使用的措辞。生产流量是更好的基础,原因有三。

分布是对的。如果你的流量中有 70% 是关于定价的,那么你的评估集中就应有 70% 是关于定价的。从想象的边缘案例中抽取的合成集不会保留这种分布。保留观察到的分布,才能让回归分数反映用户影响。

失败模式正是你所关心的那些。用户会找到你想不到要写的失败模式。一条混合了三种语言的消息、一张粘贴了整段错误日志的工单、一个引用了你上个季度重命名过的产品功能的查询。除非有人想到这些,否则合成集不会包含它们。

这些示例会跟随你的产品变化。一个六个月前上线时处理密码重置的支持机器人,现在会收到多账户问题、退款升级请求,以及竞争对手 UI 的截图。从生产数据中抽取的黄金集会跟踪这种漂移。而在上线时冻结的合成集不会。

合成样本仍有其位置,但应作为扩展而非基础。用它们来填补你真实样本过少的已知失败模式的覆盖缺口。你可以通过一次模型调用来生成它们,该调用使用结构化输出使每个样本都符合你的数据集 schema,或者通过改写现有条目来生成。在元数据中将它们标记为合成样本,并让它们只占集合中的少数。

五步流程

Diagram of the five-step process: pull production traffic, deduplicate and cluster, add expected outputs, run a first evaluation, then commit to Git and wire into CI, with the resulting frozen set used to benchmark across candidate models through one API

第 1 步:抽取生产流量样本

从一到两周的已记录输入和输出开始。如果该功能具有季节性或使用量稀少,则使用更长的时间窗口。

先随机抽样,然后看看抽出了什么。如果你的流量有长尾,随机样本会过度代表头部。这通常正是你想要的,因为头部的回归会影响最多用户,但要检查稀有且关键的意图是否完全没有出现。

记录每一个你之后可能想要切分的字段。这包括意图、功能区域、用户分群、时间戳,以及生成你所抽样响应的模型和提示词版本。你之后无法重建这些元数据。

抽样生产流量意味着抽样用户数据。检查你的服务条款,在数据到达评估框架之前运行你的 PII 清洗流水线,并记录你转换了哪些样本,以便之后能复现清洗过程。

目标是在进入第 2 步时拥有几百条样本的原始池。你会大幅裁剪。

第 2 步:去重和聚类

生产流量是重复的。一个客服聊天机器人可能每天看到上百次措辞略有不同的同一个“我如何重置密码”问题。你希望集合中有其中一个,而不是五十个。

精确匹配去重会漏掉改写。要捕捉它们,检查数据集是否已经覆盖了相同意图,可以通过精确匹配归一化输入,或通过比较嵌入相似度来比较输入之间。

一旦你有了去重后的池,就在其中抽样以覆盖。如果你的流量有一半是某个意图,该意图应大致占黄金集的一半,但不是全部。一个代表性不足但失败严重的意图,代价高于一个代表性充分但失败温和的意图,因此要向那些你承受不起的失败模式加权。

对于目标规模,Langfuse 的指南是一个有用的起点。把这些数字当作参考点,再根据你的流量和评估目标进行调整。

用途典型规模
探索单个问题约 10 条
测试模型能力边界约 10 个复杂的未解决示例
对较大改动做 CI 检查100 到 1,000 条,覆盖生产分布
用对抗性提示词做护栏测试规模大且持续增长,出现新案例就补充

为 pull request 门禁保留几十到低百条的子集,以保持快速,把完整集合留给发布分支或夜间运行。

第 3 步:添加期望输出

对于每个输入,都需要有资质的人写下正确的输出应该是什么样。有时那是一个字符串。更多时候是一份评分标准。哪些事实必须出现,哪些主张不能提出,以及需要什么语气或格式。

两种评分标准选择能让评分更一致。使用二元标准,每条标准要么 MET 要么 UNMET。使用分析式评分标准,对每条标准单独评分,而不是使用给出一个总体评分的整体式评分标准。分析式评分标准会告诉你哪里变差了,而不只是是否变差了。

让两个人独立标注一个子集。在他们意见不一致的地方,把评分标准当作问题所在,而不是标注者。修正评分标准,直到两位评审对同一个模型输出给出相同的评分。

并非每个条目都需要硬编码的期望输出。仅由无参考评估器检查的条目,例如格式有效性、安全性或语气,完全不需要期望输出。一个黄金集可以混合这两种类型。

第 4 步:运行首次评估并修正评分标准

在你信任这个集合之前,用你当前的生产模型跑一遍,并查看每一个失败。

把失败分成三类。真实失败,即模型答错了而期望输出是对的。保留这些。评分标准问题,即模型给出了合理答案,而你的期望输出没有预料到。修正期望输出。任何合理评分标准都无法评分的模糊示例。删掉它们。

这一轮预计会砍掉一部分集合。砍多少取决于你在第 3 步写预期输出时有多仔细。砍掉正是这一轮的意义所在。一个无论模型怎么变都产生相同通过率的黄金集,等于什么都没测。

不要跳过这一步。你在这里没发现的评分标准问题,之后会在真实部署中作为假回归冒出来。

第 5 步:提交到 Git,接入 CI,迭代

黄金集应该放在你的代码库里,与提示词和运行它的代码一起做版本管理。这样才能把它从一次性的质量检查变成回归测试。

一个最小的目录结构长这样。

/evals
/golden
dataset.jsonl        # 每行一个示例
rubric.md            # 如何评分
run.ts               # 加载器和测试框架
baseline.json        # 当前生产模型上的通过率
/synthetic
dataset.jsonl        # 罕见失败模式,标记为 synthetic: true

把每个示例存为一行 JSON,字段包括输入、预期输出、标签和元数据。把评分标准存为一份人类可读的文档,供评分器读取,这样任何看 pull request diff 的人都能看出通过率的变化是不是由评分标准改动引起的。

中的一行dataset.jsonl长这样。

{"id":"pw-reset-locked-account","input":"i cant log in, tried resetting three times and its saying account locked. help","expected":"The bot should acknowledge the lockout, ask for the account email, and route to the account-recovery flow. It must not offer to reset the password directly.","rubric":"must_ask_email;must_route_recovery;must_not_reset_directly","tags":["password_reset","edge_case"],"synthetic":false}

这些字段服务于不同的读者。id为每个条目提供一个稳定的引用,能在 Git diff 中保持不变。input是生产输入,清洗后逐字保留。expected是供 LLM 评判器阅读的人类可读散文。rubric保存评判器据以评分的机器可检查标准。tags让你能按意图切分通过率。synthetic在聚合指标中把真实条目和合成条目分开。

把测试框架接入 CI,在每次提示词改动或模型更换时运行。如果通过率下降超过既定阈值就让构建失败,任何更小的下降都要求书面确认。一个GitHub Actions每次 pull request 都调用你的 harness 的工作流,就足以开始了。

按固定节奏刷新数据集。每季度审查一次超过六个月的条目,逐条对照当前产品行为,是一个合理的默认做法。如果产品变化很快,就每月审查一次。过时的黄金数据集最终会不再能预测生产行为。

将评分标准与数据一起做版本管理。一次失败的运行只有在你能够诊断它时才有用,而评分标准的变更是最难事后追溯的一类变更。如果有人为了修复一个看起来像误报的回归而修改了某条标准,那次修改应该作为 pull request diff 中可见的变更出现,而不是在六周后表现为一个无法解释的通过率变动。

不要因为条目一直通过就将其退役。一个通过的条目正在证明该行为依然成立。只有当它所测试的行为在产品中已不复存在,或者预期输出现在已错误时,才退役该条目。

通过一个 API 进行跨模型基准测试

一旦你有了 100 个带预期输出的示例,用同一套数据跑另一个模型,就能告诉你该模型在你的工作负载上表现如何。不是在 MMLU 上,也不是在别人的编码 harness 上。而是在你的用户提出的问题上。

跨厂商比较模型通常意味着每个候选模型都有不同的 SDK、不同的认证、不同的响应结构和不同的速率限制。通过OpenRouter,同一个 OpenAI 兼容的请求体可以在模型目录中通用。对于共享同一接口并支持你的请求所用参数的候选模型,你可以通过把model: "openai/gpt-5.1"改成model: "anthropic/claude-fable-5.1"并重新运行 harness 来比较它们。当模型在上下文长度、工具支持或支持的参数上存在差异时,预计每个候选模型都需要一些集成工作。models 端点中的每个条目都会列出该模型的上下文长度和一个supported_parameters数组,因此你可以在切换前先检查。

一个最小化的 harness 看起来是这样的。

importOpenAIfrom"openai";
import{ readFileSync }from"fs";

constclient=newOpenAI({
apiKey: process.env.OPENROUTER_API_KEY??"",
baseURL:"https://openrouter.ai/api/v1",
});

constdataset=readFileSync("./evals/golden/dataset.jsonl","utf-8")
.trim()
.split("\n")
.map((line)=>JSON.parse(line));

constmodelsToTest=[
"openai/gpt-5.1",
"anthropic/claude-fable-5.1",
"google/gemini-3.8-flash",
];

constresults:Record<string,{pass:number;total:number}>={};

for(constmodelofmodelsToTest) {
results[model]={ pass:0,total:0};

for(constexampleofdataset) {
constresponse=awaitclient.chat.completions.create({
model,
messages: [{ role:"user",content: example.input }],
});

constoutput=response.choices[0].message.content??"";
constpassed=grade(output, example.expected, example.rubric);

results[model].total+=1;
if(passed) results[model].pass+=1;
}
}

console.table(results);

这个grade函数就是你的评分标准所在之处。对于字符串匹配的情况,它可以是output.includes(expected)。对于基于评分标准的评分,通常是再调用一次强评判模型,传入评分标准和回答,询问该回答是否满足每一条标准。

发给评判模型的评分标准检查提示词如下所示。

评分标准条目(每条为 MET 或 UNMET):
{criteria}

待评分的回答:
{response}

对每一条标准,在单独一行输出 MET 或 UNMET。不要写任何说明文字。

评判模型为每条标准返回一个 MET 或 UNMET,grade()如果所有标准均为 MET 则解析为通过,否则为不通过。二元标准加上“不要写说明文字”的指令,能让评判模型的输出足够一致,从而在多次运行间都可解析。

对于设计良好、采用二元标准并带有锚点示例的评分标准,LLM 评判模型足以可靠地用于回归检测。与人工评分者的一致程度因任务、评分标准和评判模型而异,因此在依赖其评分之前,应先用一组人工标注的示例校准评判模型。评判模型的可靠性甚至仅因随机种子不同而变化。一项研究让三个 LLM 评判模型分别对 BIG-Bench Hard 问题的回答评分 100 次,除种子外不改变任何东西,测得各评判模型之间的评分者间信度在多次重复中从 0.167 到 1.00 不等。在该尺度上,1.0 表示完全一致。使用与被测模型不同的模型作为评判模型,以避免自我偏好偏差,并且不要在高风险决策中将单次评判运行结果当作真实标准。

在每一个模型上对同一组数据评分。如果你在两次运行之间更改数据集,你就是在同时测量两样东西。冻结数据集,记录 commit hash,然后再更换模型。

在关注质量的同时也要关注成本。在你的黄金集上最准确的模型,每次调用的成本可能是第二准确模型的 30 倍,而质量差距只有两个百分点。把这个权衡呈现给负责预算的人。我们的模型对比页面会在你运行完整基准测试之前展示两个模型之间的价格差距。

当你需要可复现性时,使用提供商路由控制。同一个模型 slug 可能路由到不同的提供商,而提供商之间的行为可能略有差异。要将一次基准测试运行固定到某个提供商,请将order字段在provider对象中设置为该提供商的 slug,并将allow_fallbacks设置为false。仅使用order时,如果列出的提供商不可用,我们仍会回退到其他提供商。

结论

黄金评估数据集能告诉你某项变更对你所服务的流量是有帮助还是有害。用生产数据构建它,审查每一个预期输出,将数据集连同其评分标准和基线一起做版本管理,并在每次部署时运行它。

一旦你拥有了它,通过一个 API 在众多模型上运行同一数据集就是回报所在。对于兼容的候选模型,对比就变成了更改模型标识符并重新运行,同时对每个候选模型检查上下文长度、工具支持以及支持的参数。

常见问题

黄金评估数据集应该有多大?

从 20 到 50 个经过审查、覆盖你最重要失败模式的条目开始。逐步扩展到 100 到 1,000 个条目作为完整的回归集,并保留一个较小的子集用于每次 PR 检查。合适的规模取决于指标、流量分布,以及你需要检测出的最小质量差异。对失败模式的覆盖比原始数量更重要。

我应该使用生产数据还是合成数据?

以生产数据为基础,用合成样本填补已知的覆盖缺口。真实流量保留了你的用户所产生的分布和失败模式。针对你真实样本过少的罕见失败模式添加合成用例,在元数据中将其标记为合成,并让它们只占数据集的一小部分。

我应该多久刷新一次黄金集?

每季度审查一次集合,当提示词、模型或产品行为快速变化时更频繁地审查。对照当前行为检查旧条目,添加新观察到的失败模式,仅退役那些行为在产品中已不复存在的条目。保留先前版本,以便复现历史回归。

我可以使用 LLM 作为评判者吗?

可以,但有两个条件。评分标准必须足够具体,以至于两个人类对同一输出评分时会给出相同结果,并且你必须在依赖其评分之前,用人工标注的示例校准评判者。对于高风险决策,避免单次运行的判断,并且不要使用被测模型作为它自己的评判者。

黄金集和基准测试有什么区别?

基准测试针对公开测试衡量模型的通用能力。黄金集衡量模型在你自己的流量上的能力。基准测试帮助你缩小模型候选名单。黄金集告诉你候选名单中哪些模型适合你的产品。两者不能互相替代。

参考文献