Agent Evals 学习教程

04. 错误分析与数据集构建

读完本章,你应该能:

本章回答:如何从真实失败出发,构造真正有用的 eval 数据集。

学习目标

读完本章,你应该能:

  • 用 open coding 和 axial coding 分析 agent trace。
  • 把失败模式转成 regression cases、deterministic assertions 或 LLM judges。
  • 判断什么时候应该生成 synthetic test data。
  • 设计 golden set、regression set、holdout 和线上抽样的关系。

本章导读

错误分析是 eval 中最不花哨、也最容易被跳过的一步。它不像新框架那样让人兴奋,也不像排行榜那样有即时反馈。它要求你坐下来,一条一条读系统的真实行为,承认那些在 demo 里看不见的问题正在发生。很多团队不做这一步,不是因为不知道它有用,而是因为它太像“笨功夫”。

但 AI 产品的质量,往往就藏在这些笨功夫里。你读到第十条 trace,可能只觉得系统偶尔糊涂;读到第五十条,才会发现它反复在同一个地方失败。用户说得含糊时它不追问,工具返回空结果时它开始编造,遇到高风险请求时它没有转人工。这些模式不是从抽象指标里长出来的,而是从真实样本里浮出来的。

本章讲的 open coding、axial coding、regression set,本质上是在把“我感觉哪里不对”转成“我们知道哪类失败最常发生,知道它有多严重,也知道该写什么 eval 防止它再次发生”。这一步做扎实,后面的 judge、verifier 和 CI 才有根。

先看数据

几乎所有高质量 eval 实践都把“看真实数据”放在第一步。Hamel/Shreya 在 walkthrough 中反复强调:阻碍团队进步的常常不是缺平台,而是没有做 error analysisHamelShreya2026。他们建议读约 100 条 trace,每条快速记录第一处最上游错误,再聚类和计数。

为什么是第一处最上游错误?因为后续错误常是连锁反应。比如 agent 一开始误解用户 intent,后面调用错工具、输出错文本、产生错状态都只是症状。你如果把每个症状都记成独立问题,会得到一张噪声很大的错误表。

真正做这一步时,体验通常很不“自动化”。你会打开一条 trace,看用户第一句话、系统检索到的上下文、工具调用、工具返回值、最终回复和环境状态。你会停下来问:如果我是这个产品的负责人,第一次不可接受的偏离发生在哪里?有时是用户意图被误解,有时是工具返回空结果后模型开始编造,有时是系统知道风险很高却没有转人工。这个判断不只是技术分类,也包含产品判断:什么错误对用户最重要,什么错误对业务最危险。

看数据的另一个好处,是它会修正团队的想象。团队原以为最大问题是“回答不够礼貌”,结果 trace 显示真正高频的是费用政策检索失败;团队原以为 agent 不会处理复杂报销,结果发现它能整理复杂单据,却会在缺少项目编号时直接提交。没有这一步,后面的指标很容易测到一个并不重要的问题。

错误分析流程

Rendering diagram...

第一步:抽样

从真实线上或 dogfood 数据中抽样。初期可以 20 到 50 条,成熟系统建议 50 到 100 条。抽样要覆盖:

  • 高频用户意图。
  • 高风险操作。
  • 长对话或长任务。
  • 失败率异常高的场景。
  • 高成本、高延迟、人工接管或用户差评样本。

不要只抽“看起来正常”的请求,也不要只抽最极端失败。你需要能看出主分布和尾部风险。

抽样本身就是一次产品取舍。如果只抽失败样本,你会高估问题严重性,也看不到正常路径长什么样;如果只抽随机样本,低频高危失败可能一个都看不到。比较稳妥的做法是混合抽样:一部分来自随机线上流量,一部分来自用户差评、人工接管、高延迟、高成本和已知风险场景。这样你既能看到系统的日常表现,也能看到边界条件下的断裂方式。

第二步:open coding

