上一章 评测集建设与防过拟合 评测集从哪来?答案是从线上长出来。这一章讲分层设计(冒烟 / 回归 / 边界 / 对抗)、保留集机制,以及怎么避免你的系统把评测集背下来。 III · 评测工程 · 第 02 章 · 进阶 · 约 30 分钟
Agent 工程学习站 做棵大树 出品 主站 beatree.cn 免费 · 无需登录 · 进度存本机
III 版 · 评测工程 ◆ 第 03 章 ◆ Executable Metrics
III · 评测工程 03 / 22 进阶

办事型 Agent 的硬指标

Executable Metrics
预计阅读 30 分钟
难度 进阶
关键词 可执行率 · 参数准确性
本机状态 未读

可执行率、参数准确率、任务完成率——这三个词每个人都说得出来,但问「分母是什么」「枚举和自由文本能用同一个口径吗」,多数人答不上来。而口径不清的指标比没有指标更危险:它会让你在错误的数字上做出信心十足的决策。

编辑部注 · 本章的核心是「口径」二字。建议读完把三张表存下来,面试讲到评测时可以直接引用。

办事型 Agent 的评测看起来比洞察型简单——因为它有明确的对错。但恰恰是「明确的对错」让人放松警惕:既然有对错,就一定能算准。事实是,三个团队测同一个系统,可能得出三个差 20 个百分点的答案,而每个团队都认为自己是对的。

一、第一个坑:分母

这是最常见、影响最大的问题。看一个具体的例子:把可执行率(Executable Rate)的三种口径摆出来——

口径设计分子分母算出的「可执行率」问题
A · 只看成功的样本执行成功且结果正确成功产出了 DSL 的样本约 89%把「没产出」的样本排除在分母外,等于把失败藏起来了
B · 全样本执行成功且结果正确参与评测的全部样本约 67%准确反映端到端表现,适合对外汇报
C · 排除不可解样本执行成功且结果正确全部样本减去「信息不足无法完成」的样本约 78%更公平地衡量「在可解问题上做得多好」,但必须公开排除规则

三个数字都「对」,但含义完全不同。关键纪律是:报一个数字时必须同时报它的分母定义——这正是端到端完成率(End-to-end Rate)(分母 = 全样本)之所以最值得对外汇报的原因。而且在同一份对比报告里,口径不能变——这次用 B、下次用 A 来显示进步,是典型的指标作弊。

推荐的做法:分层报告,而不是选一个口径

与其争论哪个口径对,不如把结果拆开报:

  • 端到端完成率(分母 = 全样本):最适合对外、最能反映用户真实体验;
  • 可解样本完成率(分母 = 全样本 − 不可解):用来衡量「技术能力的上限有多高」;
  • 不可解样本的处理率(分母 = 不可解样本):衡量「该拒绝或追问时有没有正确拒绝/追问」——这是一个独立且非常重要的指标。

第三个指标最容易被漏掉,但它决定了系统「会不会硬猜」。一个把不可解问题也硬答的系统,端到端完成率数字会好看,但用户体验更差。

二、第二个坑:什么叫「参数准确」

「参数准确率」听起来是最客观的指标——毕竟参数有明确的值。但只要细看就会发现问题成堆,最核心的是比对粒度(Granularity)没事先定好。

