各类动作的验证方式
发邮件看 message_id、写库后读回、改代码跑测试、发布后做 HTTP 检查。
一句话理解:验证就是换一条独立的路径去读世界的真实状态:发出去的查回执,写进去的读回来,改过的代码跑测试,上线的页面去访问。
上一课说明了“已完成”不等于真的完成。但落到需求评审时,大家常常卡在一个很具体的问题上:**这个动作,到底怎么验证?**说一句“要验证结果”没有用,研发需要知道读哪个接口、比对哪些字段、比对不上怎么办。
这一课给出一个通用公式,一张覆盖常见动作的速查表,再细看 Coding、发票审核和发布上线三个场景。
验证的通用公式
不管什么动作,验证都可以拆成五个要素:
有三条原则:
- 观测通道要独立。 不要拿写接口自己的返回值证明写入成功,而是用另一个读接口去查。独立程度越高,证据越可信。
- 比对具体字段,而不只是“有没有”。 工单存在但负责人写错了,仍然是失败。事先定义哪些字段是关键字段。
- 判定有三种结果,不是两种。 除了“通过”和“失败”,还有“无法判断”:例如消息已受理但投递状态要几分钟后才能查到。把第三种状态显式记录下来,才能安排延迟复查,而不是被当成成功。
动作与验证方式速查表
| 动作类型 | 例子 | 验证方式 | 保留的证据 | 注意事项 |
|---|---|---|---|---|
| 读取数据 | 查询订单、操作日志 | 结构校验、关键字段非空、取值范围检查 | 原始返回 | 空结果不等于“没有问题”,可能是数据缺失 |
| 计算分析 | 计算超时时长、金额差额 | 用代码计算,不让模型心算;结果回填给模型 | 计算输入与输出 | 注意时区与单位 |
| 发送消息 | 发邮件、发 IM 通知 | 检查返回的消息 ID;高风险场景再查投递状态 | 消息 ID、投递回执 | “已受理”不等于“已送达” |
| 写入记录 | 修改工单状态、写入审核结论 | 写后读回,比对关键字段 | 读回的记录 | 主从延迟可能导致刚写完读不到,需要短暂等待后再读 |
| 创建对象 | 创建工单、创建任务 | 按返回 ID 查询详情;检查是否重复创建 | 对象 ID 与详情 | 重试前先查是否已创建,避免建两张工单 |
| 修改代码 | 修复 Bug、加功能 | 构建、类型检查、运行相关测试与全量测试 | 测试输出、diff | 警惕“为了通过而改测试” |
| 发布上线 | 部署页面、更新配置 | 访问线上地址,检查状态码与关键内容 | 响应码、页面片段 | CDN 缓存可能返回旧版本 |
| 生成文档 | 诊断报告、分析结论 | 结构检查、数字回溯到数据源、引用可追溯、按标准评审 | 引用列表 | 语义质量可以用 LLM 评审辅助 |
| 界面操作 | 在后台页面提交表单 | 截图,并检查成功提示或列表中出现新记录 | 截图、页面状态 | 页面改版会让选择器失效 |
| 文件操作 | 导出 CSV 报表 | 检查文件存在、行数、表头与抽样内容 | 文件元信息 | 空文件也是“导出成功” |
Anthropic 在 Claude Agent SDK 的工程文章里把验证手段分成三类:基于规则的反馈(例如代码 lint,明确指出哪条规则失败、为什么失败)、视觉反馈(例如对生成的 HTML 邮件截图检查排版),以及用另一个模型按模糊标准评审(它也提醒这种方式稳健性较弱、会增加延迟)。上表中大部分行属于第一类,这也是应该优先使用的一类。
把速查表里的动作按这三类手段重新归组:
三个场景细看
Coding Agent:测试是天然的验证器
Coding Agent 之所以进步快,一个重要原因是代码领域自带反馈闭环:编译器、类型检查、测试都能给出客观的通过或失败(参见办公 Agent 与 Coding Agent)。一个好的修复流程应该是:
# 示例:Coding Agent 修复金额计算 Bug 之后的验证序列
npm run typecheck # 1. 类型检查
npm test -- tests/pricing.test.ts # 2. 先跑能复现 Bug 的用例,确认由红变绿
npm test # 3. 再跑全量测试,防止改好一个坏掉一片
git diff --stat # 4. 检查改动范围,是否碰了不该碰的文件
第 4 步经常被忽略。如果 Agent 修改了测试断言让测试“通过”,前三步全是绿的,问题却没有解决。可以用规则强制检查:diff 里出现测试文件的断言变更时,标记为需要人工复核。
把这四步和失败时的去向画出来:
发票审核 Agent:规则与数据交叉核对
发票审核 Agent 从发票图片中抽取字段,再给出“通过 / 退回”的结论。抽取结果不能只靠模型“看起来对”,很多检查可以写成确定性规则:
from decimal import Decimal
def check_invoice(inv: dict, po: dict) -> list[str]:
"""返回未通过的检查项;空列表表示全部通过。"""
issues = []
net = Decimal(inv["net_amount"])
tax = Decimal(inv["tax_amount"])
total = Decimal(inv["total_amount"])
if net + tax != total:
issues.append("价税合计不等于金额加税额")
if inv["buyer_tax_id"] != po["company_tax_id"]:
issues.append("购买方税号与采购主体不一致")
if total > Decimal(po["approved_amount"]):
issues.append("发票金额超过采购单批准金额")
return issues
审核结论写入财务系统之后,再按单据号读回,确认状态和备注已经更新。模型负责看懂发票、解释退回原因,算术与一致性交给程序。
发布上线:部署完去访问一次
假设 SEO 诊断 Agent 修改了一个页面的 title 并触发部署。“部署任务成功”只能说明流水线跑完了,真正的验证是访问线上页面:
# 示例:发布后检查(URL 为示意)
curl -s -o /tmp/page.html -w "%{http_code}\n" https://example.com/pricing # 期望输出 200
grep -o "<title>[^<]*</title>" /tmp/page.html # 期望包含新标题
grep -c 'rel="canonical"' /tmp/page.html # 期望为 1
如果状态码正确但标题还是旧的,多半是缓存问题。此时判定应为“无法判断”,并安排几分钟后复查,而不是直接报告成功或失败。
把验证结果记录下来
每次验证都应该留下结构化记录,供汇报、Tracing 与任务状态使用(示例):
{
"action_id": "act_07",
"action": "update_ticket_status",
"expected": { "ticket_id": "T-20260917-0042", "status": "fix_confirmed" },
"method": "read_back",
"observed": { "status": "fix_confirmed", "updated_by": "agent-support" },
"verdict": "passed",
"checked_at": "2026-09-17T10:21:04+08:00"
}
有了这条记录,最终汇报可以附证据,出了问题可以回查,评测时也能直接统计“写操作验证通过率”。
常见误区
- 用写接口的返回值证明写入成功。 返回值和实际写入可能不一致,应当独立读回。
- 只验证“有没有”,不验证“对不对”。 工单建了但类型和负责人写错,下游照样出事。事先列出关键字段。
- 把验证写进 Prompt,指望模型“记得检查”。 模型可能跳过。关键验证应当由 Harness 强制执行,例如在工具调用后的钩子里自动读回,参见 Hooks 与生命周期。
- 忽略异步与延迟生效。 消息投递、搜索引擎收录、缓存刷新都需要时间。立即检查失败,不代表动作失败。
设计要点
- 为每一类动作写下“预期状态、观测通道、关键字段、判定规则”四项。
- 优先使用确定性验证(读回、测试、规则、HTTP 检查),LLM 评审只用于语义质量。
- 验证结果使用“通过 / 失败 / 无法判断”三态,并为“无法判断”设计延迟复查。
- 创建类动作的重试前先查重,保证幂等。
- 验证逻辑放在 Harness 或工具层强制执行,不依赖模型自觉。
练习
以实战项目「可执行的售后处理 Agent」为对象(它能创建工单、发送通知、修改状态),为下面 5 个动作填写验证表:
| 动作 | 预期状态 | 观测通道 | 关键字段 | 失败或无法判断时怎么办 |
|---|---|---|---|---|
| 查询订单与操作日志 | ||||
| 计算超时时长 | ||||
| 创建处理工单 | ||||
| 邮件通知负责人 | ||||
| 修改工单处理状态 |
做完后再检查一遍:有没有哪一行的“观测通道”其实就是动作本身的返回值?
延伸阅读
- Building agents with the Claude Agent SDK(Anthropic):规则反馈、视觉反馈与 LLM 评审三类验证手段。
下一步
速查表里反复出现“换一条独立通道”。下一课「自我检查与外部验证」会解释为什么让模型自己检查不够,以及模型互评和程序验证各自可靠到什么程度。