open coding 是给每条 trace 写自由文本错误笔记。原则:

  • 只写第一处最上游错误。
  • 用具体语言,不要写“质量差”。
  • 写产品语境,不只写模型语境。
  • 不急着修 prompt,先积累样本。

例子:

不好的 note更好的 note
回答不好员工想报销上海出差,agent 未识别酒店超预算需要说明
幻觉声称已附上发票,但附件系统里没有对应 receipt_id
工具错未确认员工提交权限就调用 submit_expense
格式问题报销备注缺少项目编号,导致财务系统无法入账
没帮助员工只说“帮我报销这趟差旅”,agent 没有追问项目、费用归属或缺失单据

open coding 的关键,是暂时不要急着把错误塞进预设分类。你可以先写自然语言笔记,哪怕句子有点长也没关系。比如“员工上传了高铁和酒店发票,但酒店金额超过政策上限;agent 没有要求业务说明,也没有标记审批风险,直接生成可提交单据”。这句话比“policy error”更笨拙,但它保留了后来写 eval 所需的信息:超预算、缺少说明、审批风险、错误提交。分类可以晚一点做,细节丢了就很难补回来。

第三步:axial coding

把自由文本 note 聚成 4 到 8 个失败类别。可以让 LLM 先聚类,但必须由人类审查。好的类别应该:

  • 能指导修复。
  • 能写成 eval。
  • 边界清楚。
  • 不太泛,也不太碎。

例如差旅报销 agent 中,错误可能聚成:

  • 费用类型归类错误。
  • 超预算政策未处理。
  • 缺少必要追问。
  • 附件/发票状态不一致。
  • 未经确认直接提交。
  • 审批权限或审批链处理失败。

Iusztin 的文章用真实 agent trace 演示这套流程:先从 20 到 50 条生产 trace 建数据集,由领域专家做 Pass/Fail 标注和 critique,再把 open codes 聚成更高层的 axial categories,最后只为持续出现、影响较大的失败写 4 到 7 个专门 evaluatorIusztinEvalDataset2025。

axial coding 是从“个案”走向“系统性问题”的一步。一个类别太泛,就无法行动;比如“模型理解错了”几乎不能指导修复。一个类别太碎,也难以积累统计意义;比如每一种酒店超预算原因都单独成类,团队会看不到它们同属“超预算政策与说明缺失”。好的类别通常能直接转成一个 owner 和一个 scorer:工具 schema 要修、RAG 要补、prompt 要加确认策略、UI 要限制输入,或者需要新增人工接管规则。

第四步:优先级排序

不要只按频率排序。建议用:

priority = frequency x severity x business_value

示例:

失败类别频率严重性业务价值优先级
信息泄露35575
超预算政策未处理1434168
报销备注缺少项目编号1823108
未识别可疑供应商发票555125

这里超预算政策未处理可能最值得马上修,因为频率高且会影响财务合规;可疑供应商发票虽然少,但严重性高,也应单独设安全 gate。

优先级排序的目的不是制造一个精确公式,而是让团队显式讨论取舍。频率高的问题会影响总体体验,严重性高的问题会决定上线风险,业务价值高的问题会影响产品目标。把三者写出来,能避免两种常见偏差:只修最常见的小问题,或者只盯最吓人的边界案例而忽略主流程体验。

从失败模式到 eval

每个高优失败类别都要回答:最便宜、最可靠的 scorer 是什么?

失败模式首选 scorer说明
输出不是合法 JSONJSON schema assertion不需要 LLM judge
报销备注缺少项目编号schema / field assertion稳定、便宜、适合 CI
agent 声称已发送邮件但未调用工具trace assertion检查 tool call
报销单未写入财务系统草稿state verifier检查最终业务状态
回答是否语气冒犯binary LLM judge + 人工校准主观,需要标签
是否应该人工接管binary LLM judge + 领域专家标签依赖业务语境
是否泄露敏感信息deterministic rules + judge高风险,组合评估

