可执行率、参数准确率、任务完成率——这三个词每个人都说得出来,但问「分母是什么」「枚举和自由文本能用同一个口径吗」,多数人答不上来。而口径不清的指标比没有指标更危险:它会让你在错误的数字上做出信心十足的决策。
编辑部注 · 本章的核心是「口径」二字。建议读完把三张表存下来,面试讲到评测时可以直接引用。
办事型 Agent 的评测看起来比洞察型简单——因为它有明确的对错。但恰恰是「明确的对错」让人放松警惕:既然有对错,就一定能算准。事实是,三个团队测同一个系统,可能得出三个差 20 个百分点的答案,而每个团队都认为自己是对的。
一、第一个坑:分母
这是最常见、影响最大的问题。看一个具体的例子:把可执行率(Executable Rate)的三种口径摆出来——
| 口径设计 | 分子 | 分母 | 算出的「可执行率」 | 问题 |
|---|---|---|---|---|
| A · 只看成功的样本 | 执行成功且结果正确 | 成功产出了 DSL 的样本 | 约 89% | 把「没产出」的样本排除在分母外,等于把失败藏起来了 |
| B · 全样本 | 执行成功且结果正确 | 参与评测的全部样本 | 约 67% | 准确反映端到端表现,适合对外汇报 |
| C · 排除不可解样本 | 执行成功且结果正确 | 全部样本减去「信息不足无法完成」的样本 | 约 78% | 更公平地衡量「在可解问题上做得多好」,但必须公开排除规则 |
三个数字都「对」,但含义完全不同。关键纪律是:报一个数字时必须同时报它的分母定义——这正是端到端完成率(End-to-end Rate)(分母 = 全样本)之所以最值得对外汇报的原因。而且在同一份对比报告里,口径不能变——这次用 B、下次用 A 来显示进步,是典型的指标作弊。
与其争论哪个口径对,不如把结果拆开报:
- 端到端完成率(分母 = 全样本):最适合对外、最能反映用户真实体验;
- 可解样本完成率(分母 = 全样本 − 不可解):用来衡量「技术能力的上限有多高」;
- 不可解样本的处理率(分母 = 不可解样本):衡量「该拒绝或追问时有没有正确拒绝/追问」——这是一个独立且非常重要的指标。
第三个指标最容易被漏掉,但它决定了系统「会不会硬猜」。一个把不可解问题也硬答的系统,端到端完成率数字会好看,但用户体验更差。
二、第二个坑:什么叫「参数准确」
「参数准确率」听起来是最客观的指标——毕竟参数有明确的值。但只要细看就会发现问题成堆,最核心的是比对粒度(Granularity)没事先定好。
三、第三个坑:任务完成率怎么判
「任务完成」是最难判定的指标,因为它通常需要人来看结果。可行的做法是按任务类型分三档,能用程序判定的优先程序化——这直接关系到任务完成率(Task Completion Rate)怎么算才不虚高:
| 任务类型 | 判定方式 | 可信度 | 成本 |
|---|---|---|---|
| 结果可程序化验证 | 跑一遍、比对期望结果(如查出的行数、生成的代码能否通过测试) | 最高 | 低(一次执行) |
| 结果可部分验证 | 检查关键断言(字段是否齐全、数值是否在合理范围、引用是否真实存在) | 较高 | 中(写断言) |
| 结果需人工判断 | 抽样人工评审 + 多人交叉打分 | 一般 | 高(每人每条若干分钟) |
把「需人工判断」的任务尽量改造成「可程序化验证」。具体做法是:不判定最终文本好不好,而是判定几个可程序化的副作用——
- 产物文件是否存在、格式是否合法(解析一下就知道);
- 报告里提到的每个引用是否真实存在(拿 URL 去 HEAD 一下);
- 生成的查询是否真的能跑通且返回非空(跑一遍);
- 是否需要澄清时真的澄清了(检查是否发生了澄清动作)。
这四条检查全部可以自动完成,而且它们覆盖了「完成度」最实质的部分。剩下真正需要人看的,往往只有 10%-20%。
四、动手:三档指标的计算实现
from dataclasses import dataclass
from typing import Any
import re, unicodedata
def normalize(v: Any, rules: dict) -> Any:
"""
语义等价的规范化。规则必须是显式的、可审阅的,
而不是「遇到不一致就放宽」——否则指标会失去意义。
"""
if isinstance(v, str):
v = unicodedata.normalize("NFKC", v).strip()
if rules.get("case_insensitive"):
v = v.lower()
for src, dst in rules.get("synonyms", {}).items():
if v == src:
v = dst
return v
def args_match(pred: dict, gold: dict, rules: dict) -> tuple[bool, list[str]]:
"""
参数比对:返回 (是否匹配, 不匹配的字段列表)。
返回字段明细很重要 —— 归因时你需要知道是哪个字段在错。
"""
bad = []
for k, gv in gold.items():
if k not in pred:
bad.append(f"{k}:缺失")
continue
pv = normalize(pred[k], rules.get(k, {}))
if pv != normalize(gv, rules.get(k, {})):
bad.append(f"{k}:{pv!r}≠{gv!r}")
return (len(bad) == 0), bad
@dataclass
class MetricReport:
"""分层报告:任何一个数字都必须带口径说明"""
end_to_end: float # 分母 = 全样本
solvable_only: float # 分母 = 全样本 - 不可解
refuse_handling: float # 分母 = 不可解样本(正确拒绝或澄清的比例)
args_field_level: float # 参数:字段级严格比对
args_semantic: float # 参数:语义等价比对
n_total: int
n_solvable: int
n_unsolvable: int
def render(self) -> str:
return (
f"端到端完成率 {self.end_to_end:.1%}"
f" (分母 {self.n_total} = 全样本)\n"
f"可解样本完成率 {self.solvable_only:.1%}"
f" (分母 {self.n_solvable} = 全样本 − 不可解)\n"
f"不可解处理正确率 {self.refuse_handling:.1%}"
f" (分母 {self.n_unsolvable} = 不可解样本,正确拒绝或追问澄清)\n"
f"参数字段级准确率 {self.args_field_level:.1%} (严格比对)\n"
f"参数语义等价准确率 {self.args_semantic:.1%} (规范化后比对)\n"
f"※ 两行参数指标差值 = {abs(self.args_field_level - self.args_semantic):.1%},"
f"差值过大说明模型偏好非标准表达,可在描述里给出枚举值收敛"
)
def evaluate(results: list[dict], rules: dict) -> MetricReport:
"""
results 的每项形如:
{
"solvable": True,
"pred_args": {...} | None,
"gold_args": {...},
"executed_ok": True, # 真正跑通且结果正确
"correctly_refused": False, # 不可解样本上是否给出了正确回应
}
"""
total = len(results)
unsolvable = [r for r in results if not r["solvable"]]
solvable = [r for r in results if r["solvable"]]
e2e = sum(1 for r in results if r.get("executed_ok")) / total if total else 0
solv = sum(1 for r in solvable if r.get("executed_ok")) / len(solvable) if solvable else 0
refu = (sum(1 for r in unsolvable if r.get("correctly_refused")) / len(unsolvable)
if unsolvable else 0)
has_args = [r for r in results if r.get("pred_args") and r.get("gold_args")]
field_ok = sum(1 for r in has_args if args_match(r["pred_args"], r["gold_args"], {})[0])
sem_ok = sum(1 for r in has_args if args_match(r["pred_args"], r["gold_args"], rules)[0])
return MetricReport(
end_to_end=e2e,
solvable_only=solv,
refuse_handling=refu,
args_field_level=field_ok / len(has_args) if has_args else 0,
args_semantic=sem_ok / len(has_args) if has_args else 0,
n_total=total, n_solvable=len(solvable), n_unsolvable=len(unsolvable),
)
# --- 规范化规则示例(显式、可审阅、可版本化) ---
RULES = {
"case_insensitive": True, # 全局:忽略大小写
"region": {"synonyms": {"华东": "east", "华东区": "east", "East": "east"}},
"op": {"synonyms": {"大于": ">", ">": ">", "gt": ">"}},
"unit": {"synonyms": {"万": "10000", "万元": "10000"}},
}规范化规则一旦修改,历史指标就不可比了。例如把「华东区」加入同义词后,参数准确率会突然上升——但这不代表系统变好了,只是评判标准变了。
做法:把 RULES 当成代码一样纳入版本管理,规则变更时在报告里显式标注「口径已更新」,并且不要在规则变更的同一版里对比新旧数据。这是评测工程的专业性所在,也是「口径不清等于没有指标」这句话的具体表现。
五、常见误区与追问
5.1 误区:可执行率只要能跑就行(其实「可执行」含「跑对」)
很多人把「输出能被解析」就当成功。错。可执行率的「可执行」必须包含「跑出正确结果」——否则语法对但字段名错的情况会被算作成功,指标系统性虚高。判据:把三种口径(解析 / 引擎接受 / 结果正确)分开报;只报最宽松口径的团队,分数至少虚高 15-25 个百分点。
5.2 误区:参数准确率用一种粒度就够了(其实枚举和自由文本天差地别)
用字段级严格比对处理自由文本,等价表达(「华东」vs「east」)会被判错;用语义比对处理枚举,该抓的错被放过。参数准确率(Argument Accuracy)的比对粒度必须按参数类型选:枚举走字段级,自由文本走语义级。判据:把两个粒度都算出来,两者差值大说明模型偏好非标准表达——这本身是一条优化线索。
5.3 误区:不可解样本也该硬答(其实是硬猜)
信息不足、根本不该答的问题,系统却给了一个看似合理的答案,端到端完成率反而更高。不可解样本(Unsolvable Sample)的正确处理是拒绝或追问澄清。判据:必须把不可解处理率(Refusal Handling Rate)单列成指标;该拒绝却硬答的样本要当作失败计入,而不是当成「完成了一次回答」。
5.4 误区:字符串「归一化」和向量「归一化」是一回事(其实完全不同)
评测里说的字符串规范化(Normalization)是统一大小写、单位、同义词后再比对;而向量语境的「归一化」是把向量缩放到单位长度。混用这两个词会导致规则写错、比对失效。判据:凡涉及参数比对,明确写「字符串规范化」;涉及 embedding 相似度再谈向量归一化,二者不 crossover。
5.5 误区:两个写法不同就是错(其实是语义等价)
「2025年3月」和「2025-03」表达的是同一个时间,字段级比对会判错。语义等价(Semantic Equivalence)要求规范化后判定两者是否同义。判据:维护一张显式的同义词与单位映射表,把「华东/East」这类都归一;但映射必须经人工审阅,不能「遇不一致就放宽」,否则指标失去意义。
5.6 误区:规范化规则改了无所谓(其实历史指标立刻不可比)
把「华东区」加入同义词后参数准确率会突然上升——但这不代表系统变好,只是评判标准变了。规则版本化(Rule Versioning)要求把规范化规则当代码管,变更时显式标注「口径已更新」,且不在变更同版对比新旧数据。判据:规则一改,新旧指标必须打上不同版本号,禁止直接相减。
六、自测
七、小结
| 指标 | 关键口径问题 |
|---|---|
| 可执行率 | 分母是「全样本」还是「成功产出样本」?差 20 个百分点 |
| 参数准确率 | 粒度是结构级 / 字段级 / 语义等价级 / 结果级? |
| 任务完成率 | 能程序化验证的优先程序化;人工评审控制在 10-20% |
| 不可解处理率 | 必须独立成指标,否则会激励「硬猜」 |
| 规范化规则 | 必须显式、可审阅、可版本化;变更时口径标注更新 |
| 报告方式 | 分层报告(端到端 / 可解 / 不可解),不选单一数字 |
指标的全部价值在于「能被复现」。一个别人复现不出来的数字,无论多么精确,都只是个人观点。本刊编辑部
下一章处理更棘手的问题:当输出是「洞察」而不是「结果」,怎么评?
八、参考与延伸
办事型指标的核心不是「算出一个数」,而是「这个数能不能被复现、口径有没有写清」。下面按「建立直觉 → 动手 → 对齐一手定义」排好。
先看图(建立直觉)
- Promptfoo 文档 —— 开源评测框架,支持把「期望行为」写成可执行断言(含参数比对、语义等价)。重点看它怎么定义不同严格度的断言,正好对应本章的粒度阶梯。
- Hamel Hussain 博客 —— 大量 LLM 评测实战,尤其「可执行指标怎么写才不骗人」。他反复强调的「先定义什么是正确,再谈怎么测」,正是分母与口径纪律的来由。
再看代码(动手实现)
- OpenAI Evals —— 官方评测框架,grading 函数与 rubric 怎么表达「结果级」判定。对比本章第四节的 MetricReport,能看出生产级框架多做了哪些分层与版本管理。
最后读论文(对齐一手定义)
- SWE-bench —— 以「能否跑通测试」定义任务完成度的一手基准。它是「端到端完成率」最硬核的样本:不可解的不算,跑错的算失败。
- HELM · 斯坦福 CRFM —— 公开口径的指标基准,把场景与指标做成可被复现的定义。想理解「为什么口径必须版本化」,看它怎么维护指标定义最直观。
◇ 面试官会怎么问
共 6 条 · 其中 2 条高频 · 先自己答一遍,再展开对照
"参数生成准确性"具体怎么算? 高频
① 枚举类参数(状态、类型、操作符)——精确匹配。生成值必须在合法枚举集合内且与期望一致,错一个就是错。
② 数值类参数(阈值、数量、日期区间)——精确匹配或带明确容差的近似匹配,容差要事前写清楚(例如日期允许 ±0,金额允许 ±0.01)。
③ 自由文本参数(检索关键词、描述字段)——结构化逐项比对:先把期望拆成必含要素集合,再检查生成结果覆盖了哪些要素,得分是覆盖率;若有明确禁止项,命中即扣分。
④ 集合类参数(多选标签、字段列表)——用精确率/召回率或 Jaccard 相似度,而不是"差不多就行"。
还有两个必须明确的口径问题:分母是什么(所有调用,还是只统计"模型认为该调用工具"的那些);部分正确怎么算(4 个参数对 3 个,是算 0 还是 0.75——不同选择会得出完全不同的结论)。
追问链
- 分母你选哪个?为什么?
- 部分正确你怎么处理?
工具调用成功率这个指标有什么陷阱?
① 分母陷阱——如果把"模型压根没调工具"的情况排除在分母外,成功率会虚高,掩盖"该调不调"的问题。分母应该是所有本应调用工具的场景。
② 成功定义陷阱——HTTP 200 不等于任务成功。要区分"调用发出成功""参数正确""业务结果符合预期"三层,混在一起会看不出问题在哪。
③ 难度混同陷阱——把简单查询和复杂多步任务混在一个数字里,任何改动都会被大类淹没。应该按任务类型分层看。
④ 重试归因陷阱——经过重试才成功的算不算成功?我倾向于分开统计:一次成功率(反映模型质量)与最终成功率(反映系统兜底能力),两者同时看。只看最终成功率,会让"重试兜住一切"掩盖模型本身的问题。
任务完成率怎么定义?由谁判定?
① 自动化判定——任务有明确的可验证结果(生成了符合条件的文件、查询返回了非空结果、代码通过了测试),这是最可靠的,应当优先把任务设计成可自动验证的。
② 模型判定——用 LLM 对照 rubric 打分,适用于结果无法程序化验证的场景,必须配人工抽检校准。
③ 人工判定——最准但成本最高,用于校准前两者、以及构建金标准集。
工程上的目标是把尽可能多的任务转化为第 ① 类:给任务设计机器可验证的产出(结构化结果、断言、测试),这比提高打分模型的准确率更根本。
「不可解样本处理率」单独成指标之后,对模型的行为激励发生了什么变化? 高频
在只有「完成率」的世界里,面对信息不足的问题,最优策略是硬猜一个答案——猜对了算完成,猜错了也不比拒绝更差,而拒绝或追问会被算作没完成任务,直接拉低分数。系统因此被塑造成一台过度自信的答题机器。
单列「不可解处理率」之后,分母只算不可解样本,正确拒绝或追问澄清算通过。硬猜的收益变成负数,追问开始得分。
工程含义是:分子分母的设计会直接塑造系统行为。所以评测集里必须真的有一批不可解样本,否则这个指标分母为零,等于不存在。
追问链
- 怎么判断一条样本到底算不算不可解?
- 追问澄清的成功率怎么定义——追问一次就算成功,还是要追问到点上?
参数准确率的四种粒度里,哪一种最不容易被「碰巧写对」骗过?
结构级最容易被骗:JSON 能解析、字段齐全就算通过,语法对而语义全错也能拿分。字段级好一些,但仍可能「参数写对了,管道里还有别的 bug」。语义等价级贴近「能不能用」,可它判断的依然是参数。
结果级是唯一直接对准「这个查询有没有把用户要的数据取回来」的粒度:参数碰巧写成等价形式、或者查了别的东西却结果一致,概率都低得多。
代价是需要一个可执行环境,成本最高。所以实践中的组合是:字段级与语义级做日常快速回归,结果级做发版前的最终验收。
追问链
- 结果级判定下,「跑得通但结果为空」算通过吗?
- 如果环境不可执行,你会退回到哪一级,为什么?
规范化规则新增了一条同义词映射,参数准确率涨了 6 个百分点。这算改进吗?
同义词映射放宽了判定标准:原本判错的等价表达(比如「华东」与「east」)现在判对了。分数的上升来自评判的尺子变松,与系统行为无关。
正确做法:① 把规范化规则当代码管,改一次就打一个版本号;② 指标连同规则版本一起报,跨版本的分数禁止直接相减;③ 规则变更的那一版不要拿新旧数据做对比,要对比就用同一套新规则把历史结果重算一遍。
判据:任何一次改动只要碰了评分代码,就必须重跑基线,而不是直接拿新分数和旧分数比。
追问链
- 重算历史基线很贵,有没有更省的做法?
- 同义词表越加越宽,会带来什么风险?
结论先行(一句话给出取舍)→ 机制(为什么是这样,涉及哪条链路)→ 代价或边界(这么做放弃了什么)→ 你的实践或数字(真实场景里怎么落地)。 只讲机制不讲代价,是背题;只讲取舍没有数字,是空谈。