技术
Agent验收:从看答案改为看旅程证据
把「成功」写成可失败的句子,把「为什么通过」留成证据
只断言「回复里有没有那句话」的验收,放过的正是 Agent 最贵的错误——循环、重复调工具、烧 Token,最后交出一段礼貌通顺、看起来正确的回答。这套方法把验收对象从最终答案换成旅程证据:先把「成功」写成可失败的句子(六句约束对应六类证据),再建风险矩阵按节点留证,头一周人工读 10–20 条高风险 Trace 攒出失败词典,然后把高频错误改写成断言,用「单元测试 / 确定性指标 / LLM Judge / 集成测试」四层判定,最后挂上双线门禁——总体基线不退化 + 核心风险项零回归,且延迟、Token、调用次数、副作用和正确率进同一张报告。
方法 · 一图看懂
1
把「成功」写成可失败的句子 — 以「查退款进度」为例,产品定义不能只写「用户能查退款状态」,要补齐六句:必须确认身份 · 必须拿到订单标识 · 只能读属于当前用户的订单 · 状态必须来自订单服务 · 没有结果时不得猜测 · 超时限时给人工入口。六句对应六类证据(身份校验记录 / 参数来源 / 权限判断 / 工具返回 / 回复事实一致性 / 失败分流),共同回答「为什么通过」而不是只留一个分数。
2
建最小风险矩阵 — 五个风险节点各写三列——必须发生 / 禁止发生 / 留存证据。身份确认禁凭自然语言猜用户(`auth_result`)· 参数获取禁模型补写订单号(`argument_source`)· 查询工具最多一次有效调用(`tool_name/attempt`)· 回复生成禁把「受理」写成「到账」(`tool_output/answer`)· 失败处理缺信息时追问或转人工(`stop_reason`)。
3
Day 0 人工读 Trace,先攒失败词典 — 挑 10–20 条高风险 Trace,每条留全:用户输入 · 模型版本 · 提示词哈希 · 工具名/参数/参数来源 · 工具返回 · 重试次数 · 最终回复 · Token/延迟;用红笔标三类位置——犹豫与循环、错误工具或参数、无证据支撑的结论。产物不是平均分,是失败词典(「缺订单号仍调用」「状态枚举被自然语言改写」「超时后凭历史消息补答案」「同一副作用动作被重复调用」)。
4
Day 1 判断分四层,不押宝一个 Judge — 单元测试(参数转换 / 路由)· 确定性指标(订单号、工具名、调用次数、状态顺序、时延预算)· LLM Judge(语义类,且尽量用布尔问题:「是否在调用退款工具前确认了订单归属」,而不是「回答质量几分」)· 集成测试(真实服务、权限、副作用)。
5
Day 2 门禁走两条线 — 总体基线不能明显退化 + 核心风险项零回归——平均分会掩盖「重复退款」这类小类退化;且延迟 / Token / 工具调用次数 / 副作用必须进同一张报告:回答正确但调 12 次工具、等 40 秒,不算可接受结果。
6
失败词典要成为资产 — 每条失败绑定「触发样本 · 可观察证据 · 责任边界 · 回归频率」;样本可过期但不得静默删除——标注规则版本、失效原因与替代样本,才能区分「这次不测它」与「它没有失败」。
原理 · 为什么有效
Agent 最贵的错误发生在最终输出之前:它可以陷入循环、重复调工具、烧掉大量 Token,最后仍交出一段礼貌通顺、看起来正确的回答;只断言「回复里有没有那句话」,这些错误全部会被判通过——因为「答案对不对」是一个点,「旅程对不对」是一条线,点落在线内才算过。把证据钉在中间节点(`auth_result` / `argument_source` / `tool_name/attempt` / `tool_output`),判定方式就从「打分」变成「举证」:不评价回答有多好,只回答为什么可以通过,判据边界由人先固定,模型不再既当运动员又当裁判。另一层机理是平均值的掩盖效应:一千条里三次重复退款会被平均分吞掉,所以门禁必须双线,核心风险项单独设零回归;而语义判断坚持布尔问题而非几分制,也是同一个道理——可证伪的二元判据能复现,五分区间的判据靠不住。
适用场景
适用:已经在跑带工具调用、多步编排的 Agent,并且踩过「回复看着没问题、上线出事故」;能拿到 Trace(工具名、参数、参数来源、返回、重试都落了日志);团队能抽出 1–3 人做一周集中标注;链路上有高风险动作(退款、下单、改权限、对外发消息),即失败有真实后果。
不适用:单轮问答或纯 RAG 检索——没有旅程可看,断言最终文本就够了;还没有 Trace 基础设施、日志里连工具参数都没留——先去补埋点,再谈门禁;模型两天一换、指标天天飘的探索期——此时只存 Trace,门禁等结构稳定再上;靠人工抽检每次只覆盖几条的团队——先把采样和覆盖率解决,不是先上 LLM Judge。
自测 · 6 问
你的验收用例里,有没有一条写的是「不能发生」,而不全是「要出现」?
某条用例没过时,你能从日志里直接指出「为什么没过」的那个字段名吗?
有没有哪条断言只看最终回复文本,中间调了什么工具、传了什么参数完全不管?
你的 Judge 问的是布尔问题,还是「给这段回答打个分」?
门禁除了总体平均分,有没有「核心风险项零回归」这一条?延迟、Token、调用次数、副作用是否和正确率在同一张报告里?
失败样本被删过吗——删的时候留了失效原因和替代样本吗?
怎么上手 · 验证步骤
三人团队一周的最小落地顺序。第 1 天:只选一条链路(要带真实副作用、且确实出过事的那种),凑 5 条正常 + 5 条故障样本,全部存成 JSON Trace,人工逐条标注,把高频错误改写成断言——例如 `len(tool_calls) <= 2`、每次调用都带 `order_id`、`answer_state == tool_state`、`token_total <= 6000`。第 2 天:把这批断言挂成离线回归,之后每次改 Prompt 或工具 Schema 都重跑一遍。第 3–5 天:从 10–20 条高风险 Trace 里攒出第一版失败词典,挑出 3–5 条最贵的失败,补上确定性指标与布尔式 Judge 问句。第 2 周再把双线门禁接进 CI——总分基线 + 核心风险项零回归,效率项(延迟 / Token / 调用次数 / 副作用)并进同一张报告。
拿三条对照检验门禁是不是真的在看旅程:① 把某条 Trace 的 `answer` 换成一段通顺但错的回答,门禁应当报红(不报红 ⇒ 你的断言还在读最终文本);② 把 `tool_calls` 从 2 次改成 12 次、把延迟拉到 40 秒,报告应当变红(不变 ⇒ 效率项没进同一张报告);③ 随机抽一条「通过」的 Trace,只用留存字段能否复述它的完整旅程(复述不出 ⇒ 证据不全,字段要补)。