跳到主要内容

解读智能体堆栈跟踪:在模型、工具与 Harness 之间定位故障

· 阅读需 11 分钟
Tian Pan
Software Engineer

用户报告 Agent 给出了错误答案。你打开 Trace。模型的推理过程看起来没问题。工具调用全部返回 200 OK。Harness 日志显示没有重试、没有截断、没有异常。然而,答案就是错的。于是你花了接下来的两个小时,将三个具有不同格式、不同时钟的独立日志流缝合在一起,最终发现某个工具针对特定的查询形状静默返回了 {"result": null},模型将这个 null 合理化为一个听起来合乎逻辑的事实,而 Harness 则愉快地将这个幻觉转发给了用户。这三个层级中的任何一个都没有单独记录任何警报。故障发生在连接处。

这是生产级 Agent 系统中最主要的故障模式,而大多数团队都在使用单层工具进行调试。模型团队归咎于工具。工具团队归咎于模型。平台团队归咎于 Harness。每个人都部分正确,因为 Agent 故障几乎从来不是单一组件的 Bug —— 它是三个组件之间的失配,而每个组件都在不同的“步骤”心理模型上运行。在你的 Trace 基础设施反映这一现实之前,你将不断为披着不同外衣的同类事故买单。

单层心理模型是大多数“无法重现”的 Agent Bug 的根源。传统的堆栈跟踪之所以有效,是因为每一帧都共享时钟、进程和函数调用抽象。Agent Trace 无法免费获得这些。模型存在于具有自身延迟、采样状态和缓存的远程 API 中。工具存在于具有自身重试和超时的异构服务群中。Harness 位于它们之间,拥有自己的状态机、上下文窗口算术和策略逻辑。将这些整合进一个连贯的 Trace 中是一个分布式系统可观测性问题,而大多数团队尚未意识到这一点。

三个层级,三种谎言

每个层级都会以其特有的方式失效,并且每个层级都会将自己的失败伪装成别人的问题。

模型通过虚假的工具结果撒谎 —— 当用户的问题可以清晰映射到工具调用时,模型通常能正确发出调用并等待响应;但当问题模糊或工具结果格式错误时,模型会编造一个听起来合理的答案,并表现得好像它执行了工具一样。可见的产物是一个自信、格式良好的响应。Trace 显示根本没有工具 Span,或者模型在其最终回答中反驳了 Trace 中的工具结果。如果你不专门寻找“模型声称 X,但 X 从未出现在我们记录的任何工具响应中”这一情况,你就找不到它。这看起来与返回错误数据的工具完全一样。

工具通过模式漂移(Schema Drift)撒谎 —— 工具的契约规定返回 {"items": [...]},但最近的一次部署将其更改为 {"results": [...]} ,而根据旧模式提示的模型将新响应解析为空。工具返回 200 OK,Agent 的下游行为是“抱歉,未找到相关项”。工具团队的仪表盘显示一片绿色。模型团队看到的是一个优雅处理空响应的模型。而实际的错误 —— 在没有与消费者协调的情况下发布了破坏性的模式变更 —— 从任何单一层级的视角来看都是不可见的。

Harness 通过静默状态变更撒谎 —— 上下文截断丢弃了关键指令、重试以细微不同的状态重新进入 Prompt、应用于每个租户但无人记录的采样覆盖、在思考中途切断响应的停止序列冲突。Harness 的日志说“请求正常完成”。模型的日志说“我生成 Token 直到看到停止序列”。两者都不会主动告诉你停止序列位于用户输入的 JSON 值内部。只有当你手动重放请求并注意到模型实际收到的 Prompt 比代码认为发送的要短时,你才会发现这一点。

统一 Trace 真正需要什么

解决方案不是“更多日志”。大多数团队生成的日志已经太多了。解决方案是一个单一的、按时间排序的、结构化类型的视图,它交织来自所有三个层级的事件,并将它们视为一个图中的节点,而不是三个表中的行。

这种视图的最小可行形态,是为每一次 Agent 运行捕捉一个由模型轮次、工具调用和 Harness 事件交织而成的序列,并置于共享的单调时间线上。每一个模型轮次都携带模型实际看到的完整 Prompt(包括检索到的上下文、系统提示词、对话历史以及任何针对每个请求的修改)、产生输出的采样状态(Temperature、top-p、如果有的话还有 Seed,以及模型快照)以及任何 Harness 后处理之前的原始输出。每一个工具调用都携带模型发出的精确参数、工具返回的精确响应以及延迟。每一个 Harness 事件都捕捉重试、上下文截断、停止序列触发、采样覆盖以及任何改变了请求或响应的策略决策。

在 2024–2025 年间趋于稳定的 OpenTelemetry GenAI 语义约定为这一视图提供了词汇表。invoke_agentchatexecute_toolembeddings 的 Span 提供了跨供应商的互操作性,使得在一个平台捕捉的 Trace 可以在另一个平台进行有意义的重放。截至 2026 年初,这些约定仍被标记为实验性,但在 LangSmith、Langfuse、Arize Phoenix、Datadog、Honeycomb 和 Helicone 中的采用速度极快,以至于 “OTel GenAI” 现在已成为事实上的基准。如果你今天正在构建 Trace 基础设施却不发送 OTel GenAI Span,那么你正在让自己陷入供应商锁定,而明年的调试器重构将不得不撤销这一切。

