跨 Agent 服务边界的分布式追踪:上下文传播的断裂
大多数分布式追踪方案在引入 Agent 之前都运作良好。一旦系统中出现 Agent A 跨微服务边界调用 Agent B——Agent B 调用工具服务器、工具服务器再查询向量数据库——原本连贯的端到端视图就会碎片化为互不相连的片段。追踪后端展示的是一个个孤立的操作,而你失去的是因果链:为什么某件事发生了,哪个用户请求触发了它,以及那 800 毫秒究竟消耗在了哪里。
这不是监控配置问题,而是上下文传播架构问题。它有着特定的技术形态,大多数团队都是在付出代价后才意识到这一点。
为何 W3C TraceContext 在 Agent 边界处失效
W3C Trace Context 标准解决的是一个范围很窄的问题:跨单次 HTTP 请求边界传播追踪身份。每个请求携带格式为 version-trace-id-parent-id-trace-flags 的 traceparent 头。下游服务读取该头,在父 Span ID 下创建子 Span,然后返回。简单、可靠、支持广泛。
这一模型内置的假设是同步的、请求范围内的通信:一个服务调用另一个服务,得到响应,追踪结束。而 Agent 以三种不同方式打破了这一假设。
第一,Agent 采用异步通信。 当编排 Agent 通过消息队列向工作 Agent 排队任务时,没有 HTTP 请求可以携带 traceparent 头。工作 Agent 开始处理消息时创建自己的根 Span,因果链就此断裂。追踪后端会将原本属于同一逻辑操作的内容显示为两条独立的追踪。
第二,Agent 跨信任边界调用 Agent。 模型上下文协议(MCP)服务器是最典型的例子。当 Agent 调用 MCP 服务器执行工具时,MCP 协议不会自动接收并传播调用方 Agent 的 traceparent 头。每次 MCP 服务器操作都以孤立的根 Span 出现,除非你在调用点手动注入该头。
第三,Agent 框架具有不透明的内部循环。 AutoGen 等框架在内部运行工具循环,在没有暴露仪表钩子的情况下进行 LLM 调用和工具调用。从外部看,整个 Agent 执行只有一个顶层 Span。内部发生了什么——哪次 LLM 调用花了 2 秒、哪个工具返回了格式错误的响应、哪次重试最终成功——都是不可见的。
实际结果是:对多 Agent 系统的一次查询本应产生一条连贯的追踪,却在 Jaeger 或 Zipkin 中产生了三到十个孤儿根 Span,在仪表板上无法将它们重新关联在一起。
孤儿 Span 的实际表现
这种故障有明确的诊断特征。在追踪后端中,查找以下情况:
- 序列中途出现没有父节点的根 Span。 如果你看到一个
parent_span_id: null的 Span 在用户请求开始 300ms 后才出现,说明某个边界处的上下文传播失败了。 - Trace ID 不连续。 用户请求进入网关时的 Trace ID 是
4bf92f3577b34da6a3ce929d0e0e4736,到达 Agent B 时 Trace ID 已变。这意味着 Agent B 创建了新的追踪根,而不是延续原有追踪。 - 工具调用 Span 作为根节点出现。 工具调用 Span 应该是 LLM Span 的子节点,而 LLM Span 是 Agent Span 的子节点。当工具调用 Span 以根节点出现时,框架的内部上下文没有传播到工具层。
- 没有对应 Span 解释的时间空白。 编排器 Span 在 T+500ms 结束,工作器 Span 在 T+550ms 开始,中间什么都没有。那 50ms 是消息队列的传输时间,现在对你来说是不可见的。
每种故障模式都需要不同的修复方式。知道你面对的是哪种,是重新连接追踪的第一步。
核心修复:显式上下文提取与注入
重新连接孤儿 Span 的标准模式是:在跨越任何异步或服务边界之前提取当前追踪上下文,将其序列化到消息或请求载荷中,并在另一端重新附加。
在 Python 和 OpenTelemetry 中,结构大致如下:在排队任务之前捕获活跃 Span 上下文,使用传播器将其序列化为载体字典,将该字典与消息载荷一起存储,在消 费者端于创建任何新 Span 之前从载体中提取上下文。
该模式适用于消息队列、任务队列以及任何其他 HTTP 头不自动可用的异步交接。关键认识在于:traceparent 只是一个字符串,它可以通过任何媒介传输——消息体、数据库行、Redis 键——只要你在发送方写入它,在接收方读取它。
对于 MCP 服务器,修复方式是将 traceparent 和 tracestate 注入 MCP 传输支持的任何头或元数据机制中。Red Hat 的实现模式使用装饰器包装每次 MCP 服务器调用,在调用触发前注入当前 Span 上下文。
Baggage:无需修改每个函数即可传播业务上下文
W3C Baggage 是 TraceContext 中被低估的兄弟机制。TraceContext 传播追踪身份,Baggage 传播任意键值对,这些键值对会自动跟随追踪内的所有 Span,而无需通过函数参数传递。
实际使用场景:你希望每个 Span——LLM 调用、工具调用、向量数据库查询——都携带发起原始请求的用户 ID 和会话 ID。没有 Baggage,你需要将这些值穿透每一个函数调用。有了 Baggage,你只需在请求边界设置一次,它们就会自动出现在所有后代 Span 中。
这在多 Agent 系统中尤为重要:编排器知道用户是谁,但专项 Agent 不知道——也不应该知道。在入口点将会话 ID 放入 Baggage,意味着可观测性后端可以筛选给定用户会话的所有 Span,而任何 Agent 都不需要明确感知这一关联需求。
注意:Baggage 值与 traceparent 一起在 HTTP 头中传输,这意味着每个下游服务都可以看到它们。不要在 Baggage 中放置敏感数据,只用于关联标识符,而非内容。
Python 中的异步上下文丢失:具体故障形态
- https://opentelemetry.io/blog/2024/llm-observability/
- https://opentelemetry.io/docs/specs/semconv/gen-ai/
- https://opentelemetry.io/docs/concepts/context-propagation/
- https://developers.redhat.com/articles/2026/04/06/distributed-tracing-agentic-workflows-opentelemetry
- https://uptrace.dev/blog/opentelemetry-ai-systems
- https://www.w3.org/TR/trace-context/
- https://arize.com/blog-course/traces-spans-large-language-model-orchestration/
- https://oneuptime.com/blog/post/2026-02-06-trace-ai-agent-execution-flows-opentelemetry/view
- https://oneuptime.com/blog/post/2026-02-06-propagate-trace-context-async-boundaries/view
- https://fast.io/resources/ai-agent-distributed-tracing/
- https://signoz.io/blog/otel-baggage/
