跳转到主要内容

智能体可识别性:当 Trace 无法分辨哪个智能体执行了哪些操作时

阅读需 2 分钟Tian PanTian Pan

用户报告说助手在上午 9:47 给了他们一个错误答案。你打开 Trace。这里有 340 个 Span。它们几乎都被命名为 agent.run、llm.invoke 或 tool.call。有些有父节点,有些是兄弟节点。其中三个进行了重试,一个重试后被取消了。没有一个能告诉你错误的输出是来自 Planner、Worker、Critic、Reflection 过程,还是在 Critic 标记后 Worker 的第二次重试。

在接下来的一个小时里,你根据截图中的 UUID 前缀搜索日志行,比对 Slack 通知的时间戳,并根据 Trace 查看器中的缩进模式在脑海中重建 Agent 拓扑。最终,你猜想第三次 Worker 调用使用的模型别名在昨晚被静默切换到了另一个快照。但你无法仅凭 Trace 证明这一点。

Agent 正常运行,Trace 完整无缺。杂乱无章的 Trace 团(Hairball)本身就是 Bug。

默认的 Span 拓扑是一个因省略而产生的谎言​

当你用传统的 APM 仪器包装一个 Agent 循环时,你得到的是 Span,而不是身份(Identity)。OpenTelemetry GenAI 语义规范定义了类似 invoke_agent 和 create_agent 的 gen_ai.operation.name 值,Span 名称格式为 invoke_agent {gen_ai.agent.name} —— 但截止到今年,这些规范仍处于实验阶段,大多数框架集成要么忽略它们,要么将 Agent 名称设置为框架类名等通用标签。结果是,无论底层拓扑多么复杂,来自 CrewAI、LangGraph 或 Microsoft Agent Framework 的运行 Trace 结构看起来都大同小异。每一次内部调用都显示为难以区分的 agent.run。

传统的 APM 是为无状态的请求-响应服务构建的。其心理模型假设:请求从边缘进入,分发到一组固定的命名下游服务,然后返回。服务名称是稳定的,拓扑结构是架构图的一部分。Span 层级就是调用图,因为调用图是静态确定的。

Agent 同时从三个维度打破了这一假设。首先,拓扑是动态的 —— Planner 在运行时决定是生成单个 Worker、三个并行 Worker 的分发、一个 Critic、一个工具调用,还是委派给一个子团队。其次,相同的代码路径根据上下文扮演多个逻辑角色 —— 同一个 agent.run 函数调用在一次调用中是 Planner,在下一次调用中就是 Worker。第三,重试和反思过程意味着同一个逻辑 Agent 可以在同一个 Trace 中出现 N 次,而你无法判断自己看到的是同一个 Agent 的第二次尝试,还是两个恰好接收到相似输入的不同的 Agent。Trace 结构忠实地记录了运行时做了什么,但它没有记录运行时的 意图。

随之而来的值班(On-call)体验是可预见的。没有身份模型的团队会花费一小时,通过日志时间戳、函数参数转储以及 Span 事件中可见的 Prompt 片段来重建调用图。而拥有身份模型的团队只需运行一个查询:agent.role = "critic" AND agent.spawn_reason = "self_consistency_check" AND outcome = "rejected",在 30 秒内就能看到答案。

五字段身份模型​

解决方案不是一个新工具。而是一项纪律:Agent 生成的每个 Span 都必须携带传统 APM 默认不会生成的五个属性。

Role(角色)。 一个来自团队控制的封闭枚举的稳定字符串 —— planner、worker、critic、router、reflector、tool_executor、summarizer。不是框架的类名,不是模型名称,也不是自由格式的描述。这种约束至关重要:封闭枚举使属性可分组、可图表化,而自由格式的描述则会变成无法搜索的噪音。当拓扑演变并出现新角色时,你有意识地将其添加到枚举中。

Parent agent 和 spawn reason(父 Agent 与生成原因)。 这是两个字段,而非一个。parent_agent_id 将调用图重建为有向无环图,而不是 Trace 查看器渲染的扁平列表。spawn_reason 捕捉了 为什么 创建这个子节点:initial_decomposition、self_consistency_check、tool_failure_retry、escalation_threshold_exceeded。两者的结合能让你查询“由于 Worker 超时而生成的每一次 Critic 调用”,如果没有这些字段,你的 Trace 无法回答这个问题。

Agent instance ID(Agent 实例 ID)。 一个确定性的标识符,在单个任务的生命周期内作用于一个逻辑 Agent。如果 Worker 在 Critic 拒绝后重试,两次尝试都会获得相同的实例 ID。如果编排器生成了三个并行 Worker,每个 Worker 都会获得自己的 ID。这个字段使 Trace 在多次重试之间具有可查性 —— 没有它,你可以问“给我看这个 Span”,但不能问“给我看同一个逻辑 Worker 在所有尝试中所做的一切”。