原因假设面板胜过日志墙

即使有了统一的 trace,凌晨 2 点的值班工程师仍在扫描一个 4,000 token 的 JSON 数据块,试图找出异常。下一个有意义的步骤是自动根因归属:一个能在给定标记的故障后,根据特征模式提出哪一层最可能负责的面板。

有些模式是机械性的,值得优先自动化。如果模型的最终回答引用了日志中任何工具响应里都未出现的实体,那么假设就是“模型幻觉”。如果一个工具 span 返回了 200 OK,但其 payload 的形状与模型获得的 schema 不符,那么假设就是“工具契约漂移”。如果 Harness 记录了一个上下文截断事件,而被丢弃的 token 中包含模型随后询问的内容,那么假设就是“上下文窗口欠载”。如果使用不同的采样状态触发了重试,那么假设就是“非确定性的 Harness 行为”。这些假设本身都不是结论性的——但每一个都能将工程师的搜索空间从“整个 trace”缩小到“这个特定的 span 及其相邻节点”。

2025 年的 TraceCoder 研究系列表明,多智能体调试循环——一个智能体注入诊断探测,一个执行因果 trace 诊断,一个验证修复——可以在基准任务上将修复质量提高 34% 的相对提升。这表明“自动原因假设生成”不是锦上添花,而是一个关键杠杆点。即使是一个完全不使用模型的基于启发式算法的假设面板,也能击败目前大多数团队采用的手动工作流。

可复现性封包才是你真正需要的产物

最棘手的 Agent bug 是那些无法复现的。用户提交一个提示词,Agent 失败了;用户重新提交同样的提示词,Agent 成功了。团队将其归咎于非确定性并继续前进。这几乎总是错误的。Agent 的输出是非确定性的,但它们的行为是由一个在原则上可以完全捕获的状态封包(envelope)决定的:模型版本和采样配置、包含检索分数的完整检索上下文、运行时注册的工具目录、包括任何针对租户覆盖的 Harness 运行时配置,以及工具读取的任何外部状态。

区分“我们抓住了 bug”与“我们无法复现”的纪律,在于捕获每次运行的封包,而不仅仅是标记失败的运行。仅在出错时记录封包是漏掉 bug 的药方,因为有些 bug 的异常信号是“这次运行看起来没问题,但实际上产生的输出质量略差”。每次运行都记录成本很高,但成本是有限的——大多数封包字段都是小型结构化数据,你可以对检索到的上下文块进行哈希处理,从而在存储层去重。

可复现性封包还可以实现跨运行的结构化差异比对(diffing):当出现回归(regression)时,你可以在之前的提示词版本上回放失败的输入,差异比对会突出显示发生变化的确切封包元素(从索引中消失的单个检索文档、发生偏移的采样参数、契约发生变动的工具),事后分析也从推测转向了技术取证。如果没有封包,你就是在对比 4,000 token 的文本输出并眯着眼睛瞎猜。

明确跨层心理模型,否则就等着“甩锅大赛”

闭环的架构认识是:一个 Agent 是分布在模型、工具、Harness 这三个组件上的分布式系统。分布式系统的可观测性在原则上是一个已解决的问题,但 Agent 生态系统表现得好像它不是。假装没看见的代价体现在事件复盘中:模型团队、工具团队和平台团队各自展示自己那一层的日志,所有的日志互不矛盾,却没人能解释发生了什么。这就是“甩锅大赛”,而如果你的追踪工具设计成分别捕获三层且从不合并,那么它在暗示这种结果。

已经跨越这一阶段的团队有一些共同的习惯。他们从每一层都发出 OTel GenAI span,这样 trace 默认就是可关联的。他们捕获每次运行的可复现性封包,而不仅仅是失败的运行。每当怀疑出现回归时,在任何人推测原因之前,他们都会针对已知良好的基线运行结构化差异比对工作流。他们投资于原因假设面板——即使是一个粗糙的面板——这样值班工程师就不必用肉眼解析 JSON。他们将追踪基础设施视为一个一等的、季度性迭代的产品表面,而不是上线时勾选的一个复选框。

对于工程负责人来说,前瞻性的做法是停止将 Agent 的可观测性视为这三层中某一层的功能。模型层的工具抓不住工具契约漂移;工具层的工具抓不住幻觉产生的工具结果;Harness 层的工具抓不住采样状态的回归。联合可观测性必须存在于某个地方,如果你不决定在哪里,答案就会变成“存在于下一个处理模糊故障的值班工程师的脑子里”。这不叫策略。这是对你最资深工程师征收的税,以故障处理时间的形式支付,直到你构建出你的技术栈真正需要的调试器。

References:Let's stay in touch and Follow me for more thoughts and updates