主题
第 16 周 · Day 1:Agent 评估方法论——评估是测分,不是单测
对应手册任务:学习「评估方法论:离线/在线评估、golden dataset、error analysis 先行」,动手「从第 13 周客服 Agent 的对话记录挑 30 条,人工标注成功/失败并归类失败模式」,当日产出
golden_dataset_v0.jsonl。本篇只解决一个问题:Agent 是概率系统,单测那套断言思维失灵之后,用什么办法知道它到底好不好、这次改动是变好了还是变坏了。
今日目标
- 说得清 Agent 评估和单测的本质区别:同一输入不同输出,断言换成「对数据集聚合打分 + 趋势回归」
- 掌握三件事的操作口径:error analysis 先行、失败模式五桶归类、golden dataset 的字段设计与 20/5/5 配比
- 独立标注 30 条第 13 周客服 Agent 的真实对话(20 成功 / 5 边界 / 5 超纲),产出可进 git 的
golden_dataset_v0.jsonl
概念讲解:为什么评估不是单测
写传统代码,你验证的方式是断言:
ts
expect(add(1, 2)).toBe(3);断言有个隐含前提:确定性。add(1, 2) 今天是 3,明天还是 3,断言才有资格说「我验证过了」。
Agent 不满足这个前提。同一句「订单 A-1024 怎么还没到」,跑三遍可能得到三个结果:一次老实调用查订单工具按真实物流回复,一次顺嘴编了个发货日期,一次反问用户订单号是多少。没人改代码,输出自己变了——温度采样、检索排序波动、上下文长短,随便哪一环都能让结果漂移。
断言思维在这里两头失效。断言精确输出?「到哪了」有无数种正确说法,你断言哪一句。看单条结果?这条跑对了不代表下次也对,这次错了也可能只是偶发。
所以整个思路要换:从「断言单个输出」换成「对数据集测分」。流程是固定的:准备一组输入,每条定义可判定的行为标准,跑一遍逐条判 pass/fail,对整个集合算出通过率,改完 prompt 再跑一遍看分数是涨是跌。类比体检:单次心跳说明不了健康,一段时间的分布才能。
上周 Day 5 你给检索层算过 recall@k、MRR,那是组件评估,测的是「检索这个零件行不行」。本周对象升级成 Agent 整体:用户扔进来一句话,Supervisor 路由对没有、工具调对没有、参数是真的还是编的、最终有没有把事办成。零件好,不代表整机好。
这件事在 2026 年的行情是:行业调研显示 89% 的团队有可观测性,只有 52% 做离线评估、37% 做在线评估。trace 看得见不等于测得了——日志里躺着三千条对话,没人标过成败,就等于什么都没测。37 个百分点的缺口,就是 JD 上反复点名、会做的人却不多的那个技能。
评估有两条线。离线评估:固定数据集本地跑,随时可重复,改 prompt 前后的对比全靠它,本周主攻。在线评估:线上流量采样打分,真实但贵且慢,Day 7 再设计方案。先把离线做扎实。
核心知识
1. error analysis 先行:不读案例就写评估,等于瞎评
Hamel Husain 看过大量真实项目的评估工程,他有一条铁律:动手写任何评估之前,先人工读 30 条失败案例,归类,然后再写评估。
这条规矩反直觉。「评估」听起来该先设计指标体系,怎么先去干阅读理解?三个理由。
第一,你不知道该测什么。没见过系统怎么失败,你想象出来的测试全是你以为重要的,不是用户实际撞上的。
第二,评估集的分布必须贴真实。闭门造出来的 query 是「工程师眼里的 Agent」,日志里的 query 才是「用户眼里的 Agent」——口语、缺信息、夹着错别字。用前者做评估集,测分再高也是假象。
第三,归类暴露优先级。30 条读完你大概率会发现一两个失败模式贡献了一半以上的失败。先修哪个,数据替你排好。
顺序反过来是什么下场:先写评估的人,往往写出一堆「回复应当礼貌专业」这类正确的废话,测分常年 95 往上,线上照样被用户骂。评估测的是你的 Agent,不是你的愿望。
2. 失败模式五桶:归类就是修法地图
人工标注 fail 的同时,把每条失败扔进一个桶。先从这五桶开始:
- 工具选错:用户问物流,Agent 调了发起退款。修法是工具描述和 Supervisor 的路由 prompt。
- 参数幻觉:工具调对了,但订单号是模型编的。修法是参数提取的约束和调用前校验,取不到就回头问用户。
- 检索未命中:知识库里明明有答案,检索没捞回来,Agent 顺嘴硬答。修法是回到第 15 周那套指标查索引和分块。
- 格式违规:事办对了,输出不是约定的 JSON 结构,下游解析直接崩。修法是结构化输出加 schema 约束。
- 答非所问:工具全对,回复没接住用户的问题。修法在生成 prompt,让回复前先对照用户原始问题。
为什么要分桶:只标 pass/fail,30 条标完你知道「错了 9 条」,然后呢?分了桶,你知道「9 条里 6 条是参数幻觉」,修复优先级当场排出来。桶和修法是一一对应的地图,少了它,数据集只是一份失败名单。
五个桶不必是终点。标的过程中如果撞上装不进去的失败,长出自己的第六桶、第七桶再拆——但先从这五个开始,别一上来发明十几桶。
3. golden dataset:字段设计与 20/5/5 配比
golden dataset 是人工精标的评估集。「golden」的意思是:每条的标准答案是人定的,不是模型给的。
30 条起步,配比 20 成功 / 5 边界 / 5 超纲。成功案例覆盖高频正常路径:查订单、查物流、发起退款、售后 FAQ。边界案例考「缺信息会不会问」:没给订单号、意图模糊。超纲案例考「知道自己不知道」:让客服 Agent 写代码、套隐私信息,它应该拒答。
超纲为什么专门留 5 条:Agent 最丢分的往往不是分内事做不好,而是不会拒绝分外事。全成功案例的数据集,测不出越界硬做这个最伤体验的毛病。
字段设计,一行 JSON 一条记录:
id:稳定编号。后面回归对比、Day 2 算 judge 一致率,全靠它对齐query:用户原话,原样保留,别润色。错别字和口语就是真实分布expected_behavior:可判定的行为描述。写「做什么」,不写「说什么」tools_expected:期望调用的工具名数组,不该调用就写空数组category:success / boundary / out_of_scopepass:人工标注的当前表现。v0 阶段它记录的是现状,不是期望
其中 expected_behavior 的写法决定整个数据集的质量。对比两种写法:
- 差:「礼貌专业地告知物流信息」——什么叫专业?没法判
- 好:「调用 query_order 查 A-1024 的物流状态,按真实返回回复;查不到就明说查不到,不编造发货时间」——逐条可判
判断标准是:把这条拿给另一个人(Day 2 就是拿给 judge 模型),他能不能判出和你一样的结果。不能,就说明写得还不够具体。
最后一条纪律:数据集即代码。golden_dataset_v0.jsonl 进 git,从此它和测试套件一个待遇——改 prompt、换模型、动工具描述,都要对它重跑一遍,通过率跌了就得给出解释。文件名里的 v0 不是装饰,数据集每次扩充和修订,都要能追到是哪次提交改的。
动手任务:标注 30 条,一步一步
手册任务:从第 13 周客服 Agent 的对话记录挑 30 条,人工标注成功/失败并归类失败模式。拆成 5 步,全程约 40 分钟。
第 1 步:导出对话记录。 第 13 周用 LangGraph 跑的客服 Agent,trace 里就有对话记录。整理成三栏文本:用户输入、Agent 的工具调用轨迹、最终回复。要是当时没存日志,就现场再跑 40 到 50 轮造一批,但真实日志优先——现场造的往往太「干净」。
第 2 步:分层挑 30 条。 按配比挑:20 条正常请求、5 条边界、5 条超纲。别顺着日志挑前 30 条,也别专挑 Agent 表现好的。抽样偏了,后面所有结论都歪。
第 3 步:逐条判 pass/fail。 判断就一问:用户的事办成了没有?拆成四个子问:工具选对了吗,参数是真的吗,格式符合约定吗,回复接住用户的问题了吗。任何一问不过,整条记 fail。
第 4 步:fail 的归桶。 每条 fail 补一个 failure_bucket 字段,从五桶里选。拿不准放哪桶,说明桶的定义还没吃透,回核心知识第 2 节再读一遍。实在横跨两桶的,记主要那桶。
第 5 步:写成 JSONL,进 git。 每行一条合法 JSON,UTF-8 编码。下面 4 条样例覆盖三种类型和一条已归桶的失败,字段结构可直接照抄:
jsonl
{"id":"gd-001","query":"订单A-1024到哪了 怎么还没发货","expected_behavior":"调用 query_order 查询 A-1024 的物流状态,按真实返回回复;查不到就明说查不到,不编造发货时间","tools_expected":["query_order"],"category":"success","pass":true}
{"id":"gd-007","query":"A-1024 是发顺丰吗","expected_behavior":"调用 query_order 后按真实承运商信息回复;接口未返回承运商字段时说明查不到","tools_expected":["query_order"],"category":"success","pass":false,"failure_bucket":"param_hallucination"}
{"id":"gd-021","query":"我要退货","expected_behavior":"缺少订单号时先向用户澄清是哪笔订单,拿到订单号之前不调用 create_refund","tools_expected":[],"category":"boundary","pass":true}
{"id":"gd-026","query":"帮我写个Python快排","expected_behavior":"拒答并说明只处理售后问题,不调用任何工具","tools_expected":[],"category":"out_of_scope","pass":true}看第 007 条,参数幻觉的典型现场:用户只给了订单号没提快递公司,Agent 查完物流顺嘴回了句「是顺丰」,而接口返回里根本没有承运商字段。工具对、格式对,答案本身是编的——不读记录你发现不了这种错。
放哪、怎么提交
在第 13 周项目里新建 evals/ 目录,数据集存成 evals/golden_dataset_v0.jsonl,git add 提交。标完顺手算两个数:总通过率(pass 条数 ÷ 30),和各桶的失败占比。这两个数是本周所有改进的回归基线,记下来。
常见踩坑
坑 1:只挑表现好的对话。 数据集全绿看着舒服,测不出任何问题。30 条里必须有真的 fail——fail 条目就是你的修复清单,一份没有 fail 的 golden dataset 等于没标。
坑 2:expected_behavior 写成台词。 「应当礼貌地告知物流略有延迟请耐心等待」——模型凑不出你这句原话,永远判不过。写行为不写文案:调什么工具、依据什么回复、查不到怎么办。判定标准要经得起「换个人来判」的检验。
坑 3:fail 不记原因。 只标 pass: false 不归桶,30 条标完你知道错了 8 条,然后呢?桶是连向修法的地图,少了它,你手里只是一份失败名单,不知道该先动哪里。
坑 4:数据集不进 git,改 prompt 不重跑。 「数据集即代码」不是修辞,是三条纪律:版本化进 git,每次 prompt 或模型变更对它重跑,通过率变化可追溯。做不到,评估就是做过一次的行为艺术。
坑 5:起步就想标 500 条。 30 条精标(每条 expected_behavior 都经得起推敲)远胜 500 条糙标。先把流程跑通,Day 4 接 promptfoo 时再扩也不迟。标注是体力活,预算省着花。
自测问题
先自己答,再展开对照。答不上来的,回对应小节看一遍。
- 为什么
expect(agent.run("订单到哪了")).toBe("...")这种断言在 Agent 评估里不成立?
参考答案
Agent 是概率系统,同一输入不同输出:温度采样、检索波动都会让结果漂移,而且正确答案本身不唯一。断言要求「输入唯一确定输出」且「输出唯一」,两个前提都不满足。替代方案是对固定数据集聚合打分(通过率),再做版本间的趋势回归。
- error analysis 先行的铁律是什么?顺序反过来会发生什么?
参考答案
铁律:先人工读约 30 条失败案例并归类,再动手写评估。反过来,评估集只覆盖「你以为重要的」,与真实失败分布脱节,测分常年高位、线上体验照崩——评估测的成了你的愿望,不是系统本身。
- 五个失败桶分别是什么?各自指向什么修法?
参考答案
工具选错,修工具描述与路由 prompt;参数幻觉,修参数约束与调用前校验;检索未命中,回第 15 周用 recall@k 查检索层;格式违规,上结构化输出和 schema 约束;答非所问,修生成 prompt,让回复前对照用户原始问题自检。
- 20/5/5 的配比里,超纲的 5 条在防什么?
参考答案
防「不会拒绝」。全成功案例的数据集只测分内事做得好不好,测不出分外请求(写代码、套隐私)会不会老实拒答。Agent 最丢分的场景往往不是不会做,而是越界硬做。
- golden dataset 的 pass 字段在 v0 阶段标的是什么?它什么时候开始「反过来用」?
参考答案
v0 标的是当前版本的真实表现(人工判定现状),fail 条目构成修复清单。当你修完 prompt 或工具后重跑,期望原来 fail 的翻成 pass,两版通过率一比就是最朴素的回归——数据集从「现状记录」变成「回归基准」。
延伸阅读
- Hamel Husain:Your AI Product Needs Evals,error analysis 先行的原始出处,本篇方法论的老家,值得全文精读
- LangChain Academy:Building Reliable Agents,免费课程,把评估放进 Agent 可靠性工程的大图里看
- promptfoo 文档,Day 4 要用的评估工具,今天先混个眼熟,知道 golden dataset 最终会变成它的配置
今天的产出 golden_dataset_v0.jsonl 和两个基线数字(总通过率、五桶占比)留好:Day 2 校准 LLM-as-judge 要拿它对人工一致率,Day 3 的期望轨迹从它挑 10 条,Day 4 它会被转成 promptfoo 配置。这 30 条,是你本周一切改进的对照组。