线上 Agent 一次用户任务往往跨越:API 网关 → 编排运行时 → 多次 chat/generate_content → 本地或远程 execute_tool → MCP tools/call。当 P95 延迟升到数分钟、且中间夹杂队列 Worker 重投时,仅靠应用日志里的 request_id 很难把「哪一轮模型决策触发了哪次工具超时」对齐到同一条因果链。分布式追踪的目标是把上述 hop 收敛到同一 trace-id,并在追踪后端按父子 Span 展开时间线。
W3C Trace Context 定义了 traceparent / tracestate 的 HTTP 头格式;OpenTelemetry 在独立的 semantic-conventions-genai 仓库中维护 GenAI 与 MCP 的 Span 命名与属性(当前文档状态为 Development,落地时应锁定约定版本并关注变更)。本文只讨论可在代码与网关配置中实现的传播与建模,不假设某一厂商托管 Agent 的专有字段。
invoke_agent(INTERNAL),每轮模型与每次工具各建子 Span;经 MCP 出站时在 _meta 注入 W3C 头字段。问题背景:HTTP 头够不到 JSON-RPC 流内的单次 tools/call
与三层微服务 RPC 相比,Agent 链路有三个常见断点:
- 一轮用户任务 ≠ 一次 HTTP 请求:编排运行时可能在同一进程内循环「模型 → 工具 → 模型」,只有入口 REST 带
traceparent时,内部各轮调用默认不会自动成为子 Span,除非运行时显式用 OpenTelemetry API 创建invoke_agent/execute_tool。 - MCP 是 JSON-RPC 消息,不是「一 RPC 一 HTTP」:OTel MCP 约定写明,当传输为 HTTP 时,HTTP 级 trace 传播只覆盖该 HTTP 请求,无法单独标识同一请求/响应流内的多条 JSON-RPC 消息。因此 Streamable HTTP 下,若只在 POST 上挂父上下文,流内后续
tools/call与通知可能丢失与 Agent 侧execute_tool的父子关系。 - 重复埋点:GenAI 约定指出 MCP 工具执行既可由
gen_ai.execute_tool覆盖,也可由 MCP 客户端 instrumentation 覆盖;两套同时开启会产生双 Span,瀑布图噪声上升。
因此,Agent 平台的可观测性设计需要同时回答:入口如何进 trace、进程内如何分层、MCP 消息级如何续链。
可落地实现:Span 分层、网关头、MCP _meta 与采样参数
1. Span 命名与关键属性(GenAI Agent 约定)
下列名称来自 OpenTelemetry GenAI Agent spans 文档,实现时应固定 gen_ai.operation.name 与 Span kind:
| 场景 | Span 名(SHOULD) | Span kind | gen_ai.operation.name |
|---|---|---|---|
| 本进程编排一次用户任务 | invoke_agent {gen_ai.agent.name} | INTERNAL | invoke_agent |
| 调用托管 Agent API(Bedrock 等) | invoke_agent {gen_ai.agent.name} | CLIENT | invoke_agent |
| 执行工具(应用代码) | execute_tool {gen_ai.tool.name} | INTERNAL | execute_tool |
| 单次 LLM 请求 | 见 gen-ai-spans.md 中 chat 等 | 多为 CLIENT | chat / generate_content 等 |
建议在创建 invoke_agent 时写入低基数、可用于采样的属性(约定中标注为 span 创建时应提供):gen_ai.agent.name、gen_ai.provider.name、gen_ai.request.model(若已知)。会话维度使用 gen_ai.conversation.id 与业务侧 thread id 对齐,但不要把该字段当作 W3C 的 trace-id 替代品——一次会话可包含多次独立 trace(例如用户点击「重试」)。
2. 入口网关:注入 traceparent
traceparent 格式为四段:{version}-{trace-id}-{parent-id}-{trace-flags},其中 trace-id 为 32 位十六进制、parent-id 为 16 位十六进制(见 W3C HTTP 头绑定文档)。若客户端未带头,网关可为每个新的用户任务生成根上下文并向下游传递;若客户端已带有效 traceparent,网关应原样转发并在下游 HTTP 客户端 span 上延续同一 trace-id。
# 网关侧检查(伪逻辑)
# - 校验 traceparent 版本与十六进制长度
# - 非法则丢弃并生成新 trace-id(避免污染后端)
# - 禁止把 traceparent 写入响应体或 SSE 事件明文(仅请求链路透传)
incoming = request.headers.get("traceparent")
if not valid_traceparent(incoming):
incoming = tracer.start_span("ingress").get_span_context().traceparent
forward_to_agent_runtime(traceparent=incoming)
编排服务收到请求后,应使用提取到的上下文作为 invoke_agent 的 parent,而不是再新建根 trace。
3. MCP:经 params._meta 注入 W3C 键(非 DNS 前缀)
MCP 约定要求:instrumentation 使用已配置的 OpenTelemetry propagator,在创建 JSON-RPC 请求或通知时,把上下文注入 params._meta;接收方从 params._meta 提取并作为 remote parent。尽管 MCP 规范通常期望 _meta 键为 DNS 前缀形式,传播用的 traceparent、tracestate、baggage 应不加前缀;SEP-414 对此有专门说明。
{
"jsonrpc": "2.0",
"method": "tools/call",
"params": {
"name": "search_docs",
"arguments": { "query": "visibility timeout" },
"_meta": {
"traceparent": "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01",
"tracestate": ""
}
},
"id": 42
}
Agent 运行时在为某次 execute_tool 调用 MCP 客户端前,应在当前 execute_tool Span 的 context 下执行 inject,使 MCP 服务端 tools/call Span 的 parent 指向该工具 Span,而不是仅指向外层 HTTP POST。
4. 属性与事件:默认少写、按需 Opt-In
gen_ai.tool.call.arguments 与 gen_ai.tool.call.result 在约定中为 Opt-In。生产环境默认应关闭或做脱敏/截断,否则追踪后端会重复存储 prompt、PII 与密钥。优先记录:gen_ai.tool.name、gen_ai.tool.call.id(若模型返回)、error.type(失败时)。
5. 推荐初始参数(可按流量调整)
| 参数 | 建议初值 | 说明 |
|---|---|---|
| 根采样率 | 5%–10%(成功路径) | Agent 单次 trace Span 数量多,全量采集成本极高;错误 trace 建议 tail sampling 保留 |
ParentBased 采样 | 开启 | 保证入口已采样的用户任务,子 Span(含 MCP)一致跟随 |
| Baggage 键 | 仅 tenant_id、deployment | W3C Baggage 会跨 hop 传播;禁止放 token、API key |
| 导出批延迟 | 1–5s | OTel SDK 批处理;长任务可在 invoke_agent 结束 force flush |
| Span 事件上限 | 按后端配额 | 勿把每个 SSE chunk 记为事件;记录首个 token / 首个 tool delta 时间戳即可 |
| MCP vs execute_tool | 二选一为主 Span | 约定允许重叠;选一个作为 SLO 统计来源并在另一处降采样 |
6. 与异步 Worker 的衔接
若用户任务经 PostgreSQL 队列(见仓库内 SKIP LOCKED 队列文)异步执行,入队时应把当前 traceparent 序列化进任务 payload 或独立列;Worker claim 后先 extract 再创建 invoke_agent,否则队列延迟段在追踪上会与用户点击断开。注意:租约重投会产生新的 Worker 尝试,是否沿用同一 trace-id 属于产品选择——沿用便于端到端,新建可避免把多次失败叠在同一条瀑布图上。
风险与边界
- 约定仍在 Development:Span 名与必填属性可能随 semantic-conventions-genai 版本变化;升级 OTel 依赖时应做瀑布图快照对比,而不是只盯单元测试。
- HTTP 传播 ≠ 消息传播:仅在 MCP HTTP POST 上透传
traceparent而不写_meta,在 Streamable HTTP 多消息场景下,官方 MCP 约定明确认为不足以关联流内单次 RPC。 - 双 Span 与时长夸大:同时启用
execute_tool与 MCP client span 时,看板上的工具耗时可能重复累计,需在建模阶段选定「权威」一层。 - 敏感数据合规:Opt-In 的工具参数/结果、以及把 prompt 记入 Span event,可能违反数据驻留与最小采集原则;应配合 collector 处理器做 drop 规则。
tracestate大小:多 vendor 透传时tracestate条目增多,极端情况下触及代理或网关头大小限制;Agent 网关宜限制入站tracestate长度。- stdio MCP:无 HTTP 头时,
params._meta传播更为关键;父进程需在每条 JSON-RPC 线路上注入/提取,不能假设 OS 管道「隐式」带上下文。 - 采样与计费对账:
gen_ai.*token 用量多在 metrics 约定中;trace 采样丢失不会自动补全账单,成本分析仍需 metrics 管线独立存在。
验收清单
- 从网关发起带固定
trace-id的请求:追踪 UI 中可见invoke_agent→ 至少一轮chat→execute_tool,且 MCP 服务端 span 的 parent 指向工具层而非孤立根节点。 - 对照实验:关闭
params._meta注入后,同一压测下 MCP 服务 span 与 Agentexecute_tool无法关联或出现平行根 trace。 - 失败路径:工具超时或 MCP 400 时,
error.type非空且 trace 在 tail sampling 策略下仍被保留(若已配置)。 - 隐私:默认导出配置下,span 属性中不出现完整工具参数与用户原文(可通过 collector 审计规则自动扫描)。
- 版本升级:锁定 semantic-conventions-genai 的 git tag 后重复上述用例,确认 Span 名未静默变更导致仪表盘断链。
参考来源
- W3C:Trace Context(
traceparent/tracestate) - W3C trace-context 源码:HTTP Request Headers Format
- OpenTelemetry:Context API — Propagators
- OpenTelemetry GenAI:GenAI agent and framework spans
- OpenTelemetry GenAI:GenAI spans(含 execute_tool)
- OpenTelemetry GenAI:Semantic conventions for MCP(Context propagation)
- MCP 社区:SEP-414: Request Meta for Trace Context
- MCP 规范:
params._meta(基础元数据字段)