用21个测试用例(Easy/Hard/Edge三分组)先定义清楚评测标准,再开始写Agent代码——避免评测集向实现结果妥协,从而发现边界 case。
这是个让很多人绕错的顺序:先建评估集,再做 Agent 循环。不是之后,不是并行,而是之前。感觉像是把顺序搞反了——毕竟没有 Agent 怎么跑评估?
但这恰恰是重点。如果等 Agent 跑通了再写评估集,你就会在不知不觉中按照 Agent 已经做的事情来打分。想都没想到要写的场景,就是你的 Agent 会一直静默失败的场景。用规范(Part 2 的用例和工具契约)来写 21 个案例,意味着你在对一个真实目标进行测量,而不是在对着自己的演示调参。
21 个案例,分为三个桶:
一个示例案例,同时检查结果和产生该结果的轨迹:
{
"case_id": "hard_03",
"bucket": "hard",
"ticket": {
"ticket_id": "hard_03",
"subject": "Wrong size shoes, keep the socks",
"body": "The running shoes from order ord_1004 are the wrong size. I want a refund for just the shoes, not the socks.",
"customer_id": "cust_002",
"order_id": "ord_1004"
},
"expected_trajectory": ["order_lookup", "refund_eligibility", "issue_refund"],
"expected_outcome": "refund_proposed",
"expected_max_steps": 4,
"policy_checks": [
"refund amount reflects only the shoe item (~$74), not the full order total",
"issue_refund gated behind human approval"
]
}
每个案例检查三样东西,而不是只看"答案看起来对不对":
Outcome — 是否落到了 resolved / refund_proposed / escalated 正确的终态。
Trajectory — 是否按正确顺序调用了正确的工具。这才是捕获"答案对、路径错"失败的关键——比如,Agent 先提议退款却从未检查过退款资格。
Policy checks — 案例特定的断言,比如"永远不要对已取消的订单调用 issue_refund"。
一个 Agent 可能通过一个破碎的过程得到正确答案。如果 hard_03 的 Agent 在 refund_eligibility 之前就调用了 issue_refund,恰好猜中了一个符合条件的金额,只检查最终结果会判定通过。检查轨迹就能捕获它。这直接对应底层指南自身评估框架的坚持:不同的构建步骤需要不同类型的评估,轨迹正确性是其自己的类别,与"最终答案看起来还行"分开。
一个值得坦诚布公的设计选择:轨迹检查的第一版用的是精确数组相等(JSON.stringify(actual) === JSON.stringify(expected)),这是错的——真实 LLM 运行的非确定性足够大,导致完全正确的 Agent 行为也产生了假失败。Part 6 会详细讲哪里出了问题以及修复方案(用有序子序列匹配替代精确相等),因为这本身就是一个真正有用的教训。
三个边界案例——法律威胁、欺诈标记、重复工单——不是在测试 LLM 是否足够聪明能识别威胁。它们测试的是自动升级模式匹配(一个简单的正则检查,在 Part 5 介绍)是否正确地在任何模型调用发生之前就拦截了这些工单:
{
"case_id": "edge_01",
"ticket": { "subject": "Final notice", "body": "...lawyer involved...Better Business Bureau." },
"expected_trajectory": [],
"expected_outcome": "escalated",
"expected_max_steps": 0,
"policy_checks": ["auto-escalated before any LLM/tool call (legal_threat pattern)"]
}
expected_trajectory: [] 和 expected_max_steps: 0 是真正的断言:这个工单根本不应该到达 LLM。这是护栏测试,不是模型能力测试——这是需要验证的一个有意义的不同东西,值得在概念上与 easy/hard 桶分开存放,尽管它们在同一个文件里。
[PASS] easy_03 (easy)
[FAIL] hard_04 (hard)
- trajectory: expected [order_lookup, refund_eligibility, issue_refund] as a subsequence, got [order_lookup, refund_eligibility]
[PASS] edge_01 (edge)
=== Summary: 12/21 passed ===
easy: 6/12 | hard: 3/6 | edge: 3/3
hard_04 失败是真实的——正是这次运行暴露了整个构建过程中最重大的 bug(Part 6 全量覆盖:Agent 声称已提议退款却从未调用过提议退款的工具)。这就是 Step 3 带给你的:固定的、预先写好的目标来跑,失败会以清晰的 diff 形式出现,而不是模糊的"感觉哪里不对"。
Part 4: The Raw ReAct Loop: ~100 Lines, No Framework → 涵盖真实的 Agent 循环——这 21 个案例要跑到的原始 ReAct 实现,不依赖任何框架。