可观测方案对照:OpenRouter Broadcast 与 Claude Agent SDK observability 各自吐什么

2026-08-18

翻这两家文档最容易踩的坑,是把「observability」当成同一件事。OpenRouter 的 Broadcast 和 Claude Agent SDK 的 OpenTelemetry 导出,名字挨得很近,接的后端也有重合(Datadog、Grafana、Langfuse 两边都出现过),但一条记录里装的是什么、配置落在谁那台机器上、prompt 内容默认在不在里面,这三处的答案完全不同。接错了会出现一种很难查的状况:仪表盘上有数据,但你想问的那个问题恰好不在数据里。

下面只对照两边文档都白纸黑字写明的部分。三方产品都是闭源的,本文不推断实现,也不排优劣。

先分清 OpenRouter 这边其实有两条出口

很多人只知道 Broadcast,其实 openrouter.ai/docs 里写明的可观测出口有两条,形态完全不同。

第一条是带外的 Broadcast。 官方文档《Broadcast》页写明,开启后 OpenRouter 会自动把请求的 trace 发到你配置的外部平台,页面自述这样做「不需要在你的应用代码里加任何额外埋点」。文档写明的开启路径是 Settings > Observability,打开「Enable Broadcast」开关后添加一个或多个 destination;组织账号下必须是 organization admin 才能改这组设置。目的地类型跨度很大,除了常见的 LLM 观测平台,还包括对象存储、数据仓库、OTLP collector 和纯 Webhook —— 也就是说落地端不一定是「看板」,也可以是一张表。平台上的可用目的地随时在变,以官方文档与设置页最新内容为准。

第二条是带内的 openrouter_metadata 官方文档《Router Metadata》页写明,这是逐请求 opt-in:发请求时带上 X-OpenRouter-Metadata: enabled 这个 header,成功响应体里就会多出一个 openrouter_metadata 对象。文档写明该 header 只接受两个值(大小写不敏感):enableddisabled,其它任何值(包括拼错和空串)一律退回 disabled,不带 header 时的默认行为也是 disabled

curl https://openrouter.ai/api/v1/chat/completions \
  -H "Authorization: Bearer <YOUR_API_KEY>" \
  -H "Content-Type: application/json" \
  -H "X-OpenRouter-Metadata: enabled" \
  -d '{
    "model": "openai/gpt-4o-mini",
    "messages": [{ "role": "user", "content": "Hello" }]
  }'

(示例里的 model slug 是官方文档当时的示例值,平台上有哪些模型随时在变,别当清单用。)

这两条出口各自的用途,两页文档都自己写明了:Broadcast 页自述它是让你「在偏好的工具里监控、调试与分析 LLM 用量」,落地端在你配置的外部平台;Router Metadata 页自述它意在「调试路由决策、归因延迟或成本、审计 pipeline 行为」,落地端就是这一次的响应体。一个是事后去别处看,一个是当场就能拿到。文档还写明了一个容易被忽略的边界:缓存命中的响应永远不带 openrouter_metadata,流式和非流式的缓存重放都会把这个字段剥掉;另外 500 状态的响应会被统一脱敏,metadata 也随之省略,而 502503504529 这几类在 opt-in 的前提下仍然带。

Claude Agent SDK 这边只有一条主出口,但分三路信号

code.claude.com/docs 的《Observability with OpenTelemetry》页写明:Agent SDK 自己不产生遥测,它把配置以环境变量的形式透传给作为子进程运行的 Claude Code CLI,由 CLI 直接导出到你的 collector。CLI 导出三路互相独立的 OpenTelemetry 信号,各有各的开关:metrics 用 OTEL_METRICS_EXPORTER,log events 用 OTEL_LOGS_EXPORTER,traces 用 OTEL_TRACES_EXPORTER

这里有一条必须照实标出来的边界:traces 是 beta。文档写明 traces 除了 exporter 变量之外还要额外设 CLAUDE_CODE_ENHANCED_TELEMETRY_BETA=1,并明确提示「span 名称与属性可能在版本之间变化」;其中 claude_code.hook span 还要再加一层 detailed beta tracing(ENABLE_BETA_TRACING_DETAILED=1BETA_TRACING_ENDPOINT)。metrics 与 log events 不需要这个 beta 开关。

OTEL_ENV = {
    "CLAUDE_CODE_ENABLE_TELEMETRY": "1",
    # Required for traces, which are in beta. Metrics and log events do not need this.
    "CLAUDE_CODE_ENHANCED_TELEMETRY_BETA": "1",
    "OTEL_TRACES_EXPORTER": "otlp",
    "OTEL_METRICS_EXPORTER": "otlp",
    "OTEL_LOGS_EXPORTER": "otlp",
    "OTEL_EXPORTER_OTLP_PROTOCOL": "http/protobuf",
    "OTEL_EXPORTER_OTLP_ENDPOINT": "http://collector.example.com:4318",
}