原则是:能用程序验证的,不要交给 LLM 猜。

从失败模式到 scorer 的转换,是错误分析真正产生工程价值的地方。比如“报销备注缺少项目编号”不需要 judge;一个字段断言就够。相反,“费用信息不足时是否应该追问”很难靠字符串判断,它需要一个窄域 binary judge,并用真实样本校准。把每个失败类别都问一遍“最便宜、最可靠的 scorer 是什么”,能让 eval 系统保持简洁,而不是过早变成一堆昂贵的 LLM 评委。

构造四类 eval set

Golden set:代表主要能力

用于日常比较模型、prompt、tool schema 和 harness。它应覆盖主要任务类型和关键边界,但不能被频繁改到过拟合。

Golden set 应该像产品的核心能力清单。它不一定最大,但要代表你最在乎的用户目标和关键边界。一个差旅报销 agent 的 golden set 里应该有标准报销、超预算报销、缺失单据、项目编号缺失、审批权限不足和员工确认;一个 email assistant 的 golden set 里应该有安全邮件、可疑邮件、需要回复、不应回复、信息不足等主路径。它是日常迭代时最常看的集合,因此要避免把它调到只服务于某一次模型选择。

建议字段:

id: expense_over_budget_001
source: production_trace
input: "帮我把上周上海出差的高铁和酒店报销掉"
context:
  channel: internal_chat
  employee_id: "E1024"
  project_code: null
  receipts:
    - type: rail
      amount: 553
    - type: hotel
      amount: 1280
  policy:
    hotel_limit_per_night: 900
expected_behavior:
  - detect_missing_project_code
  - detect_hotel_over_budget
  - ask_for_project_code_and_business_explanation
  - do_not_submit_without_employee_confirmation
scorers:
  - no_unconfirmed_submission_state_check
  - over_budget_policy_check
  - required_clarification_judge

Regression set:每个修复都留下证据

每次修 bug 都加一条或多条 regression case。它的价值是防止旧失败复活。

Regression set 是团队对过去错误的记忆。没有它,修复只存在于某个工程师的脑子里、某个已经关闭的 issue 里,或者某次会议记录里。AI 系统的 prompt、模型、工具和 runtime 都会频繁变化,旧 bug 很容易以新形式回来。把每个修复转成 regression case,是让组织不再反复踩同一个坑的最简单机制。

规则:

  • bug 修复 PR 必须带 regression case。
  • regression case 要引用原始 trace 或 issue。
  • 高危 regression case 可以单独加权或设硬 gate。

Holdout set:防止过拟合

holdout 不参与日常调参。阶段性评估或发布前才运行。它可以来自:

  • 新时间窗口的真实 trace。
  • 人工构造但未公开给 prompt 调试的样本。
  • 难度匹配的新样本。

Holdout set 的作用是给团队一个相对新鲜的镜子。日常开发中,大家会逐渐熟悉 golden 和 regression cases,甚至无意中根据它们调整 prompt。holdout 则尽量保留“没有被看穿”的评估能力。它不需要神秘化,但需要纪律:少看、少调、记录使用次数,并在被频繁讨论后及时轮换。

Online sample:发现新失败

线上抽样用于监控分布漂移和新失败模式。它不一定每条都有 ground truth,但可以定期抽样给人类或 judge 标注。

Online sample 让数据集不至于变成历史展览。产品上线后,用户会提出新问题,攻击者会尝试新诱导,业务规则会变化,工具也会产生新的错误模式。线上抽样的价值不只是算一个 failure rate,而是把新发现的失败带回 open coding、taxonomy 和 regression set,让 eval 跟着产品一起演化。

Synthetic data:什么时候能用,什么时候危险

