智能体跑起来之后,它调用过哪些工具、哪一步最慢、哪些请求失败了,靠日志猜是不够的。这篇按 Anthropic 官方文档走一遍:把 Claude Agent SDK 的追踪、指标与日志事件接到一个 OTLP 后端,从起本地收集器到在看板上核对完整调用链。
给智能体接一套可观测性:追踪、指标与日志
这篇教程解决什么
智能体与一次普通的接口调用有个本质区别:它会在一轮任务里反复调模型、调工具、再调模型,中间任何一步都可能变慢或失败。等它跑完,你手里通常只有一段最终输出 —— 至于时间花在哪、哪次工具调用拖了后腿、失败的那次请求卡在什么参数上,光看日志是拼不出来的。
这篇教程做的是一件很具体的事:把 Claude Agent SDK 的遥测数据接进一个 OTLP 后端,让你能在看板上看到一轮任务的完整调用链、每一步的耗时,以及 token 与成本的计数。
读完之后你会得到:一个本地可跑的收集器、一份能直接用的环境变量配置、一次带遥测的调用,以及在看板上核对调用链的方法。
前提与不覆盖的部分先说清:
- 假定你会用 Python 或 TypeScript 写脚本、能在终端里配环境变量,不需要预先了解 OpenTelemetry 的细节。
- 不覆盖后端选型对比。Jaeger、Grafana、Datadog、Honeycomb 这些都收 OTLP,本文只在本地用 Jaeger 验证通路,选哪个留给你自己的团队现状。
- 不覆盖成本归因报表怎么做。本文只保证数据能出去、能看见,报表是下一步的事。
- 文中所有变量名与开关都照官方文档原文给出;凡官方文档标注需要某个版本以上才生效的,本文一并标注,实际以你安装的版本为准。
开始前的准备
先把东西备齐,再动手。少一样都会在后面变成很难查的「没数据」。
- Python 3.10 以上,或 Node.js 环境。 官方文档的示例覆盖两种语言,本文两种都给。
- 安装 SDK。 Python 侧的包名是 claude-agent-sdk,TypeScript 侧是 @anthropic-ai/claude-agent-sdk;具体安装命令以官方文档的入门页为准,别照着记忆敲。
- 本机装好 Claude Code 命令行工具。 这一条最容易被忽略:SDK 自己不产生遥测,它以子进程方式启动命令行工具,遥测由后者直接导出。命令行工具不在 PATH 里,后面的环境变量配得再对也不会有数据。
- 账号凭据。 按官方文档的认证方式配置好 API Key 或已有订阅。子进程从自己的环境里读凭据,容器化部署时尤其容易漏。
- 一个接收 OTLP 的后端。 本地验证用 Jaeger 的 all-in-one 容器就够,不需要先搭一套完整的可观测性平台。
- 提前确认一件事:遥测默认是关的。 官方文档写得很明确 —— 必须显式打开总开关并至少指定一个导出器,否则不会有任何数据出去。这不是配置没生效,是设计如此。
步骤详解
第 1 步:确认命令行工具能被 SDK 找到
做两件事:在终端跑一次 claude --version(命令以官方文档为准),确认能输出版本号;再确认 SDK 能正常导入,Python 侧就是 python -c "import claude_agent_sdk" 不报错。
看到什么算成功:版本号正常输出,且 import 不报 ModuleNotFoundError。
为什么先把这一步单独拎出来:SDK 与命令行工具是父子进程关系,遥测由子进程导出。命令行工具找不到,父子进程就起不来,后面所有配置都不会产生数据,而且不会报错 —— 你会以为配置错了,其实是路径问题。
第 2 步:起一个本地 OTLP 收集器
用 Docker 起 Jaeger 的 all-in-one 镜像,把两个端口映射出来:4318(OTLP over HTTP 的默认端口)与 16686(Jaeger 界面)。镜像标签与完整命令以官方文档为准,不同版本端口可能调整。
看到什么算成功:浏览器打开 16686 能进 Jaeger 界面,此时 Service 列表是空的 —— 空的才对,说明还没数据进来。
这里有一个官方文档专门标出来的禁令:不要用 console 导出器。原因是 console 导出器把遥测写进标准输出,而 SDK 恰恰把标准输出当作自己的消息通道,两者会互相干扰。想在本地看数据,就把 OTLP 终点指向本地收集器,而不是往屏幕上打。
第 3 步:把遥测开关与导出器配齐
遥测分三种信号,各自独立开关,可以只开需要的部分:
- 指标:由 OTEL_METRICS_EXPORTER 打开,内容是 token、成本、会话、代码行数与工具决策的计数器。
- 日志事件:由 OTEL_LOGS_EXPORTER 打开,内容是每次提示、API 请求、API 错误与工具结果的结构化记录。
- 追踪:由 OTEL_TRACES_EXPORTER 打开,内容是每一轮交互、每次模型请求与每次工具调用的 span。追踪目前处于 beta,必须额外设 CLAUDE_CODE_ENHANCED_TELEMETRY_BETA=1 才会导出;指标与日志事件不需要这个变量。
再加上总开关与 OTLP 传输三件套,一份本地可用的配置是这八行(写进 .env 或直接在 shell 里 export 都可以):
CLAUDE_CODE_ENABLE_TELEMETRY=1
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://localhost:4318
OTEL_EXPORTER_OTLP_HEADERS=Authorization=Bearer your-token
最后一行在本地无鉴权时可以省略;有鉴权时官方文档提醒,凭据通过环境变量提供,不要写进提交到仓库的配置文件里。
配置放在哪有两种选择:放在进程环境里(shell、容器或编排器),这样每次调用都自动生效,是官方推荐的生产做法;或按调用传入,适合同一进程里不同智能体需要不同配置的场景。
第 4 步:跑一次带遥测的调用
Python 侧把配置作为环境变量字典传进去:
import asyncio
from claude_agent_sdk import query, ClaudeAgentOptions
OTEL_ENV = {
"CLAUDE_CODE_ENABLE_TELEMETRY": "1",
"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://localhost:4318",
}
async def main():
options = ClaudeAgentOptions(env=OTEL_ENV)
async for message in query(prompt="列出当前目录下的文件", options=options):
print(message)
asyncio.run(main())
TypeScript 侧有一处必须注意的差异:它的 env 会整个替换掉继承来的环境,而 Python 侧是叠加在继承环境之上。所以 TS 里要先展开 ...process.env 再合并你的遥测变量,否则 PATH 与 API Key 会被一起丢掉,症状是「环境变量明明配了,但命令找不到」。官方示例里也专门注明了这一点。
看到什么算成功:任务正常跑完,同时 Jaeger 的 Service 列表里出现了新的条目。如果你只看到任务输出、看板没动静,直接翻到下面的排错一节 —— 大概率是导出静默失败了。
第 5 步:在看板上核对调用链
打开 Jaeger,找到刚才那个 service,你会看到这样的结构(span 名称照官方文档列出):
- claude_code.interaction —— 包住一轮交互,从收到提示到给出响应。
- claude_code.llm_request —— 每次模型调用,属性里带模型名、延迟与 token 计数,是 interaction 的子 span。
- claude_code.tool —— 每次工具调用,同样是 interaction 的子 span,下面还嵌着 claude_code.tool.blocked_on_user(等权限确认的那段)与 claude_code.tool.execution(执行本身)。
- claude_code.hook —— 每次 hook 执行,是 interaction 的子 span,但需要额外打开详细 beta 追踪才会生成。
嵌套关系是这套数据的价值所在:智能体派生子智能体时,子智能体的 llm_request 与 tool span 会嵌在父智能体的 tool span 下面,所以整条委托链在同一条追踪里就能看全,不用去拼日志。
span 默认带 session.id 属性,同一会话的多次调用可以按它筛成一条时间线 —— 排查「这个会话后来为什么变慢」时靠的就是它。把 OTEL_METRICS_INCLUDE_SESSION_ID 设成假值可以去掉这个属性。
一个提醒:官方明确写了追踪仍在 beta,span 名称与属性可能在版本之间变化。不要把自动化断言或告警规则写死在某个 span 名上,升级时留一次核对。
第 6 步:加上归属维度,让数据能筛
只有调用链还不够 —— 等数据量上来,你需要能按环境、服务、团队去筛。做法是用 OTEL_RESOURCE_ATTRIBUTES 带上这些维度,例如服务名与环境名。
官方文档在这里有个提醒值得抄下来:这个变量可以做终端用户归因,形成按人的审计轨迹,属于要按自己的数据策略处理的数据;属性值需要按百分号编码。换句话说,加维度之前先想清楚哪些维度你们允许进可观测性系统。
第 7 步:短任务把导出间隔调小
默认导出间隔是:指标 60 秒,追踪与日志 5 秒。这对长期运行的服务没问题,但对跑完就退出的短任务是个坑 —— 数据还没到导出周期,进程就结束了,看板上什么都没有。
做法是把三个间隔都调小到 1000(毫秒):
OTEL_METRIC_EXPORT_INTERVAL=1000
OTEL_LOGS_EXPORT_INTERVAL=1000
OTEL_TRACES_EXPORT_INTERVAL=1000
注意第一个变量是 METRIC 单数,不是 METRICS。这个拼写很容易顺手写错,写错了不报错,只是不生效 —— 属于最难发现的那一类问题。
常见坑与排错
数据一直不出现,但没有任何报错。 这是最常见的现象。命令行工具对导出错误是静默失败的:终点不可达或拒绝数据时,智能体照常运行,遥测被直接丢弃,应用层完全无感知。要看到导出错误,设 CLAUDE_CODE_OTEL_DIAG_STDERR=1(需要命令行工具 v2.1.179 及以上),再通过 SDK 的 stderr 回调读诊断信息。排查时先把这个开关打开,能省掉大量猜测。
用了 console 导出器,输出被自己的消息流淹掉。 回到第 2 步:SDK 把标准输出当消息通道,两条流会互相干扰。想本地看数据就指向本地收集器。
TypeScript 里配置写了却丢了 PATH 与 API Key。 env 是替换不是合并,先展开 ...process.env。
span 里看不到提示词与文件内容。 这是默认行为,不是配置问题。遥测默认是结构化的:只记耗时、模型名、工具名与 token 计数,不记智能体读写的内容。需要内容时要显式打开,见下一节。
开了追踪但没有 hook 的 span。 hook span 需要额外设 ENABLE_BETA_TRACING_DETAILED=1 与 BETA_TRACING_ENDPOINT。注意官方文档提示,这一对变量还会改变日志与追踪的发送位置,加之前先看一眼现有的终点配置。
指标里 token 计数缺失。 官方文档说明:token 计数只在底层 API 请求返回了用量数据时才记录,失败或中途中止的请求可能没有对应数据。看到计数比预期少,先确认是不是有失败请求。
进阶用法
前面七步解决的是「看得见」。等真的要定位具体某次调用时,才需要往下开内容类开关。官方文档给了四个,风险与信息量逐级上升:
- OTEL_LOG_USER_PROMPTS=1 —— 在用户提示事件与 interaction span 上记录提示词文本。
- OTEL_LOG_TOOL_DETAILS=1 —— 记录工具的输入参数(文件路径、shell 命令、搜索模式),并让成本与 token 指标带上真实的智能体、技能、插件与 MCP server 名称。
- OTEL_LOG_TOOL_CONTENT=1 —— 在 tool span 上附带工具输出,包括文件内容与 shell 输出,默认截断为 60KB,可用 CLAUDE_CODE_OTEL_CONTENT_MAX_LENGTH 调整(需要 v2.1.214 及以上),且要求追踪已开启。
- OTEL_LOG_RAW_API_BODIES —— 把完整的 API 请求与响应导出为日志事件。设为 1 时内联(默认同样截断 60KB),设为 file:目录 时把未截断内容写盘、事件里只给路径引用。官方文档提示:它包含整个对话历史,启用它等于同时接受前三个变量会暴露的所有内容。
取舍建议是明确的:不要一上来就开 RAW_API_BODIES。推荐顺序是先在预发布环境用 OTEL_LOG_TOOL_DETAILS 定位,确认确实需要看到内容,再往上加一级。官方文档的原话是,除非你的可观测性管道已被批准存储智能体所处理的数据,否则不要设置这些变量 —— 这句话不是免责声明,是实际操作顺序。
另一条实用做法:把 interaction 的耗时与 llm_request 的 token 计数做成两张看板。前者回答「卡在哪一步」,后者回答「成本花在哪」。跨会话的问题用 session.id 串起来查。
常见问题
遥测会不会把代码和提示词发出去?
默认不会。遥测默认是结构化的,只记耗时、模型名、工具名与 token 计数,不记智能体读写的内容。提示词、工具参数、工具输出、完整请求体这四类都需要显式打开对应的开关才会导出,每档开什么、截断多少,官方文档都写清楚了。
一定要自建收集器吗?
验证通路时自建一个本地收集器最省事,因为不用申请账号、也不用考虑数据出境。但生产环境直接用现成的托管后端更合理 —— 它们收的都是同一套 OTLP 协议,换后端只需要改终点与鉴权头,代码不用动。
追踪还在 beta,能上生产吗?
能上,但要有心理准备:官方明说 span 名称与属性可能在版本间变化。稳妥做法是先把指标与日志事件上生产(这两项不在 beta),追踪先在预发环境跑,并且不要把告警规则写死在 span 名上。
已经有日志了,为什么还要 span?
日志回答「发生了什么」,span 回答「这件事在整条链里处于什么位置、前面卡了多久」。智能体一轮任务可能有几十次模型与工具调用,靠时间戳在日志里拼顺序,在多轮并发时会直接失效。span 的父子关系把顺序显式记录下来了。
这套只适用于 Claude 生态吗?
配置方法是 Claude Agent SDK 的,但思路是通用的:只要是「多步、会调外部工具、可能失败」的智能体,都需要把调用链显式记下来。换成别的框架时,换成该框架自己的导出方式,看板这一层不用重做。
相关工具与内容
- Claude Code —— 本文使用的命令行智能体,终端形态便于接进流水线
- 腾讯CodeBuddy —— 插件、IDE 与命令行三种形态,国内直连的替代路线
- 文心快码 Comate —— 多智能体协同,跨文件改造的国内选项
- 通义灵码 —— 支持代码不出内网的部署方式,合规场景可用
- CodeRabbit —— 专攻评审环节,与生成类工具不重叠
- Dify —— 需要把智能体放进可视化流程时看这一篇
- AI 编程工具分类 —— 编程类工具的完整清单
- AI 智能体工具分类 —— 编排与自动化平台清单
- 2026年AI编程工具推荐榜单:21 款定位与取舍 —— 编程类工具的横向对比
- 给智能体接上实时联网搜索:从一次调用到可用工具 —— 上一篇智能体实操教程
- 用 EmbeddingGemma 2 搭本地多模态检索 —— 把检索做成显式步骤的做法
相关内容
智能体编排工具怎么选:模型之外还有三道题
十月第三周多家厂商同时把开放权重模型的卖点压到长程智能体上,模型这一层的可选面变宽了。真正卡住的仍是编排层:数据放哪、谁维护、模型能不能换。逐个看五款编排工具各自守住的位置,并给出按场景取舍的路径。
Reflection AI 发布 Beam:5010 亿参数开放权重模型,Apache 2.0 授权,主打编码与长程智能体
Reflection AI 在 2026 年 10 月 16 日宣布 Beam 正式可用,并以 Apache 2.0 许可释出权重。这是一个总参数 5010 亿、每 token 激活 230 亿的稀疏专家混合模型,官方公布 100 万 token 上下文与可调推理强度,面向编码与长程智能体任务。同日亚马逊云科技给出 SageMaker 上的部署路径,Vercel 的 AI Gateway 也上架了该模型的免费档。
pydantic-ai v2.57.0 发布:接入 Meta Muse 模型、工具调用可先由模型审批,并打通 MCP Apps 前端通路
pydantic-ai 在 2026 年 10 月 16 日发布 v2.57.0。这一版新增了对 Meta Muse 系列模型的接入(走 Responses 接口),给 Harness 增加了让模型先批准低风险工具调用、再交给人工复核的 ApprovalReviewer,并把 MCP Apps 的工具结果打通到前端,同时附带若干兼容性调整。
继续阅读:Reflection AI 发布 Beam:5010 亿参数开放权重模型,Apache 2.0 授权,主打编码与长程智能体 · Black Forest Labs 发布 FLUX 3 Image Klein:56 亿参数开放权重图像模型,支持编辑与最多 10 张参考图