Model 和 prompt version pair(模型与 Prompt 版本对)。 gen_ai.request.model 和一个 Prompt 产物标识符 —— 内容哈希、注册版本或 git SHA。必须是这两个字段,而不仅仅是一个。如果你的 Prompt 注册表在 09:31 推送了热重载,而性能回退在 09:33 开始,那么异常 Span 上的 Prompt 版本就是证据,能让复盘指向 Prompt 而不是模型。如果模型提供商在夜间将一个快照升级到别名 claude-x-latest 下,异常 Span 上的 request-model 字段就是证据,能让复盘指向提供商而不是 Prompt。如果没有这两个字段,每一次性能回退都是模糊不清的。

Outcome(结果)。 不是状态码,而是结果。succeeded、tool_error、validation_failed、critic_rejected、timeout、cancelled_by_parent。APM 默认提供 HTTP 状态和异常类型,但两者都无法捕捉 Agent 循环的语义,而这些语义决定了一个 Span 是有用功还是浪费了计算资源。添加了 Outcome 字段的团队可以发现错误率告警永远无法发现的一类性能回退 —— 运行完成且没有异常,但 Critic 拒绝了所有 Worker 的输出,导致循环耗尽了预算。

这五个字段并不是完整的 Schema。它们只是底线。在此之上,你可以添加特定领域的字段 —— 检索 Span 命中的文档库、委派去往的子 Agent 团队、Trace 起源的用户层级。但是,如果这五个底线字段缺失了任何一个,Trace 就会变成一团乱麻,再多的高级属性也救不了你。

为什么 OpenTelemetry 的 GenAI 规范无法独立解决问题​

这些规范是真实存在的。gen_ai.agent.id 和 gen_ai.agent.name 已经存在于注册表中。gen_ai.operation.name 用于区分 create_agent 和 invoke_agent。主要的供应商——Datadog、Grafana、Langfuse、Arize——都已经开始原生支持这些规范。这是真正的进步,对于任何刚起步的团队来说,遵循这些规范进行埋点是正确的基准线。

但在三个重要方面,这些规范还未达到上述身份模型的标准。首先,gen_ai.agent.id 是一个供应商标识符——通常是 OpenAI Assistants API 调用或类似调用返回的 Assistant ID——而不是一个允许你关联重试的单次任务实例 ID。其次,在应用层面上没有标准的 agent.role 属性——规范描述的是操作,而不是 Agent 在系统逻辑架构中扮演的角色。第三,规范没有指定 parent-agent 或 spawn-reason 字段,因为 OpenTelemetry 的 parent-span 关系是最接近的类比,而 OTel 并不倾向于重复定义它。

parent-span 关系与 parent-agent 并不是一回事。在典型的 Agent 框架中,编排器(orchestrator)会生成子 Agent,这些子 Agent 作为编排器 span 的子 span 运行——但它也会生成作为兄弟节点(siblings)运行的重试任务,调用作为工作线程子节点而非编排器子节点运行的工具,并授权给内部 span 嵌套很深的子团队。OpenTelemetry 的 parent-span 图是对“哪个函数调用了哪个函数”的忠实记录。而你真正想要的是 parent-agent 图,它是“哪个 Agent 决定调用了哪个其他 Agent 以及为什么要调用”的逻辑图。两者有重叠,但并不相同。你需要两者,并为后者提供显式的属性。

务实的立场是:在适用之处遵循 OpenTelemetry GenAI 规范进行埋点,并在其之上添加应用级的身份属性,作为带有你自己命名空间前缀的自定义字段。当规范赶上进度时(它们一定会赶上的),你可以迁移这些自定义字段。跑在规范前面的成本很小;而等待规范成熟的代价就是下一次值班时的手忙脚乱。

故障发生前一天必须确认的事项​

大多数团队在故障期间才发现这一差距,原因是身份模型在正常运行时是不可见的。当一切正常时,trace 看起来没问题。每个 span 都有名称,层次结构渲染清晰,你可以逐步查看。

万事顺遂之日,正是验证身份模型是否稳健之时,因为一旦发生故障,你将没有时间去添加字段。日常工作中应包含三项检查:

第一项检查是拓扑审计(topology audit)。提取最近的一百个生产环境 trace。按 agent.role 对 span 进行分组,并确认分布是否符合架构设计意图——如果你在设计中有一个“评审者(critic)”角色,但 agent.role = "critic" 在所有 span 中从未出现,那么你的埋点就在无声无息地进行错误标注。如果出现了一个你并没有定义的角色,说明有人添加了设计之外的代码路径。无论哪种情况,审计都能在故障发生前捕获漂移。