以上为按官方文档中的字段语义组合的示例,未经实测,以官方文档与 API 的实际响应为准。

Windows 侧要注意两件事。 一是官方 Monitoring 页给的快速开始是 POSIX shell 的 export 写法,Windows 上你得在 PowerShell 或容器编排里设同名环境变量(这一步属于 shell 层面的等价操作,官方文档只给了 export 形式)。二是文档明确写了 PowerShell:trace context 传播启用时,CLI 会把 TRACEPARENT 转发给它运行的每一条 Bash 与 PowerShell 命令;文档进一步写明,通过 Bash 工具启动的命令如果自己也发 OpenTelemetry span,这些 span 会嵌在包住该命令的 claude_code.tool.execution span 下面。这是少见的、在文档里点名 Windows 侧行为的地方。文档同时写明,如果你在 options.env 里显式设了 TRACEPARENT,自动注入就会跳过,你可以自己钉一个父上下文。

数据形态:一条记录里到底装了什么

这是两边差得最远的地方。

OpenRouter Broadcast 的一条 trace,对应的是一次 API 请求。 文档列出的默认内容有六类:请求与响应数据(多模态内容会被剥掉)、token 用量(prompt / completion / total)、成本、时序(开始时间、结束时间、延迟)、模型信息(model slug 与 provider 名)、工具使用情况(请求里有没有带 tools、有没有真的发生 tool call)。注意最后一项的粒度:它告诉你「有没有工具调用」,而不是「哪个工具跑了多久」。

层级要靠你自己传。 文档写明可以在请求体里带 trace 字段,里面的 trace_idtrace_namespan_namegeneration_nameparent_span_id 有约定语义 —— 用同一个 trace_id 把多次请求归到一条 trace,用 parent_span_id 把 OpenRouter 的调用挂到你自己已有的 OpenTelemetry span 下面。另外还有两个独立字段:user(关联终端用户)和 session_id(把一次会话或 agent 流程的多次请求归组,也可以走 x-session-id HTTP header 传)。换句话说,OpenRouter 那边看到的结构,是你在请求里描述给它的结构。OTLP collector 目的地页面进一步写明,trace 里的自定义键会落到 trace.metadata.* 命名空间下的 span 属性,user 映射到 user.idsession_id 映射到 session.id,模型、token、成本这些走标准的 gen_ai.* 语义约定。

Claude Agent SDK 的 span 树,对应的是一轮 agent 循环,层级是内建的。 文档写明的 span 有:claude_code.interaction(一轮交互,从收到 prompt 到产出回复)、claude_code.llm_request(每次模型调用)、claude_code.tool(每次工具调用,下面还有两个子 span:claude_code.tool.blocked_on_user 记录等权限决策的时间,claude_code.tool.execution 记录实际执行)、以及 claude_code.hook。subagent 的 llm_requesttool span 会嵌在父 agent 的 claude_code.tool span 下,整条委派链是一条 trace。

「等权限等了多久」这一格,是两边形态差异最刺眼的地方。 OpenRouter 的文档里没有对应的概念 —— 它记录的单位是一次 API 请求;而 Claude Code 侧把权限等待单独切成了一个 span,还带 decisionaccept / reject)和 source 属性。如果你想回答的问题是「这次跑得慢,是模型慢还是在等人点确认」,只有后者的数据形态能答。反过来,如果你想回答的是「这次请求被路由到哪个 provider、重试了几次、是不是走了 fallback」,那是 openrouter_metadatastrategyattemptattemptsendpoints 这几个字段的活儿,attempt0 文档写明表示请求根本没到 provider(候选在提交前被过滤光了)。

落地端:配置在谁那儿、按什么分叉

这一条决定了很多团队最终选哪个,比数据字段更现实。

维度OpenRouter BroadcastClaude Agent SDK
配置位置账号 / 组织级服务端设置(文档写明在 Settings > Observability)跑 CLI 的那个进程的环境变量
客户端改动文档自述不需要在应用代码里加埋点需要设环境变量,或用 options.env 逐次传
分叉方式按 destination 分叉,可同时配多个按信号分叉(metrics / logs / traces 各自的 exporter 与端点)
凭据存放目的地凭据由 OpenRouter 加密存储OTLP 头由你自己在环境里配
团队统一organization admin 可配组织级共享目的地管理员可用 managed settings 锁定 OTLP 目的地

按 destination 分叉这件事的实际价值,文档里给了具体机制:每个目的地都能单独配 API Key 过滤(只接收指定 API key 发出的请求的 trace,不选则全收)、采样率、以及 Privacy Mode。文档明确举了个用法:调试用的目的地收全量带内容的 trace,成本监控用的目的地收脱敏后的 trace。Claude Code 侧的分叉维度不一样 —— 它是按信号分的,可以给 metrics 和 logs 指定不同的 endpoint、protocol、headers,managed settings 里设了通用端点还会在启动时移除开发者自己设的分信号端点。

