第 1 课:为什么你说不清它哪儿错了
学习目标:
- 说清一个用户可见的症状底下,为什么会压着好几个从外面看完全不可区分的原因
- 讲明白非确定性怎么让「复现一次再打断点」这套传统调试直觉失去着力点
- 认出 Agent 系统里错误的几种形态:级联、轨迹改道、跨轮次复利、多 Agent 的涌现
前置要求:完成本系列前 10 门课,能手写以 stop_reason 驱动的 harness 循环,知道评测跑道只给你 pass/fail | 下一课 第 2 课 >>
一个说不清的周二下午
你的内部研究 Agent 上线两周了。周二下午,运营同事转来一条用户反馈:
我让它查我们去年的定价方案,它回我说没找到。可那份文档明明就在知识库里,我自己两秒钟就翻到了。
你打开这条会话,屏幕上只有两样东西:用户的那句问题,和 Agent 最后回的那句「我没有检索到相关资料」。中间到底发生了什么,你手里一个字都没有。
于是你开始猜。
是它把搜索词起坏了?比如把用户那句大白话原样丢进检索,搜了一长串自然语言而不是几个关键词。还是搜到了但挑来源时挑歪了?返回八条结果,它偏偏读了最不相关的那两条,然后判断「没有」。又或者,那次检索工具压根就报了错,而 Agent 把报错读成了「这个方向没有资料」,接着往下走了?
这三个猜测,从用户那一侧看长得一模一样:Agent 找不到明明就在那儿的信息。
这不是你一个人的处境。Anthropic 团队复盘他们的多 Agent 研究系统时记下过同一件事:用户会报告 Agent「找不到明显的信息」,而他们看不出为什么——是 Agent 用了糟糕的搜索词?挑了差的来源?还是撞上了工具故障1。同样三个问题,答不上来。
评测跑道只回答「挂没挂」
你的第一反应大概是打开上一门课(本系列第 10 门)建的那套东西:终态判分、验证器、评测集。这是对的第一步。你把这条真实的用户问题加进评测集,配上一句判据「回答里必须引用到那份定价文档」,跑一遍。
结果是一行红字:fail。
这行红字有用——它把一条主观抱怨变成了可复跑、可回归的判定。但它答不了你现在真正想问的那个问题:为什么。判分器看的是终态,终态是「没引到」;至于这个「没引到」是搜索词造成的、来源选择造成的,还是工具报错被当成空结果吞掉了,判分器不关心,也没有能力关心。它站在跑道终点,只负责举牌。
这门课要补的就是中间那一段。用一句本课自己的说法:
验证告诉你挂没挂,可观测性告诉你为什么。
这句话不是哪份官方文档的原话,是本课程用来串起后面五节的框架,背后有两条真实经验撑着。一条来自那个多 Agent 系统的复盘:把全链路 tracing 上到生产之后,他们才终于能诊断 Agent 为什么失败,并系统性地修问题1。另一条来自工具工程那边:把评测 Agent 留下的原始记录交给模型去分析,可以帮你探究 Agent 为什么调、或者为什么不调某个工具2。两句话指的是同一件事——只有让过程留下痕迹,「为什么」才有地方可问。
「复现一下再打断点」为什么在这儿失灵
先把词说清楚。确定性系统的意思是:给它同样的输入,它每次产出同样的输出。非确定性系统则相反——Agent 就是这类系统,即便起始条件完全相同,它也可能生成不同的响应2。
这一条把传统调试的地基抽掉了。Agent 做的是动态决策,两次运行之间是非确定的,即便提示词一模一样也是如此,调试因此变难1。传统的评估方式还默认了一个假设:给定输入 X,系统应当沿路径 Y 产生输出 Z。多 Agent 系统不这么工作——即便起点完全相同,Agent 也可能走上完全不同、但同样成立的路径去达成目标1。
具体长什么样:
两条路径都不算错,终态也都能算通过。可如果出事的是运行 A 的第 2 步,你重跑十次跑出九个运行 B,那九次都帮不上忙。
断点也一样悬空。断点挂在代码行上,前提是「第二次执行会走到这一行、且带着同样的上下文」。而 Agent 出岔子的地方常常根本不在你的代码里,它在模型的某一次决策上;就算你想停,你也不知道该停在第几轮——这次第 3 轮出岔,下次可能第 11 轮才出,也可能压根不出。
你可能会想到把采样温度调低,或者把工具返回录下来回放。这些工程手段确实能减少一部分抖动,本课后面也不反对你用。但它们改变的是你实验室里的那次运行,不是线上出事的那一次——线上那一次已经过去了,它唯一留下的东西就是记录。
Anthropic 团队给的做法是另一个思路。他们的说法叫「像你的 Agent 一样思考」:用系统里一模一样的提示词和工具搭出模拟环境,然后一步一步地看 Agent 干活。这立刻暴露出了几种失败模式——已经拿到足够结果却还在继续、搜索词写得过于冗长、选错了工具1。
重点在哪儿:不是「复现出同一次」,而是「看得见每一步」。这是可观测性和传统调试分岔的地方。
Agent 出的错,形状也不一样
就算你接受了「得靠记录」,还有一层要理解:Agent 系统里的错误,形态跟传统 bug 不是一回事。
传统软件里,一个 bug 可能破坏某个功能、拖慢性能,或者引发一次宕机。而在 Agent 系统里,微小的改动会级联成很大的行为变化,这让「为需要在长时间运行中维护状态的复杂 Agent 写代码」变得异常困难1。
具体到一次运行里,最典型的形态是轨迹改道。错误在 Agent 系统里会复利,对传统软件而言只是小问题的东西,足以让 Agent 彻底跑偏:一步失败就可能让 Agent 转去探索一条完全不同的轨迹,最后给出不可预测的结果1。
注意这条链的第一环有多小:一次超时。在传统服务里它可能只是一条重试日志,在这里它改写了后面十二轮的内容,而终态那份报告不报错、不崩溃,读起来还挺流畅。
第二个形态是跨轮次复利。Agent 是有状态的,错误会复利:它们可以跑很长时间,在很多次工具调用之间维持状态,所以你需要能持久地执行、并在过程中处理错误1。这也是为什么出错之后不能简单地从头再来——重启既昂贵又让用户难受,所以他们做成了能从出错的位置恢复1。对你的意义很直接:出错时那份状态没被记下来,「从哪儿恢复」这个问题就无从答起。
第三个形态要到多 Agent 系统里才看得见:涌现行为。多 Agent 系统会出现没被专门编程过的涌现行为——对主 Agent 的一个小改动,可能不可预测地改变子代理的行为方式;把这件事做对,靠的是理解交互模式,而不只是单个 Agent 的行为1。
这几种形态合起来是什么样子,那个团队的早期版本给过很直白的答案:早期的 Agent 会犯这类错——为一个简单查询孵出 50 个子代理、为并不存在的来源无休止地扫网、用过量的相互更新把彼此带偏1。
这三种错误有个共同点:站在外面看,终态都可能只是「回答质量不太行」。你分不出是哪一种。
抽象层会遮住证据
还有一层麻烦不来自模型,来自你的工具链。
Agent 框架让人容易起步,它们简化了调用 LLM、定义和解析工具、把调用串起来这些底层活儿。但它们往往制造出额外的抽象层,遮住底层的提示词与响应,让这些东西更难调试3。给开发者的建议因此是:先直接用 LLM API 开始,很多模式几行代码就能写出来;如果确实要用框架,得确保你理解底层的代码,因为对水面之下的错误假设是客户出问题的常见来源3。(这篇文章自己标注了其中对工具生态的描述已经过时,所以这里取的是原则部分,不当作现在的选型建议。)
这一条对你来说其实是好消息。你在本系列第 7 门课手写过 harness 循环,那层抽象你没有:
证据全在这个循环里流过,一次不落,问题只在于流过之后就没了。你没有遮挡,但也没有留存——从事后要证据的角度看,这两者一样难受。
所以本课的判断是:调试 Agent 的难,一半来自非确定性这个客观事实,另一半来自「本来能记下来的东西没记」。前一半改不了,后一半在你手里。
出路:接下来五课给你什么
那个多 Agent 系统的团队把这件事放在了和提示词、工具设计同一档的位置上:把这事做对,靠的是仔细的提示词与工具设计、扎实的启发式规则、可观测性,以及紧凑的反馈回路1。复盘里还有一句更直白的——他们把重心放在了一个由可观测性和测试用例构成的快速迭代回路上1。
注意「测试用例」这半句:可观测性不是来替代评测跑道的,它俩是一副挑子的两头。评测告诉你这次挂没挂、改完有没有变好;观测告诉你这次为什么挂、该动哪一处。
后面五节课按这个顺序推进:
- 第 2 课 先立地基:原始记录是第一手证据,Agent 的自述不算数——它省略掉的往往比写出来的更要紧。
- 第 3 课 把每一步变成数据:结构化日志和指标。同一组指标,上一门课拿来判分,这门课拿来诊断。
- 第 4 课 把散落的记录串成一棵树:一条提示词触发的所有模型请求与工具执行读成一个整体,子代理的调用嵌进父级。
- 第 5 课 在循环的生命周期关口安探头,走一遍非确定性下的定位流程:从一堆记录里找到第一处分岔。
- 第 6 课 动手:给你在本系列第 7 门课写的那个 harness 装上一整层观测,从「说不清」的症状追到具体哪一步出的岔子。
本课只点名不展开:记录怎么读是第 2 课的事,指标怎么设计是第 3 课,trace 是什么结构是第 4 课。
分寸:什么时候不用上全套
不是每个 Agent 都需要这一整套。一次性的小脚本——跑一遍、看一眼、跑完就删——给它配日志、指标和 trace 是纯粹的浪费。
判断标准不复杂:观测投入应该跟「出事之后你得花多久说清楚为什么」成正比。 自己跑、自己看、错了重来成本为零的,不用记;给别人用的、跑得久的、错了要花半天翻聊天记录的,从第一天就该记。
有一条线跟规模无关:只要 Agent 能动手——写文件、发请求、花钱——就别省测试。Agent 的自主性意味着更高的成本,以及错误复合的可能,所以建议在沙箱环境里做广泛测试,并配上合适的护栏3。
至于怎么知道自己做对了:跟任何 LLM 功能一样,成功的关键在于测量表现、并对实现做迭代3。而测量的前提是有东西可测——这就绕回了这门课要解决的事。
💻 练习
小结
- 一个用户可见的症状底下,常常压着好几个从外面完全不可区分的原因。用户报告 Agent「找不到明显的信息」时,你没法判断是搜索词差、来源挑得烂,还是撞上了工具故障1。
- 评测跑道回答的是「挂没挂」。本课用一句自己的提法把分工说清楚:验证告诉你挂没挂,可观测性告诉你为什么。支撑它的是两条真实经验——上了全链路 tracing 才能诊断失败并系统性地修1,以及读原始记录能帮你探究 Agent 为什么调或不调某个工具2。
- 「复现再打断点」失灵,是因为 Agent 做动态决策、两次运行之间非确定,即便提示词相同也一样1;确定性系统给相同输入产出相同输出,Agent 这类非确定性系统不保证这一点2。传统评估那套「输入 X 走路径 Y 出输出 Z」的假设也不成立,相同起点可以走出完全不同但都成立的路径1。
- Agent 的错误形态和传统 bug 不同:传统软件里 bug 破坏一个功能,Agent 系统里微小改动会级联成很大的行为变化1;一步失败就能让 Agent 转去探索完全不同的轨迹1;Agent 有状态、错误会复利,所以要能持久执行并沿途处理错误1;多 Agent 还有涌现行为,对主 Agent 的小改动会不可预测地改变子代理,要理解的是交互模式而非单体1。早期版本闹出过为简单查询孵 50 个子代理、为不存在的来源无休止扫网、用过量更新互相干扰这类事1。
- 抽象层会把证据挡住:框架常常制造额外的抽象层,遮住底层的提示词与响应,让它们更难调试3;建议直接从 LLM API 起步,用框架也要理解水面之下的代码3。你手写的 harness 没有这层遮挡,但也没有留存。
- 出路是把观测和紧凑的反馈回路当成一等要求1,落成一个由可观测性和测试用例构成的快速迭代回路1。后面五课依次是:原始记录、日志与指标、trace、hooks 与定位流程、动手实装。
- 分寸:一次性小脚本不必背全套。但只要 Agent 能动手,就在沙箱里做广泛测试并配上护栏3;判断做没做对,靠的是测量表现、对实现做迭代3。
>> 第 2 课:第一手证据:原始记录,而不是它的自述