为什么必须验证
Think → Act 之后缺失的 Verify,是 Demo 与产品的分界线。
一句话理解:Agent 说“已完成”只是一个说法,验证拿到的才是证据。没有 Verify 的 Agent Loop,本质上是一个很会自信汇报的脚本。
几乎每个 Agent 项目都会碰上这件事:Demo 阶段一切顺利,上线第一周就有人投诉:“Agent 说已经给客户发了处理结果邮件,客户什么都没收到。”翻 Trace 一看,邮件接口返回了限流错误,模型却把“我调用了发送工具”当成了“邮件已经送达”,然后写下了那句“已为您成功完成”。
这不是模型不够聪明,而是系统里少了一步。这一课讲清三件事:为什么 Agent 天生容易“谎报完成”,验证应该放在哪些位置,以及加上验证之后产品会有什么不同。
先用一张图看清本模块的全貌:验证嵌在每一次行动之后;验证发现问题时,先归类失败,再决定重试、换路还是交给人。后面四课分别展开图中下方的四个面板。
缺失的那一步
在初识 Agent Loop里,我们把 Agent 的核心描述为 Observe → Reason → Act → Verify。但很多 Demo 实际跑的是另一条路径:
Anthropic 在《Building effective agents》中强调,Agent 在执行过程中的每一步都要从环境中拿到 “ground truth”(例如工具调用结果或代码执行结果)来判断进展。Claude Agent SDK 的工程文章也把 Agent 的工作循环概括为:收集上下文 → 采取行动 → 验证工作 → 重复。两者说的是同一件事:行动之后必须回头看世界真实发生了什么。
“说完成”和“真完成”之间隔着什么
下表是 Agent 最常见的几种“声明与事实不一致”:
| Agent 的说法 | 实际可能发生的事 | 为什么 Agent 自己不知道 |
|---|---|---|
| 邮件已发送 | 接口超时或被限流,邮件没发出 | 只看到“发起了调用”,没检查返回 |
| 工单已创建 | 创建成功,但负责人字段写错了 | 接口返回成功,但没有读回内容核对 |
| Bug 已修复 | 改了代码但没跑测试,另一处功能坏了 | 没有执行任何测试 |
| 用户收不到通知的原因是“他自己关了提醒” | 日志接口返回了空数组,结论是编出来的 | 数据缺失被“合理推理”填补 |
| 页面已上线 | 构建成功,但线上仍是旧版本 | 没有在发布后访问线上地址 |
背后有三个结构性原因:
- 模型生成的是“最合理的下一段文字”。 执行完一串动作后,“已为您完成”恰恰是最顺的续写。这和幻觉同源:流畅不等于真实。
- 工具的返回本身就可能含糊。 很多接口只表示“请求已受理”(例如 HTTP 202),真正的处理在后台异步完成;还有的接口批量处理时部分成功、部分失败,返回码却仍然是成功。
- 长链路的误差会累积。 假设每一步独立出错,单步正确率 95%(示例数字),10 步全部正确的概率约为 0.95 的 10 次方,大约 60%;20 步只剩约 36%。步骤越多,越需要在中途把错误拦下,而不是等到最后才发现。
验证放在哪里:三个层次
验证不是只在任务结束时做一次,它分布在三个层次上:
| 层次 | 验证什么 | 什么时候做 | 工单处理 Agent 的例子 |
|---|---|---|---|
| 步骤级 | 单个动作是否真的生效、返回是否可用 | 每个有副作用的动作之后,或关键数据读取之后 | 创建工单后按 ticket_id 读回,核对标题和负责人 |
| 任务级 | 整体产出是否正确、完整、有依据 | 交付给用户之前 | 报告里的每个时间点和数值都能在工具返回里找到出处 |
| 业务级 | 是否真正达成了业务目标 | 往往要延迟一段时间才能确认 | 48 小时后检查该工单是否被关闭、用户是否不再反馈 |
还要分清验证和程序校验的分工:执行前的校验回答“能不能做、该不该做”(参数合法吗、有权限吗、要审批吗);执行后的验证回答“做成了没有、做对了没有”。两者缺一不可:
案例:工单处理 Agent 加上 Verify 之后
以一个「工单处理 Agent」为例。任务是:输入工单号,查询工单记录、操作日志与账号配置,判断问题原因;如果属于配置错误,就创建修复工单并邮件通知负责人。
没有验证时,Agent 的汇报通常是一句话:“已查明原因为通知配置被关闭,已创建工单并通知负责人。”加上验证后,至少多了这几个检查点:
- 查询日志后:如果日志为空,不允许推理出“用户已阅读”或“用户自己关闭了提醒”,只能标注数据缺失。
- 形成结论前:结论引用的最后一次变更时间必须能在日志工具的返回中找到原值,间隔时长由程序计算,而不是让模型心算。
- 创建工单后:按返回的 ticket_id 调用查询接口读回,核对标题、问题类型、负责人。
- 发送邮件后:检查邮件服务返回的消息 ID;没有 ID 或返回报错,就不能声称“已通知”。
最终交付的不再是一句话,而是一份带验证状态的结果(示例):
{
"case_id": "T-20260917-0011",
"conclusion": "账号的通知配置被关闭,用户收不到提醒",
"evidence": [
{ "source": "get_account_settings", "field": "notify_disabled_at", "value": "2026-09-14T08:12:00+08:00" }
],
"actions": [
{ "type": "create_ticket", "ticket_id": "T-20260917-0042", "verified": true, "method": "read_back" },
{ "type": "send_email", "message_id": null, "verified": false, "reason": "邮件服务返回 503,已重试 2 次仍失败" }
],
"status": "partially_completed"
}
注意 status 是“部分完成”,而不是“成功”。验证最大的产品价值,是让 Agent 能诚实地说出“我做了一半”:用户知道工单建好了,也知道邮件还没发出去,可以自己补一步。怎么把这种状态展示给用户,见失败的呈现。
当然,验证有成本:多一次读回就多一次调用、多一点延迟和 Token。所以验证强度应该跟着操作分级走:只读查询做轻量检查(结果非空、字段合法);写入、发送、涉及资金的动作必须做强验证。
常见误区
- “工具返回成功就是成功。” 返回码只能说明请求被接受,不能说明结果正确。写入要读回,发送要看回执,发布要去访问线上地址。
- “让模型再检查一遍就算验证了。” 同一个模型对着同一份上下文“再想想”,发现不了它本来就不知道的错误。验证要尽量换一条独立的通道去读真实状态,详见自我检查与外部验证。
- “验证越多越好,最好每一步都让人确认。” 过度验证会让 Agent 又慢又贵,用户也会嫌烦弃用。验证强度要和风险匹配。
- “验证是研发的事。” “什么才算完成”是产品定义。产品经理不写清楚完成标准,研发就只能验证“接口有没有报错”。
产品经理检查清单
- 这个 Agent 的每个任务,“完成”有没有一个可以观测、可以检查的定义?
- 每个写入、发送、发布类动作之后,用什么证据证明它真的生效了?
- 最终汇报能不能区分“已验证完成”“未验证”“失败”和“部分完成”?
- 验证失败时,用户会看到什么?Agent 接下来会做什么?
- 哪些结果只能延迟验证(例如客户是否收到、问题是否真的解决),由谁、在什么时候跟进?
- 验证本身的成本(延迟、调用次数)是否计入了单任务成本?
练习
找一个你能用上的 Agent 产品(例如 Claude Code,或你自己搭的 Tool Agent),让它完成 3–5 个带实际动作的任务,然后填写下表:
| Agent 声称完成了什么 | 你用什么方式核实 | 核实结果 | 如果不一致,Agent 缺了哪一步验证 |
|---|---|---|---|
| 例:修改了配置并重启服务 | 访问健康检查地址 | 服务未启动 | 重启后没有检查进程与端口 |
完成后统计:有几次“声称完成”和“真实完成”不一致?
延伸阅读
- Building effective agents(Anthropic):关于从环境中获取 ground truth、停止条件与沙箱测试的论述。
- Building agents with the Claude Agent SDK(Anthropic):“收集上下文 → 采取行动 → 验证工作”的循环,以及规则反馈、视觉反馈、LLM 评审三类验证手段。
下一步
知道了为什么必须验证,下一课「各类动作的验证方式」给出一张可以直接套用的速查表:发邮件、写库、改代码、发布上线分别该怎么验证。