Chapter 01
核心概念:评什么,以及别把两个 eval 搞混
index 给了全局地图——这章建立评测的词汇表:先分清两个 eval,再看为什么难,最后建立「评什么」的四层心智模型。
读完本章,你的脑子里会多出这几条
- 「eval」是一词两义:A 评测体系(开发期离线,量 agent 行不行)与 B 运行期 Evaluator(推理时在环,让单次输出更好)。本教程讲 A。
- agent eval 比传统单元测试难,根因有三:非确定性、多步轨迹、开放式无唯一正解——这三点是后面所有方法的存在理由。
- 评测对象分四层:结果层 outcome / 轨迹层 trajectory / 组件层 component / 安全合规层 safety。大多数人只评最外层,回归藏在里面三层。
1.1两个都叫「eval」的东西:评测体系 vs 运行期 Evaluator
同一个词「eval」指两件事:A 是开发期量「行不行」的离线学科,B 是推理时让「这次输出」变好的在环组件。
翻开任意一篇 agent 文章、一段团队讨论或一个开源仓库,「eval」这个词会在两种完全不同的语境里出现,而二者共享技术却目的相反。把它们当成一回事,是这个领域最高频的概念混淆——后续每一章的术语都建立在这条分界线上,所以先钉死它。
A = Eval(评测体系)
一句话定义:一套开发期、离线的工程纪律,用固定测试集 + 打分 + 回归报告,回答「这个 agent 到底行不行、改完有没有变差」。它跑在 CI 里,针对一份冻结的数据集,产物是分数和回归报告。改了 prompt、换了模型、调了检索参数之后跑一遍,看分数有没有掉——这是它的主战场。
B = Evaluator(运行期组件)
一句话定义:agent 循环内部的一个推理时角色——「生成 → 批评 → 改进」,让这一次任务的输出更好。evaluator-optimizer、Reflexion、Self-RAG、CRAG 都属于这一类:模型先产出一版答案,再由一个评判角色指出问题,据此重写,迭代到满足标准为止。它每个任务都跑,目的不是统计 agent 的整体质量,而是抬高单次产出的下限。
A 与 B 共用一项技术——「用模型给输出打分」(即后面会展开的 LLM-as-a-judge,让一个 LLM 充当裁判给另一个 LLM 的输出评分)。正因为技术同源,读者极易把「在 agent 里加一个反思步骤(B)」误当成「给 agent 建了评测(A)」。但 B 装得再好,CI 里依然没有任何一条用例在拦截回归——下次改坏了照样上线。A 的缺失不会被 B 补上。
| 维度 | A · Eval 评测体系 | B · 运行期 Evaluator |
|---|---|---|
| 何时跑 | 开发期 / CI,离线,针对固定数据集 | 推理时,每次任务在 agent 循环里跑 |
| 目的 | 防回归、让迭代有据可依 | 提升这一次输出的质量 |
| 产物 | 分数 / 回归报告 | 一个更好的 answer |
| 本教程覆盖 | 主体——后续四章都在讲 A | 只在此划界,正文不展开 |
本教程从这里之后,「eval」一律指 A——评测体系。B 是它的近邻、技术上的表亲,但属于 agent 架构 / 推理模式的范畴,已在别处系统讲过:Reflexion 与 Self-RAG 的机制见 agent-reasoning-patterns 教程,evaluator-optimizer 作为一种工作流的成本权衡见 agent-interview 的 evaluator-optimizer 章节。本章把它放在视野里,是为了让读者每次遇到「eval」时都能先问一句:这是 A 还是 B。
某团队在 agent 里接了 Reflexion,让模型每次回答前先自我批评、重写一版。一个月后线上质量投诉变多了,他们却说「我们早就做了 eval」。问题出在哪?
展开答案(先停 10 秒再点)
他们做的是 B(运行期 Evaluator),不是 A(评测体系)。Reflexion 抬高了单次输出的下限,但 CI 里没有任何固定用例在对比「这次改动 vs 上一版」——所以改坏了没有任何东西拦得住,质量回归只能等线上投诉暴露。
这正是必须分清 A/B 的实际代价:B 装得再漂亮,也替代不了 A 的回归防线。两者要分别建。
1.2为什么 agent eval 比传统测试难
传统单元测试靠「同输入→同输出→assertEqual」三件套,agent 把这三件套逐条打破。
传统软件测试为什么省心:函数确定(同输入必同输出)、输出是单点(一个返回值)、正确答案唯一(事先写死在断言里)。一句 assertEqual(f(x), expected) 绿了就是对,红了就是错。agent eval 难,恰恰因为它在这三个前提上各失守一处。逐条拆开,每个难点都直接决定了后面要引入什么方法。
难点一 · 非确定性(同输入不同输出)
采样温度、并发顺序、底层模型的随机性,让同一个输入跑两次可能得到不同输出。assertEqual 立刻失效:它假设输出是个固定值,而 agent 的输出是个分布。单跑一次的「通过」可能纯属运气,单跑一次的「失败」也可能只是这次采样不走运。机制层面看,这意味着对单次结果的判断本身带方差,要测的其实是「这个输入在多大比例下能成」。
非确定性逼出 pass^k(同一用例跑 k 次、要求全部通过的指标——衡量稳定性而非运气)。把「通过率」当成被测量,而不是把单次结果当成真值。详见第 02 章。
难点二 · 多步轨迹(错可能在任何一环)
agent 不是一次函数调用,而是一条链:planning → tool call → 观察结果 → reflect → 再决策……(trajectory,即一次任务里 agent 走过的完整步骤序列)。最终答案对,不代表过程对——它可能选错了工具却歪打正着,可能绕了五步弯路才到,也可能调了一个本不该调的破坏性操作然后侥幸没出事。只看最终答案的 outcome eval,对这整条链是瞎的。
多步逼出轨迹层评测(trace/span:把一次任务的每一步记录成可回放的结构化日志)——单独检查「工具选对没、步数有没有膨胀、有没有重复调用」。这是 §1.3 四层模型里最容易被漏掉的一层。
难点三 · 开放式 · 无唯一正解(语义等价)
一份会议简报、一段长文总结、一个开放式回答,根本没有 ground truth(标准答案)可比。更尖锐的是语义等价问题:用户问「法国首都是哪」,「Paris」和「法国的首都」都对,但作为字符串它们不相等。最朴素的 exact match(精确字符串匹配)会把语义完全正确的答案判成 0 分。机制层面,这说明「相等」必须在语义空间而非字符空间里判定,而字符串比较根本够不到语义空间。
开放式逼出 LLM-as-a-judge——用一个 LLM 按 rubric(评分细则)判断语义层面的对错与好坏,而不是比字符串。为什么 exact match 不够、judge 怎么搭,第 02 章展开。
用 exact match 给「Paris」和参考答案「法国的首都」打分,会得到几分?这暴露了什么?
展开答案(先停 10 秒再点)
0.0。exact match 比的是字符串,两个字符串不相等,哪怕语义完全等价也判 0 分。
它暴露的是:开放式任务的「相等」必须在语义空间判定,字符比较够不到那一层。这是后面引入 LLM-as-a-judge 的直接动机——把判分从「字符是否一致」抬到「意思是否一致」。(具体怎么判,第 02 章展开。)
1.3评什么:四层模型
评测对象从外到里分四层:结果 outcome → 轨迹 trajectory → 组件 component → 安全 safety;越外层越省事,回归越藏在里面。
「agent eval」听起来像单一动作,其实它在问四个不同层次的问题。这套四层模型是本章承重的心智框架——读者带走它之后,任何一句 eval 讨论都能被归位:这是在评结果、评轨迹、评某个组件,还是评安全合规。四层从外(最终任务)到里(每个零件、每条红线)逐层收紧。
第一层 · 结果层 outcome
一句话定义:最终任务到底成没成(端到端 pass/fail)。为什么需要它:这是用户唯一直接感知的东西——任务没完成,过程再漂亮也是失败。底层机制:通常落成一个二元或标量信号(成功率、任务完成率),最省事也最粗——它把整条链压扁成一个点,答案对不代表过程对,所有内部代价都被它吞掉看不见。例子:「订机票」agent,最后订到了正确的票 = pass。至于它中途查了三次错误航班、调了八次 API,outcome 一概不记。
第二层 · 轨迹层 trajectory
一句话定义:过程对不对——走的每一步合不合理。为什么需要它:outcome 只看终点,轨迹层看的是怎么到的终点,这里藏着 outcome 永远照不到的成本与隐患。常评的维度:
- tool-selection correctness(工具选择正确性):每一步选对了工具、且参数对吗?
- step count / step-efficiency(步数 / 步效率):最优步数 ÷ 实际步数,绕路越多越低。
- redundancy(冗余):有没有重复调用同一个工具拿同样的结果。
- latency(延迟)与 cost(成本,常以 tokens/run 计):跑完一次花了多少时间、多少 token。
换一个底层模型后,outcome 可能纹丝不动(成功率还是 92%),但同一批任务的轨迹从平均 3 步膨胀到 10 步——token 成本和延迟翻了三倍。这是一次真实的成本回归,而只看 outcome 的评测对它完全失明。生产级日志(Anthropic 等会逐用例记录 n_turns / n_toolcalls / n_tokens)的意义正在于此:让轨迹膨胀变成可观测、可回归的量。
第三层 · 组件层 component
一句话定义:把 agent 拆开,对其中某个零件单独评分。为什么需要它:结果或轨迹出问题时,得知道是哪个零件坏了——是检索捞错了,还是路由分错了,还是记忆读串了。底层机制:给被测组件固定上下游,只量它这一段的输入输出质量。最常见的是 RAG 检索(检索增强生成:先检索资料、再让模型据此作答),它的四个 de-facto 标准指标来自 Ragas 这套库:
- faithfulness(忠实度):答案有没有脱离检索到的内容自行编造(即有没有幻觉)。
- context precision(上下文精确率):召回回来的资料,相关的占多少。
- context recall(上下文召回率):该被召回的相关资料,召回全了吗。
- answer relevancy(答案相关性):答案切不切题、有没有跑偏。
路由(选对了下游分支吗)、记忆(读写的上下文对吗)同理,都可以拆成独立组件来评。RAG 这几个指标的完整定义与计算方式,见 rag 教程的质量章。
第四层 · 安全合规层 safety
一句话定义:输出有没有越过红线。为什么需要它:前三层都在量「好不好用」,这一层量「能不能放出去」——一个又快又准的 agent,若会泄露敏感词或不遵守指令格式,照样不能上线。底层机制:通常是一组独立的拦截式检查,与质量分并列而非混算。常评:幻觉率、毒性(toxicity)、格式合规(输出符不符合约定的 JSON/结构)、指令遵循(instruction following,有没有照系统提示的约束办事)、敏感词处理。例子:要求只输出 JSON 的接口,agent 多吐了一段寒暄——质量也许不差,但格式合规这一层判不合格。
| 层 | 典型指标 | 何时该评它 |
|---|---|---|
| 结果层 outcome | 成功率 / 任务完成率 / 端到端 pass-fail | 任何 agent 的第一道线——先确认到底成不成 |
| 轨迹层 trajectory | tool-selection correctness、step-efficiency、redundancy、latency、cost(tokens/run) | 换模型 / 改 prompt 后,outcome 没变也要查轨迹有没有膨胀 |
| 组件层 component | faithfulness、context precision/recall、answer relevancy(RAG);路由 / 记忆同理 | 结果或轨迹出错、要定位是哪个零件坏了 |
| 安全合规层 safety | 幻觉率、毒性、格式合规、指令遵循、敏感词 | 上线前的放行检查——质量再高也得过这关 |
最常见的失败模式是「只建了结果层就以为评测做完了」。outcome 全绿、用户却在抱怨变慢变贵,或偶尔蹦出编造的事实——因为成本回归藏在轨迹层、编造藏在组件层的 faithfulness、越线藏在安全层。一份只有 outcome 的评测,对这三类问题天生失明。
§本章 self-check
先合上教程,把能想到的答案写在纸上或编辑器里。 写完再点开答案对照——直接点开等于把这一节当再读一遍。
- 用一句话各自说清 A(评测体系)和 B(运行期 Evaluator):它们何时跑、产物分别是什么?
- 传统单元测试的「同输入→同输出→assertEqual」三件套,被 agent 在哪三处分别打破?
- 四层模型从外到里依次是哪四层?RAG 的 faithfulness 属于哪一层?
- 一个 agent 最终答案对了,为什么仍可能给它判负分?这属于哪一层的评测在起作用?
答案(先做完再展开)
- A:开发期 / CI 离线跑、针对固定数据集,产物是分数 / 回归报告,目的是防回归。B:推理时每次任务在 agent 循环里跑,产物是一个更好的 answer,目的是提升单次输出。同源技术是「用模型打分」,但时机与目的相反。
- ① 非确定性——同输入不同输出,输出是分布不是单点;② 多步轨迹——不是单次函数调用,最终答案对不代表过程对;③ 开放式无唯一正解——没有 ground truth,且语义等价(「Paris」≡「法国的首都」)让 exact match 失效。
- 从外到里:结果层 outcome → 轨迹层 trajectory → 组件层 component → 安全合规层 safety。faithfulness 属于组件层(RAG 检索组件的指标)。
- 因为最终答案对只是结果层通过;但它可能绕了五步弯路、多花 5× token,或重复调用工具——这些由轨迹层评测捕捉,outcome 对它失明。
给四层各举一个「只有这层才抓得到」的真实失败
挑一个你熟悉的 agent(写代码的、查资料的、客服的都行)。为四层中的每一层各编一个具体失败场景,要求:这个失败恰好只有那一层的评测能抓到,换成相邻层就漏掉。比如「结果对、但轨迹层抓到它绕了路」。四个场景写完,你就验证了自己真的能把任意失败归位到正确的层。
提示(卡住再展开)
从「哪一层对这个失败失明」反推往往更快:先想一个 outcome 看不见的失败(→轨迹或更里),再想一个连轨迹也看不见、要拆组件才现形的失败(→组件,例如检索召回了相关文档但答案却编造,这是 faithfulness),最后想一个三层都判「好用」、却不能上线的失败(→安全,例如格式越线或泄露敏感词)。