「参数对了」有四种含义 同一个样本,不同粒度会得出「对」或「错」两种结论。必须先定粒度,再算数字。 任务:查 2025 年 3 月华东区、销售额大于 100 万的客户 人工标注的期望参数: {"region": "east", "year": 2025, "month": 3, "metric": "sales", "threshold": 1000000, "op": ">"} ① 结构级 只要求 JSON 能解析、 必填字段齐全 放宽度:最松 适合:早期快速迭代 风险:语法对但语义全错 也能算「通过」 通常虚高 15-25 个百分点 ② 字段级 逐个字段比对,要求值 完全一致 放宽度:中 适合:参数枚举明确的接口 风险:等价表达被判错 ("east" vs "East") 最常见的选择 ③ 语义等价级 规范化后比对:大小写、 单位、同义词映射 放宽度:较严 适合:生产环境的准确评估 优点:贴近「能不能用」 成本:要维护规范化规则 推荐:指标与业务指标对齐 ④ 结果级 不看参数,直接比 查询返回的数据结果 放宽度:最严(也最真) 适合:最终验收 优点:无法被「碰巧写对」 的参数骗过 成本:需要可执行的环境 枚举与自由文本不能用同一个口径 枚举参数(region / unit / op):字段级比对即可,值来自封闭集合,等价形式可以穷举。 自由文本参数(用户的自然语言条件、备注):无法用字段级比对,必须走语义等价判断(模型或规则)。 把两者混在一起算一个「参数准确率」,得到的数既不能反映结构正确性,也不能反映语义正确性。
图 1 参数准确率的四种粒度。选择原则很简单:指标粒度应该与「业务会不会因此出错」对齐——如果大小写不影响执行结果,就不该判错;如果阈值写错会导致查错数据,就必须判错。
同一系统,三种分母,三个数 可执行率:89% / 67% / 78%——差 22 个百分点,全是分母不同 0 50 100 A 89% 只看成功样本 B 67% 全样本(推荐对外) C 78% 排除不可解样本 A 把「没产出」的样本排除在分母外,等于把失败藏起来。报数必须连带分母;对比时口径不能变,否则差距只是分母游戏。
图 2 三个团队测同一个系统能得出差 22 个百分点的数,且都「没错」。识别方法永远是三问:分子、分母、边界怎么算——这一问,大多数「我们更强」的争论会自己结束。
不可解样本必须单列指标 一个把不可解问题也硬答的系统:端到端完成率虚高,但不可解处理率暴露了真相 0 50 100 端到端 85% 看起来不错 不可解处理 20% 实际在硬猜 判据:不可解处理率必须独立成指标;端到端高但不可解处理低 = 系统把「不知道」也硬答了,体验更差。
图 3 只看端到端完成率,这个系统能拿 85 分;但它在不可解问题上基本靠硬猜(仅 20% 正确处理)。单列不可解处理率,才拦得住「硬猜」这种最隐蔽的退化。

三、第三个坑:任务完成率怎么判

「任务完成」是最难判定的指标,因为它通常需要人来看结果。可行的做法是按任务类型分三档,能用程序判定的优先程序化——这直接关系到任务完成率(Task Completion Rate)怎么算才不虚高:

任务类型判定方式可信度成本
结果可程序化验证跑一遍、比对期望结果(如查出的行数、生成的代码能否通过测试)最高低(一次执行)
结果可部分验证检查关键断言(字段是否齐全、数值是否在合理范围、引用是否真实存在)较高中(写断言)
结果需人工判断抽样人工评审 + 多人交叉打分一般高(每人每条若干分钟)
一个能大幅降低人工成本的技巧

把「需人工判断」的任务尽量改造成「可程序化验证」。具体做法是:不判定最终文本好不好,而是判定几个可程序化的副作用——

  • 产物文件是否存在、格式是否合法(解析一下就知道);
  • 报告里提到的每个引用是否真实存在(拿 URL 去 HEAD 一下);
  • 生成的查询是否真的能跑通且返回非空(跑一遍);
  • 是否需要澄清时真的澄清了(检查是否发生了澄清动作)。

这四条检查全部可以自动完成,而且它们覆盖了「完成度」最实质的部分。剩下真正需要人看的,往往只有 10%-20%。

四、动手:三档指标的计算实现

executable_metrics.pypython
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)要求把规范化规则当代码管,变更时显式标注「口径已更新」,且不在变更同版对比新旧数据。判据:规则一改,新旧指标必须打上不同版本号,禁止直接相减。

六、自测

本章自测第 2 题是真实面试题
Q1为什么「不可解样本的处理正确率」是一个必须有独立指标?
B。如果只看「完成率」,一个面对模糊需求就直接猜一个查询的系统会拿到更高分数——因为「追问澄清」在完成率视角下等于没完成任务。这会系统性地激励错误行为。把「正确处理不可解问题」单列成指标,才能纠正这个激励。
Q2枚举参数与自由文本参数可以合用同一个「参数准确率」口径吗?
D。这是「口径」问题最典型的表现。用严格比对处理自由文本 → 大量等价表达被判错(指标偏低且无意义);用语义比对处理枚举 → 该抓的错被放过(指标虚高)。正确做法是分列两个指标,并观察两者差值——差值大说明模型偏好非标准表达,这本身是一条有价值的优化线索。
Q3团队 A 报可执行率 89%,团队 B 报 67%,最可能的原因是?
A。这是最典型的「口径差异伪装成能力差异」。识别方法:拿到任何指标先问三件事——分子是什么、分母是什么、边界情况怎么算。三问之后,大部分「我们的数字比你们好」的讨论都会自动结束。B、C 也可能造成差异,但 22 个百分点的差距更符合分母口径不同的特征。