没有生产数据时,可以用 LLM 生成 synthetic inputs。Hamel 的建议不是“让模型一次生成 100 条”,而是先定义维度,再逐个组合生成AwesomeEvalsPatterns2026:

  • 功能:search、schedule、refund、handoff。
  • 场景:happy path、empty result、ambiguous、invalid、multi-step。
  • persona:新手、专家、急躁用户、恶意用户。
  • 渠道:web、internal chat、email、voice transcript。

每条合成样本都要绑定真实系统状态,例如真实 listing、真实 policy、真实 tool schema。否则会生成“看起来合理但根本触发不了系统路径”的假样本。

合成数据最适合补覆盖,不适合替代现实。它可以帮助你系统性构造边界条件:空结果、歧义意图、恶意用户、多步骤请求、罕见渠道。但如果合成样本不绑定真实状态,它很容易变成语言练习。比如生成一条“员工要报销超预算酒店”的输入并不够,你还要有真实或模拟的员工权限、发票附件、费用政策、项目编号和审批链,明确工具会如何返回,正确行为是什么。否则 scorer 通过了,也不说明生产系统会通过。

Synthetic data 的最低要求

  1. grounded:引用真实 DB 行、API schema、policy 文档。
  2. one at a time:避免批量生成导致模式坍缩。
  3. trigger verified:运行一次确认它真的触发目标场景。
  4. human spot-check:抽样检查是否像真实用户。
  5. separate from holdout:不要把生成数据当作唯一泛化证据。

Criteria drift:标准会在看样本时变化

EvalGen 的关键洞见是 criteria drift:你以为可以先写好评分标准,再评输出;但实际上,你往往是在看输出的过程中才发现标准应该怎样写EvalGen2024。

例如你最初写:

“回答必须有帮助。”

看了 30 条 trace 后,你可能发现真正标准是:

“当员工提交的报销信息缺少项目编号,或费用超过政策上限时,assistant 必须先追问缺失信息或要求业务说明,而不是调用 submit_expense。”

所以不要过早冻结 rubric。正确流程是:

  1. 先看样本。
  2. 写临时标准。
  3. 用标准标注更多样本。
  4. 记录分歧。
  5. 修订标准。
  6. 再写 judge 或 verifier。

criteria drift 不是坏事,它说明你正在学习任务本身。危险的是假装标准一开始就已经完整。很多产品质量标准只有在看见真实输出后才会变清楚:什么算“有帮助”,什么算“过度承诺”,什么时候必须转人工,什么时候允许给出替代方案。把标准的修订记录下来,比追求一开始就完美更可靠。

错误分析模板

可以把下面表格复制成 CSV/Sheet:

字段说明
trace_id原始 trace ID
sourceproduction / dogfood / synthetic / benchmark
user_intent人类理解的用户意图
channelweb / sms / email / voice / api
first_failure_note第一处最上游错误
axial_category聚类后的失败类别
severity1 到 5
business_value1 到 5
suggested_scorerassertion / verifier / judge / human
fix_ownerprompt / tool / RAG / product / infra
regression_case_id加入回归集后的 ID

常见误区

误区 1:从指标开始,而不是从失败开始。
先选一堆通用指标,往往会得到一堆不可行动的分数。

误区 2:让 LLM 自动完成全部错误分析。
LLM 可以聚类和辅助命名,但产品 taste 和业务严重性必须由人类给出。

误区 3:每条 trace 记录所有错误。
先记录最上游错误,否则 taxonomy 会混入大量症状。

误区 4:合成数据不验证触发路径。
没跑过系统的 synthetic case 可能只是漂亮的假输入。

误区 5:修完 bug 不加 regression case。
没有 regression case,修复只是临时状态。

本章练习

  1. 抽样 20 条真实或 dogfood trace。
  2. 给每条失败 trace 写一个 first_failure_note。
  3. 用 LLM 聚类成最多 8 个 axial categories。
  4. 人工重命名类别,使它们更可执行。
  5. 选前三个类别,为每个类别写一个 scorer 方案。
  6. 至少把一个失败样本改写成 regression case。

延伸阅读

On this page