第 2 课:第一手证据:原始记录,而不是它的自述
学习目标:
- 说清 Agent 的自述为什么只能算二手材料,并知道「它省略的比它写出来的更要紧」具体指向记录里的哪些位置
- 认得出什么算原始记录:一次运行里每个工具调用的工具名与完整参数、每个工具响应的完整返回或报错,并能把一处可疑行为定位到「第几条消息的哪个字段」
- 用四问(调错了工具、参数传错了、调得太少、把响应处理错了)把一段记录读成分类结论,并知道哪些问题人眼读得出、哪些该交给程序
前置要求:读完第 1 课,理解非确定性怎么让「复现再调试」失灵、一个症状底下压着几个不可区分的原因 | 上一课 第 1 课 << | 下一课 第 3 课 >>
它说它交叉核对了三个来源
一个调研 Agent 跑完,收尾时给你一段话:
我检索了三个权威来源并做了交叉核对,数据一致。
你信了。这句话读起来专业、有分寸,还主动交代了方法。你把结果发给了业务方。
三周后用户投诉:那个数字错得离谱。你翻开这次运行的原始记录一条条看下来——它只成功调了一次搜索;第二次调用是抓取官方页面,返回的是一行超时报错;第三次调用压根没发生过。「交叉核对」这四个字,只存在于它给你的那段总结里。
这不是模型在骗你——它写那段话时并没有「核对次数」可查,只是在续写一段听起来该有的收尾。代价却是实打实的:你信了自述、跳过了记录,花三周才知道这次运行从第二步就断了。
第 1 课的结论是:Agent 在两次运行之间是非确定性的,「复现一下再打断点」这套传统直觉直接失灵,出路是让运行过程自己留下证据。本系列第 10 门课讲评测时顺带说过一句「Agent 的自述不能当证据」,那是在裁判和打分的语境里一句带过,这一课把它展开成一套读法。
自述是二手材料
Agent 给你的自述——收尾总结、思维链(chain of thought,模型在给出答案前写出来的那段推理过程,下面简称 CoT)、工具执行完之后的自我评价——都属于它写的关于它自己的文字。这些文字有用,但它们和「它实际做了什么」之间隔着一层。
隔的这一层,最要命的地方不是它会写错,而是它会不写。Anthropic 的工具工程文章讲得很直白:Agent 在反馈与回复里省略的内容,往往比它写出来的更要紧;LLM 说出来的,未必就是它心里那回事1。省略比写错难对付——写错了你有机会撞见矛盾,省略了你连撞的机会都没有。上面那个例子里它没写「第二个来源抓取失败」,只是不提,而缺失的部分不会举手。
所以顺序是:读它的推理和反馈(也就是 CoT)能帮你找到毛糙的地方,但要抓出 CoT 里没有明说的行为,得去看原始记录,包括工具调用与工具响应1。CoT 是线索,记录是证据。两者颠倒,你就会反复得出「它说它做了,那大概是做了」这种结论。落到操作上:拿自述里的每个具体数字去记录里找出处,找不到出处、也从记录里的数字推不出来的,就是它自己生成的。
什么算「原始记录」
「原始记录」不是新东西,你在本系列第 7 门课已经亲手造过:那个 messages 数组。每轮循环往里追加一条 assistant 消息、再追加一条带工具结果的 user 消息,跑完一次任务,数组里沉淀下来的就是这次运行的原始记录。
摊开看就两类块。tool_use 块是模型点名要调哪个工具,里面有工具名和一份完整的参数对象,参数由模型自己生成。tool_result 块是宿主把工具真跑一遍之后回传的东西,成功是完整返回值,失败是报错内容加一个错误标记。两个块必须成对读:单看 tool_use 只知道它想干什么,单看 tool_result 只知道环境回了什么。
为什么它们够格当证据?Anthropic 关于 Agent 模式的文章讲的是 Agent 自己的视角:执行过程中,Agent 必须在每一步从环境获得 ground truth(环境给出的、不由模型自己编的事实——比如工具调用结果、代码执行结果)来评估自己的进展2。同一批东西换你来读,作用一样——它判断自己走到哪了靠这些,你判断它走到哪了也只能靠这些。
同一篇文章还给了一条设计原则:优先保证透明度,把 Agent 的规划步骤显式地展示出来2。这条常被当成 UI 建议,但它对调试的含义更实在——凡是没落到工具边界上的动作,都不会在记录里留下痕迹。模型「在脑子里比对了两份数据」不留痕,调一次 diff 工具就留痕。你想让哪件事可查,就得把它挤到工具边界上去。
人眼能看见评测看不见的东西
到这儿会有人问:本系列第 10 门课不是刚建好评测集吗?跑分不就完了,为什么还要人一条条翻记录?
因为评测集只能发现你已经想到的失败。人工测试能抓到评测漏掉的边缘情况——罕见查询上的幻觉、系统性的故障、以及微妙的来源选择偏差3。第三类最值得琢磨:Anthropic 的多 Agent 研究系统里,人工测试者注意到早期的 Agent 一贯偏爱经过 SEO 优化的内容农场,而放过学术 PDF、个人博客这类权威但排名没那么靠前的来源3。
这个偏差在评测里几乎看不出来:答案不一定是错的,内容农场也会抄对事实,跑分一直挺好看。它从头到尾只写在一个地方——记录里那一串它真实挑中的 URL。
工具这一层也有同性质的例子。Claude 的网页搜索工具上线时,团队发现 Claude 会没必要地往查询参数里追加 2025,这让搜索结果产生偏斜、表现变差;修法是改进工具描述,把 Claude 引导回正轨1。这个 bug 不崩溃、不报错、不超时,工具每次都调用成功,返回也是合法的搜索结果。指标顶多告诉你「表现变差了」;要指名道姓说出「差在 query 里多了一个 2025」,只能靠逐条读参数这一层。
记录不只给人读
上一节容易读出一个误解:翻记录 = 人肉逐条看。不是——记录是结构化文本,模型很擅长读它。工具工程文章给了一个近乎粗暴的做法:把评测 Agent 的记录直接拼接起来,粘进 Claude Code——模型是分析记录的行家,还能顺手一次性重构一大批工具,比如保证工具实现与工具描述保持自洽1。
「读记录」的瓶颈在耐心,不在智力。分工于是很清楚:模型适合覆盖式粗筛,把一批记录压成一份可疑清单;人适合定性,判断捞出来的样本算不算问题。
别人的记录格式是别人的内部实现
既然记录这么重要,直接读现成产品落在磁盘上的记录行不行?拿 Claude Code 举例,它会把会话转录持久化下来,落在 ~/.claude/projects/*/*.jsonl 这些 JSONL 文件里4。看上去很理想:一行一条消息,现成的、结构化的原始记录。
但官方文档紧跟着一条告诫:转录条目的格式是 Claude Code 的内部实现、会在版本之间发生变化,所以一个依赖这些字段做联接的管道可能在任何一次发版之后失效,这类联接应当按「版本特定」来对待,而不是当成稳定契约4。hooks 文档还有第二条:hook 拿到的那个转录文件是异步写入的,可能滞后于内存里的对话,所以 hook 触发时它还不一定包含当前这一轮最近的几条消息5。
两条教的是同一个道理:别人的格式不归你管,写入时机也不归你管。你可以读它、可以用它排查,但不能把自己的观测建在上面。结论是:你自己的 harness,要自己落自己的记录。第 3 课讲怎么设计这份记录的字段,第 6 课把它写进你在本系列第 7 门课写的那个 harness 里。
读记录的四问
翻开一段记录,从哪儿开始看?工具工程文章给了一个现成的分类:Agent 可能调错了工具、可能调对了工具但传错了参数、可能调得太少、也可能把工具响应处理错了1。这四项本来是讲工具设计的,但当成读记录的检查清单极好用,因为它们在记录里各有各的指认位置:
一问:调错了工具。 看每个 tool_use 块的 name 字段,对照当时的任务和上一条工具响应,判据是「有没有一个更该用的工具摆在那儿没被用」。
二问:调对了工具,参数传错了。 看 tool_use 块的 input 字段,一个键一个键地看。前面那个往查询词里塞 2025 的例子就住在这一问里。它最容易被跳过,因为参数又长又像模像样——专门盯那些你能独立判对错的键:日期、路径、ID、范围上下界、单位。
三问:调得太少。 这一问的证据是缺失,所以最难读。指认方式是找那些「本该有下一次调用却没有」的位置:报错之后没有重试、搜索返回五条却只抓了一条、任务要求两个来源而记录里只有一次成功往返。缺失不会自己跳出来,你得拿任务要求当模板去数。
四问:把工具响应处理错了。 看每条 tool_result 之后紧跟的那条 assistant 消息,问一句「它接下来说的话,和它刚拿到的东西对得上吗」。报错被当成成功、返回的是 A 期数据却被当作 B 期用、返回里明写着「结果被截断」而它照单全收——都在这一问下面。它还和报错信息的写法直接相关:工具抛错时,可以对错误响应做提示词工程,让它传达具体、可执行的改进建议,而不是甩出不透明的错误码或 traceback1。看到 Agent 反复误读同一类报错,先去看那条报错本身写了什么。
分寸:不是每条记录都要人逐字读
人读记录是贵的动作:一次运行几十条消息,认真读一遍十几分钟起步,跑一百次就没人读得动了。所以人读只用在两个地方。一是抽查:定期随机挑几次运行读一遍,不为找具体的 bug,是校准你对「它平常怎么干活」的直觉——SEO 内容农场那类偏差,正是「不为找 bug 而读」才撞得见的东西。二是给新问题定性:碰上没见过的症状,头一两次必须人读,因为你还不知道该找什么;等你读明白它在哪个字段上现形,就该交给程序了。
日常的量靠程序化的读法扛:日志字段怎么设计是第 3 课,怎么把散落的记录关联成一次运行是第 4 课。到那时人读的入口会变成「先用指标定位到可疑的几次运行,再摊开记录细看」。证据先要存在,然后才谈得上怎么高效地读——这一课管前半句。
💻 练习
小结
- Agent 的自述是二手材料:它在反馈与回复里省略的内容往往比写出来的更要紧,LLM 说出来的未必是它心里那回事1,而缺失的部分不会在总结里举手
- 读 CoT 能找到毛糙的地方,但要抓出 CoT 里没有明说的行为得看原始记录,包括工具调用与工具响应1——CoT 是线索,记录是证据
- 原始记录就是本系列第 7 门课那个
messages 数组沉淀下来的东西。它够格当证据,是因为工具结果、代码执行是环境每一步给出的 ground truth2;官方还有一条「优先保证透明度、把规划步骤显式展示出来」2——反过来读就是本课的引申:凡是没落到工具边界上的动作都不会留痕
- 人工测试能抓到评测漏掉的边缘情况:罕见查询上的幻觉、系统性故障、微妙的来源选择偏差3;真实案例是早期 Agent 一贯偏爱 SEO 内容农场、放过学术 PDF 与个人博客3——这类偏差落在记录里,就是那串它真实挑过的 URL。工具层的 bug 也住在参数里:Claude 曾往搜索工具的查询参数里没必要地追加
2025,让结果偏斜、表现变差,修法是改进工具描述1
- 记录不必只由人读:把评测 Agent 的记录拼接起来粘进 Claude Code,模型很擅长分析记录,还能顺手保证工具实现与描述保持自洽1。读记录的四问是:调错了工具、调对了工具但传错了参数、调得太少、把工具响应处理错了1
- 别人的记录格式是别人的内部实现。Claude Code 把会话转录落在
~/.claude/projects/*/*.jsonl4,但官方明说这个格式是内部实现、版本之间会变,靠它做联接的管道可能在任何一次发版后失效,应按版本特定对待4;hook 拿到的转录文件还是异步写入的,可能滞后于内存里的对话5。所以你自己的 harness 要自己落自己的记录——第 3 课设计字段,第 6 课动手写
>> 第 3 课:结构化日志与指标:把每一步变成数据