第二项检查是重试可解析性检查(retry resolvability check)。找到一个带有重试的 trace,并确认通过 agent.instance_id 查询能够将所有尝试作为一个逻辑组返回。如果重试出现在不同的实例 ID 下,说明你的 ID 生成是非确定性的,你无法跨尝试进行关联。这种失效模式会将五分钟的调查变成五个小时,因为每一次重试看起来都像是一个从未被显式调用过的独立 Agent。

第三项检查是回归溯源演练(regression-traceability rehearsal)。选择一个使用已知 prompt 版本和已知模型快照运行的 trace。模拟一次故障——更改 prompt、更改模型、更改路由逻辑——并确认生成的 trace 仅凭 span 就能体现出这些变化,而无需查阅部署日志。如果 trace 没有记录版本,值班工程师也无法得知,事后复盘时就会说“我们不确定是哪项改动导致的”。

这三项检查都可以纳入每周的观测性审查(observability review)中。跳过它们在故障发生前感觉不到代价,但一旦发生故障,代价就是整个调查窗口期。

问题的一半在于文化​

身份属性不会凭空出现。它们是由那些曾因缺乏这些属性而吃过苦头、并承诺绝不重蹈覆辙的工程师添加的。这种承诺才是最难维持的。

Agent 埋点的默认轨迹是衰减。第一个版本有丰富的属性,因为有人刚刚调试了一个乱成乱麻的 trace。第二个版本丢掉了其中两个属性,因为框架升级更改了包装器签名而没人注意到。第三个版本在代码库中增加了一个新的 Agent 角色,但没有将其添加到角色枚举(enum)中,导致一半的新 span 显示为 unknown。到了第六个版本,trace 在结构上看起来与第一版完全相同,但传达的信息量仅剩 60%。

防御手段是将身份模型视为契约(contract),而非为了图方便。角色枚举是代码库中版本化的产物,而不是散落在各处调用点的字符串字面量。实例 ID 生成器是一个带有测试的单一函数。模型和 prompt 版本属性由加载 prompt 产物的同一个库生成,而不是在每个 span 边界手动处理。当这些基础组件分散时,第六个版本的漂移就会发生;当它们被集中管理时,漂移大多是可以避免的。

在生产环境中部署 Agent 一年而不发生故障的团队是罕见的。能够在故障发生后三十秒内回答“哪个 Agent 在何时、为何、使用哪个版本做了什么”的团队,才是其身份模型在代码库磨砺中幸存下来的团队。Trace 不是运行时的副作用。它是一个设计出来的产物,而这种设计先于团队最终将要撰写的每一份事后复盘。

参考资料

保持联系,关注我获取更多内容

阅读需 10 分钟

止于供应商边界的链路追踪

你的分布式链路追踪往往在推理 API 的边缘中断。本文将介绍如何对流式数据块、请求 ID 和供应商侧信道进行插桩,从而找回流水线中最昂贵的分钟级性能损耗。

observability
llm
阅读需 10 分钟

流式响应追踪模式鸿沟:为什么你的 APM 在 LLM 延迟上撒了谎

流式 LLM 响应打破了请求/响应的 Span 模型。duration 字段具有误导性;故障发生在边界之间——如 TTFT 回归、中途停顿、内容死循环——而解决方案是采用基于检查点的 Token 时间事件,并建立真正的尾部事件分类体系。

llm-observability
streaming
阅读需 9 分钟

Agent 流水线的分布式追踪:为什么你的 APM 工具形同虚设

标准 APM 工具在多步骤 Agent 流水线上会失效。以下是 AI Agent 专用可观测性的真正需求——以及三个能在用户察觉之前预判 Agent 劣化的关键指标。

insider
ai-engineering
阅读需 11 分钟

你的智能体追踪在撒谎:LLM 智能体的基数、采样与 Span 层级结构

分布式追踪最初是为每个请求约 10 个 Span 设计的。而单个智能体运行可能会产生数百个 Span,默认的 OpenTelemetry 配置会系统性地漏记工作负载。本文将介绍能够经受住生产环境智能体工作负载考验的 Span 层级、尾部采样策略以及 Payload 处理方式。

insider
observability
阅读需 9 分钟

APM 仪表盘不会告诉你:生产环境中的 LLM 可观测性

生产环境中的 LLM 系统往往在无声中失效 —— 绿色仪表盘背后隐藏着幻觉、提示词漂移和错误的工具选择。本文将介绍一种真正能发现问题的埋点模型。

llm
observability