Agent 工程学习站 做棵大树 出品 · beatree.cn 免费 · 无需登录 · 进度存本机
III 版 · 评测工程 第 03 章 Executable Metrics
III · 评测工程 03 / 22 进阶

办事型 Agent 的硬指标

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

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

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

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

一、第一个坑:分母

这是最常见、影响最大的问题。看一个具体的例子:

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

三个数字都「对」,但含义完全不同。关键纪律是:报一个数字时必须同时报它的分母定义。 而且在同一份对比报告里,口径不能变——这次用 B、下次用 A 来显示进步,是典型的指标作弊。

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

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

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

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

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

「参数准确率」听起来是最客观的指标——毕竟参数有明确的值。但只要细看就会发现问题成堆。

「参数对了」有四种含义 同一个样本,不同粒度会得出「对」或「错」两种结论。必须先定粒度,再算数字。 任务:查 2025 年 3 月华东区、销售额大于 100 万的客户 人工标注的期望参数: {"region": "east", "year": 2025, "month": 3, "metric": "sales", "threshold": 1000000, "op": ">"} ① 结构级 只要求 JSON 能解析、 必填字段齐全 放宽度:最松 适合:早期快速迭代 风险:语法对但语义全错 也能算「通过」 通常虚高 15-25 个百分点 ② 字段级 逐个字段比对,要求值 完全一致 放宽度:中 适合:参数枚举明确的接口 风险:等价表达被判错 ("east" vs "East") 最常见的选择 ③ 语义等价级 规范化后比对:大小写、 单位、同义词映射 放宽度:较严 适合:生产环境的准确评估 优点:贴近「能不能用」 成本:要维护规范化规则 推荐:指标与业务指标对齐 ④ 结果级 不看参数,直接比 查询返回的数据结果 放宽度:最严(也最真) 适合:最终验收 优点:无法被「碰巧写对」 的参数骗过 成本:需要可执行的环境 枚举与自由文本不能用同一个口径 枚举参数(region / unit / op):字段级比对即可,值来自封闭集合,等价形式可以穷举。 自由文本参数(用户的自然语言条件、备注):无法用字段级比对,必须走语义等价判断(模型或规则)。 把两者混在一起算一个「参数准确率」,得到的数既不能反映结构正确性,也不能反映语义正确性。
图 1 参数准确率的四种粒度。选择原则很简单:指标粒度应该与「业务会不会因此出错」对齐——如果大小写不影响执行结果,就不该判错;如果阈值写错会导致查错数据,就必须判错。

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

「任务完成」是最难判定的指标,因为它通常需要人来看结果。可行的做法是按任务类型分三档:

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

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

  • 产物文件是否存在、格式是否合法(解析一下就知道);
  • 报告里提到的每个引用是否真实存在(拿 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 当成代码一样纳入版本管理,规则变更时在报告里显式标注「口径已更新」,并且不要在规则变更的同一版里对比新旧数据。这是评测工程的专业性所在,也是「口径不清等于没有指标」这句话的具体表现。

五、自测

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

六、小结

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

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

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

面试官会怎么问

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

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

追问链

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

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

本章收尾 · Wrap up
掌握度自评
点一下给自己打分;低于 3 分建议加入复习队列
个人笔记 · Notes