七、小结

指标关键口径问题
可执行率分母是「全样本」还是「成功产出样本」?差 20 个百分点
参数准确率粒度是结构级 / 字段级 / 语义等价级 / 结果级?
任务完成率能程序化验证的优先程序化;人工评审控制在 10-20%
不可解处理率必须独立成指标,否则会激励「硬猜」
规范化规则必须显式、可审阅、可版本化;变更时口径标注更新
报告方式分层报告(端到端 / 可解 / 不可解),不选单一数字

指标的全部价值在于「能被复现」。一个别人复现不出来的数字,无论多么精确,都只是个人观点。本刊编辑部

下一章处理更棘手的问题:当输出是「洞察」而不是「结果」,怎么评?

八、参考与延伸

办事型指标的核心不是「算出一个数」,而是「这个数能不能被复现、口径有没有写清」。下面按「建立直觉 → 动手 → 对齐一手定义」排好。

先看图(建立直觉)

  • Promptfoo 文档 —— 开源评测框架,支持把「期望行为」写成可执行断言(含参数比对、语义等价)。重点看它怎么定义不同严格度的断言,正好对应本章的粒度阶梯。
  • Hamel Hussain 博客 —— 大量 LLM 评测实战,尤其「可执行指标怎么写才不骗人」。他反复强调的「先定义什么是正确,再谈怎么测」,正是分母与口径纪律的来由。

再看代码(动手实现)

  • OpenAI Evals —— 官方评测框架,grading 函数与 rubric 怎么表达「结果级」判定。对比本章第四节的 MetricReport,能看出生产级框架多做了哪些分层与版本管理。

最后读论文(对齐一手定义)

  • SWE-bench —— 以「能否跑通测试」定义任务完成度的一手基准。它是「端到端完成率」最硬核的样本:不可解的不算,跑错的算失败。
  • HELM · 斯坦福 CRFM —— 公开口径的指标基准,把场景与指标做成可被复现的定义。想理解「为什么口径必须版本化」,看它怎么维护指标定义最直观。

◇ 面试官会怎么问

共 6 条 · 其中 2 条高频 · 先自己答一遍,再展开对照

"参数生成准确性"具体怎么算? 高频
不能一句话"用 LLM 打分"糊过去,要分类型定义口径:
① 枚举类参数(状态、类型、操作符)——精确匹配。生成值必须在合法枚举集合内且与期望一致,错一个就是错。
② 数值类参数(阈值、数量、日期区间)——精确匹配或带明确容差的近似匹配,容差要事前写清楚(例如日期允许 ±0,金额允许 ±0.01)。
③ 自由文本参数(检索关键词、描述字段)——结构化逐项比对:先把期望拆成必含要素集合,再检查生成结果覆盖了哪些要素,得分是覆盖率;若有明确禁止项,命中即扣分。
④ 集合类参数(多选标签、字段列表)——用精确率/召回率或 Jaccard 相似度,而不是"差不多就行"。
还有两个必须明确的口径问题:分母是什么(所有调用,还是只统计"模型认为该调用工具"的那些);部分正确怎么算(4 个参数对 3 个,是算 0 还是 0.75——不同选择会得出完全不同的结论)。

追问链

  1. 分母你选哪个?为什么?
  2. 部分正确你怎么处理?
