DanLevy.net

评估制恶!

基准测试衡量基准。你的系统需要自己的度量。

每一款新模型登场时,都披着一身基准测试的燕尾服。

MMLU:92.4%。HumanEval:87.2%。LLeMU:88.7%。MATH:73.6%。AGI:127%!

然而,对于99%用AI构建流程与产品的企业来说,这些统统无关紧要。

真正重要的是什么?你的工作负载表现如何?是在变好还是变差?唯一靠谱的衡量方式,就是编写能反映你系统具体任务、数据和失败模式的Evals(LLM测试)。

基准测试没有撒谎,它们只是在回答别人的问题。


“凭感觉评估”的实际代价

标准做法:上线一个模型变更,盯着投诉渠道,如果抱怨声变大就回滚。

这种做法几乎漏掉了所有关键信息:

你只能捕捉到明显的失败。 那些得到自信错误答案却没意识到的用户?静默。那些得到更差答案后直接放弃功能的用户?静默。支持工单和错误率只能捕获质量退化的一小部分。

你无法区分退化与改进。 如果新模型在任务A上更好,在任务B上更差,那么关于B的抱怨看起来和笼统的“AI变差了”的反馈一模一样。你根本不知道要修复什么。

你在把用户当成测试基础设施。 他们可没同意过这个。


评估光谱(以及大多数团队错在哪里)

评估方法分布在一个从“快速但脆弱”到“昂贵但有效”的光谱上。

一张光谱图,比较确定性检查、LLM作为评判者和人工评估在速度、成本和有效性上的差异。

使用能诚实捕捉失败的最廉价评估方法。

LLM作为评判者 是当前的热门:让一个强大的模型去给另一个模型的输出打分。快速、可扩展、廉价。问题在于:它内嵌了评判模型的偏见,可以被操纵,并且形成了循环依赖。如果你用GPT-5来给GPT-5的输出打分,你实际上是在测量“GPT-5与GPT-5的共识程度”。这并非毫无意义,但绝不是你以为的那样。

人工评估 是每个人都想跳过的黄金标准。让人来评估输出既昂贵又缓慢,不同评估者之间不一致,而且安排起来很烦人。但它是唯一能验证你的系统对真实人类是否有用的方法。

任务特定的自动化检查是大多数团队应该投入更多时间的地方。它们并不光鲜,但快速、确定性强,并且与系统中真正重要的东西紧密相关。


真正有效的方法

1. 在发布前定义失败

在更改模型或提示词之前,写下什么样子算是糟糕。要具体。

不是“输出应该准确”。那不是测试。更像是:

你可以通过编程方式检查这些。不需要评判模型。

评估框架:确定性检查

type EvalResult = { passed: boolean; reason?: string };
const evals: Record<string, (output: string, context: EvalContext) => EvalResult> = {
// JSON must parse
validJson: (output) => {
try {
JSON.parse(output);
return { passed: true };
} catch (e) {
return { passed: false, reason: `Invalid JSON: ${e.message}` };
}
},
// No hallucinated citations — every claim must appear in context
groundedCitations: (output, { retrievedChunks }) => {
const claims = extractCitations(output);
const ungrounded = claims.filter(
(claim) => !retrievedChunks.some((chunk) => chunk.includes(claim))
);
return ungrounded.length === 0
? { passed: true }
: { passed: false, reason: `Ungrounded claims: ${ungrounded.join(', ')}` };
},
// Response length sanity check — catch truncation or runaway generation
reasonableLength: (output) => {
const words = output.split(/\s+/).length;
return words >= 10 && words <= 2000
? { passed: true }
: { passed: false, reason: `Word count ${words} out of bounds` };
},
};

2. 从你最糟糕的日子构建黄金测试集

你最好的评估数据是那些令人尴尬的东西:那些导致有人提交工单、截图幻觉、或者悄悄停止使用功能的输出。

每当用户报告糟糕的输出、标记幻觉,或者你手动注意到失败时,将其添加到你的黄金测试集中:输入、上下文和正确行为。保留50-100个案例,并在每次模型更改时运行它们。

起初这感觉很手动。六个月后,你就拥有了一套任何公开基准都无法作弊的测试套件,因为每个案例都来自你自己的失败历史。

A workflow diagram showing how bad production incidents become golden cases, then CI eval runs, then blocked regressions or approved releases.

黄金测试集将令人尴尬的东西变成回归测试套件。

黄金案例结构

interface GoldenCase {
id: string;
input: string;
context: Record<string, unknown>;
expectedBehavior: {
mustContain?: string[];
mustNotContain?: string[];
structureCheck?: (output: string) => boolean;
minSimilarityToReference?: number; // cosine similarity to a reference answer
};
sourceIncident?: string; // link back to the bug report or ticket
}

3. 回归测试,而不仅仅是验收测试

大多数团队只在考虑模型更改时才运行评估。那是验收测试:“这个新东西够好吗?”

你还需要回归测试:“这是否破坏了之前能用的东西?”

