跳到主要内容

难以调试的庞大 Agent 追踪:当记录了一切却读不懂任何内容时

· 阅读需 12 分钟
Tian Pan
Software Engineer

关于 Agent 可观测性的标准建议只有三个词:记录完整 trace。捕获每一次工具调用、每一个 prompt、每一条模型响应、每一次内存读写。团队照做了。接着第一个真实故障发生了,工程师打开 trace,发现它有 40 层工具调用深,20 万个 token 宽。从技术层面看,trace 是完整的;但从实践层面看,它完全不可读。

接下来是熟悉的仪式。工程师不断滚动屏幕。他们展开一个 span,看到 5 万个字符的 JSON,折叠它,再次滚动。十分钟后,他们终于找到了那个模型选错工具的回合——它被埋在 37 个完全符合预期的回合之间。原本旨在让故障清晰可见的 trace,反而增加了排查成本。

这并不是工具上的短板。任何可观测性供应商都会很乐意摄取你的 Agent span,而且 OpenTelemetry GenAI 语义约定现在已经为你提供了 invoke_agentchatexecute_tool 等 span 的清晰 schema。真正的差距在于观念。我们沿用了一套对“可观测性”的定义——“我们捕获它了吗”——这对于请求-响应式服务是正确的,但在 Agent 领域却悄然失效了。对于 Agent 来说,捕获是最简单的部分。最难的部分,即决定一个故障是在 20 分钟还是两天内解决的关键,在于人类能否在故障发生的热度期内,找到那一个真正关键的关键点。

捕获的完整性不等于可观测性策略

在传统服务中,一个 trace 可能只有 5 到 15 个 span,其结构与你脑海中已有的调用图是一致的。你在看之前就知道它的形状。调试主要是按顺序读取 span,并留意哪一个是红色的。

Agent trace 打破了这三个假设。首先,它的结构是涌现的——Agent 决定采取多少步骤、调用哪些工具以及何时循环,因此在你读完之前,你并不知道它的具体形状。其次,数据量是巨大的——一个包含 8 次工具调用的运行通常会产生 5 万个 token 的日志输出,而一个携带完整 prompt 的 span 本身就可能有 20 万个字符的 JSON。最后,故障很少表现为红色的 span。Agent 并没有崩溃。它返回了 200,完成了任务,并给出了一个错误、缓慢或不再需要的答案。Trace 中没有任何部分被标红,因为从技术上讲,没有任何步骤失败。

因此,“记录一切”产生的 trace 中,关键信号(改变结果的那三个步骤)在统计学上被 37 个正常的步骤淹没了。工程师的工作变成了一个没有索引的搜索问题。在处理故障的压力下,搜索是让工程师做的最糟糕的事情,因为搜索的代价是你最稀缺的资源:在上下文变冷、值班工程师转移注意力之前的宝贵时间。

这里还有第二个、更隐蔽的失败。有关 AI 可观测性成本的行业报告指出,团队在接入 Agent 工作负载后,账单增加了 40% 到 200%,因为思维链(chain-of-thought)trace 产生的遥测数据是同等 API 调用的 10 到 50 倍。因此,“记录一切”的直觉并不是免费的。你正在为庞大且不断增长的存储账单买单,而这些数据在你真正需要它的那一刻,首要作用却是拖慢你的速度。这是一种奇怪的优化方向。

决策主干:人类最先真正需要什么

当工程师在故障期间打开 Agent trace 时,他们并不是在问“Agent 做了什么”。他们问的是一个更窄的问题:“哪里出错了”。这个问题的答案几乎从不隐藏在 token 级别的细节中,而是在决策中——Agent 追求的目标、它选择的工具、以及它本可以走两条路却选择了其中一条的分歧点。

我们称之为决策主干(decision spine)。它是运行的骨架:一份简洁、有序的列表,记录了 Agent 决定了什么以及为什么要这么决定,去除了 prompt 主体和工具负载。一个包含 40 次调用的 trace,其决策主干可能只有 20 行长。它可以完整地显示在一个屏幕上。工程师可以在 15 秒内读完它并提出假设——“它在第九步用空查询调用了 search_orders,这就是偏差所在”——而无需展开任何一个 token 级别的 span。

Token 级别的细节仍然重要。当你需要它时,你需要它的全部。但它是参考资料,而不是首页。架构上的转变是将“主干”和“细节”渲染为两个独立的层,以主干为默认视图,细节则一键即达。目前大多数可观测性工具都把这个关系倒置了:它们向你展示完整的 trace 树并让你钻取(drill in)。对于 15 个 span 的服务 trace,钻取没问题;但对于 40 个 span 的 Agent trace,钻取只是增加了额外步骤的滚动问题。

构建主干并非没有成本,但相对于它节省的成本来说,它是廉价的。每个模型回合都已经声明了它的意图——gen_ai.response.finish_reasons 字段会告诉你它是否停下来调用工具,工具名称和简短的参数摘要就在 span 中。在摄取时或首次打开时对 trace 进行一次简短的摘要处理,就能将“已做出的决策”转化为一行可读的文字。昂贵的不是生成主干,昂贵的现状是——每一位工程师每次都要通过不断滚动,在自己的脑海中重新构建那个决策主干。

跳转到分叉点

决策主干告诉你会发生了什么。它本身并不能告诉你运行是在哪里偏离轨道的。这是两个不同的问题,后者值得拥有专属的功能支持。

加载中…
References:Let's stay in touch and Follow me for more thoughts and updates