单智能体时代,可观测性是"一条 Trace、一条线"的问题:一次用户请求对应一条线性调用链,token 归因、工具耗时、错误栈都落在同一个上下文里。但当编排器把任务扇出给 6 个专业智能体、再由它们各自二次委派,调用结构就从线性链坍缩成有向无环图(DAG),而因果链几乎总在第二、第三跳断裂。2026 年 LLM 可观测性市场规模已升至 26.9 亿美元,但绝大多数平台仍在用单智能体的"父子 Run 树"去套多智能体的网状拓扑,结果是:成本分摊失真、错误根因漂移、并行扇出的聚合段无人认领。本文以 OpenTelemetry GenAI 语义约定为基准,实测六套追踪栈在因果链保真度、跨边界上下文传播、DAG 重建与成本归因上的真实表现,给出断链分类法与自愈工程范式。
经典分布式追踪的假设是"上下文随请求透传"。在单体微服务里,W3C Trace Context 通过 HTTP 头 traceparent 在服务间接力,span 树可以横跨几十个服务而不丢一跳。但多智能体系统的拓扑比微服务更"滑":智能体之间的交接(handoff)往往发生在异步队列、子进程、独立运行时甚至不同语言运行时之间。一旦交接路径绕过了同步调用栈,traceparent 就不会被自动透传,下游智能体只能起新的 trace 根。
结果是同一个用户请求在追踪面板上碎成 N 段互不相连的孤岛。Red Hat 在其生产级多智能体追踪实践中明确指出:智能体应用的可观测性难点不在"看到一次 LLM 调用",而在于重建跨自主节点的完整请求流——而这恰恰要求显式的上下文传播工程,而非框架默认行为。更隐蔽的是,很多框架的"内置追踪"只在同一进程内生效,跨进程交接时静默丢弃因果链,面板上看起来一切正常,实则根因已被掩盖。
gen_ai.* 与 traceparent2026 年,OpenTelemetry 的 GenAI 语义约定(semantic conventions)正在从草案走向稳定,它定义了一组 gen_ai.* 属性,把 LLM 调用、工具调用、token 用量标准化为可跨平台聚合的 span 维度。核心属性包括 gen_ai.system、gen_ai.request.model、gen_ai.usage.input_tokens、gen_ai.usage.output_tokens、gen_ai.operation.name,以及工具维度的 gen_ai.tool.name 与 gen_ai.tool.call.id。
这套约定之所以是多智能体追踪的"基准尺",是因为它把"一次智能体调用"标准化为一个可挂载属性的 span,而 span 之间的 parent_span_id 关系天然编码了因果层级。关键代码骨架如下:
from opentelemetry import trace
from opentelemetry.semconv.attributes import gen_ai_attributes as G
tracer = trace.get_tracer("agent.mesh")
def invoke_agent(name: str, role: str, hop: int, task: str):
with tracer.start_as_current_span(f"agent.{name}") as span:
span.set_attribute(G.GEN_AI_OPERATION_NAME, "invoke_agent")
span.set_attribute(G.GEN_AI_SYSTEM, "openai")
span.set_attribute("agent.name", name)
span.set_attribute("agent.role", role) # orchestrator | worker
span.set_attribute("mesh.hop", hop) # 因果深度
# OpenLLMetry 自动在其下挂 gen_ai LLM 子span
return run(task)
真正决定因果链是否连续的,不是这些属性,而是上下文是否随交接透传。OTel 通过 propagate.inject/extract 序列化当前上下文,traceparent 是其载体。凡是绕过同步栈的交接,都必须显式注入与提取 traceparent——这是六栈实测中分水岭式的能力差异。
把"链断了"拆成可枚举的故障模式,是多智能体可观测性工程的第一步。实测中,断链几乎总落在以下五个位置之一,按发生频率排序:
同步交接(不断):编排器直接调用子智能体函数,上下文随调用栈自然继承,父子 span 正确嵌套——这是唯一"免费"的连续段。
异步交接(高频断):任务经 asyncio、线程池、celery、消息队列派发,新执行单元不继承父上下文。asyncio.gather 扇出是最常见的隐形断点,子任务各自起新根。
子进程/独立运行时(中频断):智能体被隔离在独立进程或沙箱(见前轮代码沙箱评测),traceparent 不会跨进程边界,除非通过环境变量或协议信封显式传递。
跨语言运行时(中频断):Python 编排器调用 TypeScript 工具智能体,两套 OTel SDK 各自维护上下文,traceparent 必须作为协议字段手工桥接。
协议层交接(低频但致命):通过 A2A、MCP 或自定义 HTTP 调用的智能体间通信,若信封未携带 traceparent,跨服务因果链彻底重置。
from opentelemetry import propagate, context
def handle_handoff(envelope: dict):
ctx = propagate.extract(envelope.get("headers", {})) # 提取traceparent
if "traceparent" not in envelope.get("headers", {}):
record_metric("chain.broken", hop=hop_count(),
reason="async_or_cross_runtime")
token = context.attach(ctx)
try:
invoke_agent(envelope["to"], "worker", hop_count()+1, envelope["task"])
finally:
context.detach(token)
flowchart LR
U([用户请求]) --> ORC[编排器 span:hop0]
ORC -->|同步,连续| W1[研究智能体 hop1]
ORC -.->|asyncio.gather 扇出,断链| W2[并行智能体 新根]
ORC -->|子进程,断链| W3[沙箱智能体 新根]
W1 -->|A2A协议,信封无traceparent,断链| W4[外部智能体 新根]
W1 --> W5[工具调用 hop2 连续]
classDef broken stroke:#e53935,stroke-width:2px,color:#b71c1c
classDef ok stroke:#2e7d32,stroke-width:2px,color:#1b5e20
class W2,W3,W4 broken
class ORC,W1,W5 ok
以"一次跨 3 跳、含并行扇出的请求能否在面板上重建为单一 DAG"为基准,实测六套栈的表现差异巨大。
| 追踪栈 | 因果链保真 | 跨边界传播 | DAG 重建 | 成本归因 | 自托管 |
|---|---|---|---|---|---|
| OTel + OpenLLMetry | 高(需手工配) | traceparent 原生 | span 树 + span link | token 维度精确 | 是 |
| LangSmith | 中(进程内强) | 限 LangChain 生态 | Run 树 parent_run_id | 按 run 聚合 | 否 |
| Langfuse | 中高 | SDK 嵌套 observation | 嵌套树,扇出展平 | 按observation分摊 | 是 |
| Arize Phoenix | 高 | OTel span parent/child | span 树 + link | OTel 维度精确 | 是 |
| AG2 内置 OTel | 中(群聊场景优) | 群聊内连续 | 群聊级聚合 | 群聊token总量 | 是 |
| agenttrace (ob4cl) | 高 | OTel 原生 | 推理步+工具 span | 步级归因 | 是 |
关键分水岭有二。其一,是否原生 OTel:Phoenix、OpenLLMetry、agenttrace 走 OTel span 路线,traceparent 是一等公民,跨边界传播只需遵守 OTel 规范;LangSmith、AG2 的"内置追踪"更多是进程内 Run 树,跨进程即退化。其二,扇出聚合的表达力:OTel 的 span link 允许一个子 span 声明"我同时关联多个父 span",这是表达 DAG 而非纯树的关键原语——Langfuse 的嵌套 observation、LangSmith 的 Run 树都缺乏这种多父关联能力,导致并行汇聚段在树形视图里被迫"挂"在某个父节点下,因果被扭曲。
多智能体最棘手的追踪结构不是深度,而是宽度加汇聚。编排器把一个任务拆成 6 个并行子任务,分派给 6 个 worker,再把结果汇聚给一个汇总智能体——这在因果上是 DAG(六个父 → 一个子),而非树。传统 span 模型只允许单一 parent_span_id,强行套用会出现三种失真。
第一种是孤儿汇聚:汇总智能体的 parent 被设为编排器,于是面板上看不出它依赖 6 个 worker 的产出,回归测试时无法复现"某个 worker 慢导致汇总超时"。第二种是成本重复计数:6 个 worker 共享的检索子任务(如一次共享向量库召回)被各算一次,token 总账虚高 6 倍——AG2 在群聊 token 归因中专门强调的"群聊实际用了多少 token"问题,本质就是这种扇出重复计数。第三种是因果错位:把 fan-out 的 6 个子任务画成串行,掩盖了真实的关键路径。
OTel 的解法是 span link:在汇聚 span 上为每个输入父 span 添加一条 link,既保留单 parent 的树形结构(兼容采样与瀑布图),又显式记录多源因果:
import asyncio
from opentelemetry import trace
async def fanout(parent_ctx, tasks):
parent_span = trace.get_current_span()
async def one(t):
# 用 attach 把父上下文带进新协程 —— 否则 async 断链
token = context.attach(parent_ctx)
try:
with tracer.start_as_current_span("agent.worker") as s:
s.set_attribute("mesh.fanout.peer_count", len(tasks))
return await invoke(t)
finally:
context.detach(token)
return await asyncio.gather(*(one(t) for t in tasks))
def join(results, parent_spans):
with tracer.start_as_current_span("agent.summarizer") as s:
for ps in parent_spans: # 多源因果 → span link
s.add_link(ps.get_span_context())
return synthesize(results)
当智能体数量上到几十、请求并发上千,全量追踪的存储与带宽成本本身就会压垮可观测性预算。单智能体采样策略(按比例 head 采样)在 mesh 上几乎失效,原因是因果链必须整条保留或整条丢弃:若根 span 被采样、某跳 span 被丢弃,重建出的就是断链,等于没采。
正解是尾部采样(tail-based sampling):先把整条 mesh 请求的所有 span 收集到缓冲区,等请求结束后依据策略决定整条保留或丢弃。多智能体场景下,尾部采样策略应叠加三类条件——错误(任何 span 异常则全留)、高成本(总 token 超阈值则全留)、高延迟(关键路径超阈值则全留)。Cisco Outshift 在其多智能体 OTel 实践中把分布式追踪称为"复杂工作流的可观测性基石",并特别强调 mesh 级采样必须与 token 成本挂钩,否则要么采到一堆廉价成功的链、要么漏掉真正昂贵的失败链。
第二个税是高基数。每个智能体实例若用唯一 ID 打标签,agent.instance_id 的基数会随并发线性膨胀,时序库与索引会先于业务崩。工程上应区分低基数维度(agent.name、agent.role、mesh.hop)用于聚合,与高基数标识(trace_id、span_id)仅用于关联,严禁把高基数字段塞进可聚合属性。
断链检测的终点不是告警,而是自愈。成熟的 mesh 会部署一个上下文传播守卫层:在每条交接信封注入 traceparent(缺失则补);在下游若检测到"无父上下文"则记录 chain.broken 指标,并尽可能用业务级关联键(如 request.correlation_id)把断开的 trace 段事后缝合。这与前轮"工具副作用回滚:补偿事务 Saga"评测直接衔接——Saga 的每一步补偿都需要从追踪链中定位"是哪一跳的状态变更需要回滚",断链的 mesh 里补偿事务会找不到自己的因果锚点。
更进一步,把 traceparent 的注入下沉到协议层(A2A、MCP 信封默认携带),是从"应用手工传递"走向"传输层原生透传"的关键一跃,正如 HTTP 微服务当年把分布式追踪标准化为中间件默认行为。这要求追踪栈具备协议级注入钩子——OTel 原生栈可通过 instrumentation 自动完成,而进程内 Run 树栈则需框架显式支持,否则永远停留在单进程幻象里。
sequenceDiagram
participant U as 用户
participant O as 编排器 hop0
participant Q as 异步队列
participant W as Worker hop2
participant O11y as 追踪后端
U->>O: 请求 (注入 traceparent T0)
O->>O: span open T0.s1
Note over O,Q: 信封携带 traceparent ✅
O->>Q: enqueue(task, traceparent=T0)
Q->>W: dequeue —— traceparent 透传 ✅
W->>W: span open T0.s2 (parent=s1) 因果连续
W-->>O: 结果
Note over O: 若队列丢弃信封 ❌ → W 起新根 T1
O->>O11y: 上传 T0.s1, T0.s2 (同 trace)
从零起步、单体为主:选 Arize Phoenix(自托管、OTel 原生、span link 支持 DAG),用 OpenLLMetry 做 LLM/工具自动插桩,最小工程量获得因果链保真。
全 LangChain 生态、且不跨进程:LangSmith 的 Run 树开发体验最佳,但要清醒认知它在跨进程、扇出汇聚上的因果失真,关键链路用 traceparent 手工兜底。
群聊式多智能体、关注群组级 token 账:AG2 内置 OTel 直接覆盖群聊 token 归因痛点,配合 Phoenix 后端做可视化。
生产级、跨语言、需要 mesh 级采样与补偿衔接:以 OTel GenAI 语义约定 + W3C Trace Context 为地基,尾部采样叠加 token/错误/延迟三策略,traceparent 下沉到 A2A/MCP 协议信封。这是唯一能扛住规模化因果链重建的架构,也是把"断链"从故障变成可工程化治理对象的路径。
[!tip] 51学通信知识星球 星主:爱卫生(微信:gprshome201101)——公众号「51学通信」创办者,19 年通信行业运维与讲师经验,著有《GPRS网络信令实例详解》《EPC原理与实践》《5G核心网原理与实践》三本纸质书(各大电商平台有售)。 星球定位:深耕 5G 核心网、IMS、NFV/AI、云原生等 ICT 领域,主打运维岗位与信令分析。 当前规模:5200 篇主题 / 990 个回答,198 元/年。 加入福利:加入星球后请加爱卫生微信,拉您进知识星球年度陪伴与答疑群——不限量回复您的各种问题,另有群内分享。 AI 加持:51学通信知识星球已作为 skill 接入 WorkBuddy,使用 deepseek-4-flash 在海量星球帖子中精准找贴、找视频、找 FAQ 和案例,做总结、找文档和 PPT 等场景全覆盖。