加分点:主动把"分母定义"和"部分正确"作为口径问题提出来,说明你做过真实评测而不是纸上谈兵。
工具调用成功率这个指标有什么陷阱?
至少四个陷阱:
① 分母陷阱——如果把"模型压根没调工具"的情况排除在分母外,成功率会虚高,掩盖"该调不调"的问题。分母应该是所有本应调用工具的场景。
② 成功定义陷阱——HTTP 200 不等于任务成功。要区分"调用发出成功""参数正确""业务结果符合预期"三层,混在一起会看不出问题在哪。
③ 难度混同陷阱——把简单查询和复杂多步任务混在一个数字里,任何改动都会被大类淹没。应该按任务类型分层看。
④ 重试归因陷阱——经过重试才成功的算不算成功?我倾向于分开统计:一次成功率(反映模型质量)与最终成功率(反映系统兜底能力),两者同时看。只看最终成功率,会让"重试兜住一切"掩盖模型本身的问题。
加分点:"一次成功率 vs 最终成功率"分开看,是很能体现工程思维的细节。
任务完成率怎么定义?由谁判定?
定义上要区分三种口径,并明确说明用的是哪种:
① 自动化判定——任务有明确的可验证结果(生成了符合条件的文件、查询返回了非空结果、代码通过了测试),这是最可靠的,应当优先把任务设计成可自动验证的。
② 模型判定——用 LLM 对照 rubric 打分,适用于结果无法程序化验证的场景,必须配人工抽检校准。
③ 人工判定——最准但成本最高,用于校准前两者、以及构建金标准集。
工程上的目标是把尽可能多的任务转化为第 ① 类:给任务设计机器可验证的产出(结构化结果、断言、测试),这比提高打分模型的准确率更根本。
「不可解样本处理率」单独成指标之后,对模型的行为激励发生了什么变化? 高频
它把一个原本会被惩罚的行为,变成了会被奖励的行为。
在只有「完成率」的世界里,面对信息不足的问题,最优策略是硬猜一个答案——猜对了算完成,猜错了也不比拒绝更差,而拒绝或追问会被算作没完成任务,直接拉低分数。系统因此被塑造成一台过度自信的答题机器。
单列「不可解处理率」之后,分母只算不可解样本,正确拒绝或追问澄清算通过。硬猜的收益变成负数,追问开始得分。
工程含义是:分子分母的设计会直接塑造系统行为。所以评测集里必须真的有一批不可解样本,否则这个指标分母为零,等于不存在。

追问链

  1. 怎么判断一条样本到底算不算不可解?
  2. 追问澄清的成功率怎么定义——追问一次就算成功,还是要追问到点上?
加分点:加分点:把「指标设计」和「激励结构」连起来讲,说明你不只是会算数。
参数准确率的四种粒度里,哪一种最不容易被「碰巧写对」骗过?
结果级——不看参数本身,直接比对查询返回的数据结果。
结构级最容易被骗:JSON 能解析、字段齐全就算通过,语法对而语义全错也能拿分。字段级好一些,但仍可能「参数写对了,管道里还有别的 bug」。语义等价级贴近「能不能用」,可它判断的依然是参数。
结果级是唯一直接对准「这个查询有没有把用户要的数据取回来」的粒度:参数碰巧写成等价形式、或者查了别的东西却结果一致,概率都低得多。
代价是需要一个可执行环境,成本最高。所以实践中的组合是:字段级与语义级做日常快速回归,结果级做发版前的最终验收。

追问链

  1. 结果级判定下,「跑得通但结果为空」算通过吗?
  2. 如果环境不可执行,你会退回到哪一级,为什么?
规范化规则新增了一条同义词映射,参数准确率涨了 6 个百分点。这算改进吗?
不算,这是口径变更,不是能力提升。
同义词映射放宽了判定标准:原本判错的等价表达(比如「华东」与「east」)现在判对了。分数的上升来自评判的尺子变松,与系统行为无关。
正确做法:① 把规范化规则当代码管,改一次就打一个版本号;② 指标连同规则版本一起报,跨版本的分数禁止直接相减;③ 规则变更的那一版不要拿新旧数据做对比,要对比就用同一套新规则把历史结果重算一遍。
判据:任何一次改动只要碰了评分代码,就必须重跑基线,而不是直接拿新分数和旧分数比。

追问链

  1. 重算历史基线很贵,有没有更省的做法?
  2. 同义词表越加越宽,会带来什么风险?
加分点:加分点:主动说出「改评分标准等于换尺子,必须重算基线」。
答这类题的通用结构

结论先行(一句话给出取舍)→ 机制(为什么是这样,涉及哪条链路)→ 代价或边界(这么做放弃了什么)→ 你的实践或数字(真实场景里怎么落地)。 只讲机制不讲代价,是背题;只讲取舍没有数字,是空谈。

本章收尾 · Wrap up
掌握度自评
点一下给自己打分;低于 3 分建议加入复习队列
个人笔记 · Notes
    下一章 洞察型评分与 LLM-as-Judge "洞察好不好"也能评吗?能,但要先把维度拆开:方向相关性、证据充分性、可操作性、表达清晰度。以及一个必须警惕的东西——机器打分的四类偏差。 III · 评测工程 · 第 04 章 · 深入 · 约 34 分钟