采样这一项两边不对称:OpenRouter 文档写明每个目的地可配采样率,取值 1.0 表示发送全部 trace,且采样是确定性的 —— 只要你传了 session_id,同一 session 内的 trace 会被整体保留或整体丢弃,不会碎成半截(但文档也说明,不同目的地之间保留的 session 不一定相同)。Claude Code 的这两页文档里我们没有找到对应的采样开关说明,这一点不比。

内容默认在不在里面:方向是反的

这是最容易咬人的一条,两边默认值方向相反。

  • OpenRouter Broadcast 默认带内容:请求与响应数据本来就在那六类默认内容里,你要不带,得逐个目的地去勾 Privacy Mode。文档写明开启后剥掉的是输入 messages 与输出 choices,token 数、成本、时序、模型信息、自定义 metadata 照常发送。
  • Claude Agent SDK 默认不带内容:文档写明遥测「默认是结构化的」,时长、模型名、工具名每个 span 都有,但 agent 读写的内容默认不记录。要带内容得逐个打开 opt-in 变量 —— OTEL_LOG_USER_PROMPTS(prompt 文本)、OTEL_LOG_TOOL_DETAILS(工具输入参数,含文件路径与 shell 命令)、OTEL_LOG_TOOL_CONTENT(工具输入输出全文,需先开 tracing)、OTEL_LOG_RAW_API_BODIES(完整 API 请求响应 JSON,文档写明其中含完整会话历史,开这个等于同意前三个变量会暴露的一切)。

如果你的合规要求是「日志里不许出现 prompt 原文」,这个方向差异意味着两边要做的动作不同:一边是默认合规、你要显式解锁,另一边是默认不合规、你要逐个目的地去关。漏关一个目的地,内容就出去了。

还有两处不比

  • 导出失败怎么感知:Claude Code 侧文档写得很直白 —— CLI 默认在导出出错时静默失败,agent 照跑,遥测被丢掉,要看到导出错误得另设 CLAUDE_CODE_OTEL_DIAG_STDERR=1(文档写明需 Claude Code v2.1.179 或更高版本),再从 SDK 的 stderr 回调里读。OpenRouter 侧的目的地配置页(例如 OpenTelemetry Collector 那一页)写明配置时有一步 Test Connection,且只有测试通过配置才会保存,但**「保存之后某条 trace 发失败了你怎么知道」这一点,我们在 OpenRouter 文档里没有找到对应说明,不比。**
  • 带内读取用量openrouter_metadata 是带内的;Claude Agent SDK 侧文档提到可以「直接从 SDK 响应流里读 token 用量与成本」而不导出到后端,但那是另一页文档的主题,本文没有依据展开对照。

从你的处境倒推

  1. 你能不能改跑 agent 的那台机器 / 容器的环境变量? 不能(比如 OpenRouter 的调用方是别人的服务,或者你只是提供 API key),那 Broadcast 这条路是唯一可行的 —— 它整条链路都配在服务端。
  2. 你要回答的问题里有没有「工具」和「权限」? 有(哪个工具跑了多久、等确认等了多久、subagent 干了什么),那必须走 Claude Agent SDK 的 traces,并接受它目前是 beta、span 名与属性可能变。
  3. 你要回答的问题是不是「这次路由到底怎么走的」? 是,那 X-OpenRouter-Metadata: enabled 是最短路径,不用先搭一套后端。记得留意缓存命中与 500 不带这个字段,需要事后补查时,文档写明可以用响应头 X-Generation-Id 去查 generation 记录。
  4. 落地端是数仓 / 对象存储而不是看板? Broadcast 的目的地类型里本来就有这类;Claude Code 侧是标准 OTLP,得靠 collector 自己中转一道。
  5. 合规口径是什么? 参照上一节的方向差异,先确认你要做的是「解锁」还是「关闭」。

两边的字段名、环境变量名和默认值都会随版本变动,落地前请以官方文档最新内容为准。


本文依据 OpenRouter 官方文档(openrouter.ai/docs)于 2026-08-18 的公开内容整理。 该平台闭源,本文只复述官方文档写明的机制,不推断其内部实现我们没有对文中涉及的功能做过实测,因此不涉及界面外观与运行表现的任何描述。 该平台的供应商、模型与路由策略随时变动,文中不列具体供应商名单与模型清单; 价格、额度与限流的具体数值请以官方定价页与用量说明为准。

本文涉及的另一方内容依据其官方文档整理(Claude Code:code.claude.com/docs)。 双方均为闭源商业产品,本文只对照各方公开写明的机制,不推断实现,也不对产品做优劣排名

安全与合规相关做法请结合自身环境评估,本文不构成安全方案建议。

想系统学会用 AI?报名体系课或加入会员,照着学、照着用。