对你的黄金测试集运行每一次提示词变更,而不仅仅是模型变更。一个原本运行良好的提示词,在你添加新工具、更改 RAG 检索策略或更新上下文模板时,可能会悄然退化。没有基线,你无从知晓。像 Langfuse 这样的工具会将评估分数附加到生产追踪上,这样退化就会显示在仪表盘中,而不仅仅出现在事件报告中。

评估框架:基线 vs 候选对比
async function compareModelVersions(
goldenCases: GoldenCase[],
baselinePipeline: Pipeline,
candidatePipeline: Pipeline
) {
const results = await Promise.all(
goldenCases.map(async (tc) => {
const [baseline, candidate] = await Promise.all([
baselinePipeline.run(tc.input, tc.context),
candidatePipeline.run(tc.input, tc.context),
]);
return {
id: tc.id,
baselinePassed: runEvals(baseline, tc.expectedBehavior),
candidatePassed: runEvals(candidate, tc.expectedBehavior),
regression: /* baseline passed */ && /* candidate failed */,
improvement: /* baseline failed */ && /* candidate passed */,
};
})
);
const regressions = results.filter((r) => r.regression);
const improvements = results.filter((r) => r.improvement);
console.log(`Regressions: ${regressions.length} / ${goldenCases.length}`);
console.log(`Improvements: ${improvements.length} / ${goldenCases.length}`);
if (regressions.length > 0) {
console.error('Blocking regressions found:');
regressions.forEach((r) => console.error(` - ${r.id}`));
}
return { regressions, improvements };
}

如果候选版本在已知的失败案例上出现退化,那么升级讨论就会变得非常具体:哪些案例改进了,哪些案例出问题了,以及这个权衡是否值得。

4. 将 LLM 作为评判者只用于一件事

LLM 作为评判者适用于没有确定性正确答案的开放式输出:“这个回复有帮助吗?”、“这个摘要保留了关键点吗?”、“这个解释对初学者来说合适吗?”

用它来做这些事。不要用它来做确定性答案。当你使用它时,请明确评分标准:

评估框架:基于评分标准的评判者

async function judgeHelpfulness(
userQuery: string,
modelResponse: string
): Promise<{ score: number; reasoning: string }> {
const judgePrompt = `
You are evaluating a customer support response.
User question: ${userQuery}
Response: ${modelResponse}
Rate the response on a scale of 1-5:
5 = Directly answers the question with accurate, actionable information
4 = Answers the question but could be more specific or actionable
3 = Partially addresses the question; key information is missing
2 = Tangentially related but doesn't answer the question
1 = Off-topic, factually wrong, or harmful
Respond with JSON: {"score": <number>, "reasoning": "<one sentence>"}
`;
const result = await judgeModel.generate(judgePrompt);
return JSON.parse(result);
}

明确的评分标准能降低评判者的方差,提供可解释的输出,并且更容易在评判者出错时进行审计。像 AutoevalsBraintrust 这样的库为常见任务提供了预建的评分标准——在从头编写之前值得借鉴。


值得了解的工具

你不必从头构建所有这些东西。有几个工具在评估基础设施问题上取得了显著进展:

Braintrust — 完整的评估平台,包含实验跟踪、数据集管理和评分函数。按提示词、模型和部署组织评估运行,这样你就能对质量进行随时间变化的差异分析,而不仅仅是跨版本的对比。与其开源库 Autoevals 配合良好,后者为常见任务(事实准确性、帮助性、毒性、语义相似度)提供了预建的模型评分函数。

Langfuse — 开源 LLM 可观测性工具,位于你的应用和模型之间。追踪每一次调用,将评估分数(人工或自动化)附加到单个跨度上,并在生产流量中展示质量趋势。如果你希望可观测性和评估在同一工具中,而不是单独的评估框架,这是一个不错的选择。

Evalite — 由 Matt Pocock 开发的 TypeScript 原生评估框架。低仪式感:定义一个任务,定义一个评分器,在你现有的测试设置中运行它。面向那些希望评估感觉像单元测试而不是单独的 ML 实验平台的团队。

promptfoo — 以 CLI 为主的评估运行器,专注于提示词比较和红队测试。通过 YAML 轻松配置,与大多数模型提供商集成,并内置了对检测提示注入和其他对抗性输入的支持。

deepeval — Python 评估框架,拥有大量内置指标库(G-Eval、RAG 忠实度、答案相关性、幻觉检测)。对于 RAG 管道很有用,因为你希望针对检索质量(而不仅仅是生成质量)进行特定评分。

正确的工具取决于你的技术栈和起点。比选择框架更重要的是运行评估的纪律——持续地、在每次重大变更时都运行。

令人不适的部分

大多数团队跳过这一步,因为它过早抛出一个棘手的问题:这里“好”的标准是什么?

对于一个新的 AI 功能来说,这确实很难。但如果你在乎可靠性,这就不是可选项。那些交付可信 AI 的团队,做的正是他们对任何关键代码路径都会做的事:定义预期行为、测试它、并持续运行这些测试。

基准测试没有撒谎。它们只是在回答别人的问题。别再把它当成产品路线图来读,开始编写匹配你系统的测试吧。

你的用户会在你的仪表盘察觉之前就发现问题。先把测试套件建起来。