集成AgentClaude Code

用 Litefuse 追踪 Claude Code

Claude Code 是 Anthropic 的终端编码 Agent。本集成使用 Claude Code 的 Stop hook 把每一轮对话发送到 Litefuse —— 无需修改 Claude Code 源码。

该 hook 会解析 Claude Code 的会话 transcript(位于 ~/.claude/projects/ 下的 JSONL 文件),并为每个用户回合生成一条 Litefuse trace,每个 LLM 调用(thinking / text / tool decision)和每次工具执行都会作为单独的 observation。

给 AI —— 自动安装

如果你正在和 Claude Code 对话,把下面这段 prompt 粘贴过去,Agent 会端到端地完成整个安装:

Read https://litefuse.ai/SKILL.md and follow the instructions to install and configure Litefuse for Claude Code.

skill 会向你索取 Litefuse 的 API Key(如果还没有账号,会引导你注册),然后在本地完成全部配置。如果你想手工一步步配置,请继续往下看。

会捕获哪些数据

数据捕获方式备注
用户 prompttrace input文本 + 图片 block 摘要
助手发出的每个 thinking / text / tool_use blockgeneration observation每个 JSONL 行一个 observation,使用真实时间戳
工具执行(输入 + 输出)tool observation按工具类型给出结构化的 toolUseResult 摘要
token 用量(input_tokensoutput_tokenscache_read_input_tokenscache_creation_input_tokensgeneration 上的 usage_details仅附加到每条 Anthropic 消息的最后一行,避免重复累加
模型名称generation 上的 modelLitefuse 用它来计算成本
API 错误 / 重试 / 限流event observation,level=ERROR来自 Claude Code 的 system
工具错误(is_error=truetool observation,level=ERROR附带 status_message 预览
会话分组trace 的 session_idClaude Code 会话 UUID
用户身份trace 的 user_id$LITEFUSE_USER_ID,回退到 getpass.getuser()
Claude Code 版本trace 的 release来自 JSONL 的 version 字段
环境上下文trace metadatacwd、git 分支、entrypoint、permission mode、sidechain 标志
单个 observation 的身份metadatauuidparentUuidrequestIdmessage.id —— JSONL 字段名原样保留

Trace 结构

一个典型的多工具回合产生的 trace 形如:

Claude Code - Turn 7                       (root span, AS_ROOT, trace headers)
├── user message                           (event — carries the user prompt)
├── Thinking (#1)                          (generation)
├── Text response (#2)                     (generation)
├── Decision to call tool: Bash (#3)       (generation, usage_details here)
├── Tool call: Bash (#3)                   (tool)
├── Thinking (#4)                          (generation)
├── Decision to call tool: Edit (#5)       (generation, usage_details here)
├── Tool call: Edit (#5)                   (tool)
└── Final response (#6)                    (generation)

设计说明:

  • 每个回合都以 user message event 开头。 这是一个瞬时的 event 类型 observation,把用户的 prompt 作为 input 携带。它视觉上和回合最后的 Final response (#N) generation 形成开闭对应,让 trace 时间线从上到下读起来像一个清晰的“开-闭”结构。
  • 逐行保真:每个 JSONL 行变成自己的 observation。同一条 Anthropic 消息中的 thinkingtexttool_use block 不会 被合并。
  • 唯一的 span 名称(#N) 步骤后缀让一个回合内的 observation 名称保持唯一,即便同一个工具被多次调用,Litefuse 的图视图也能保持线性。
  • 真实时间戳:span 的 start/end 来自 JSONL 的 timestamp 字段,而不是 hook 的本地时钟 —— Litefuse 的时间线反映的是事情真正发生的时刻,包括冷启动和排队延迟。
  • 不重复计算 token:多行消息的每个 JSONL 行都会带上同一份 usage block;hook 只把 usage_details 附加到最后一行。
  • 1ms 间隔:相邻的同级 span 之间留 1ms 间隔,这样 Litefuse 的图视图就不会把首尾相接的 observation 当成并行分支。
  • 命名空间化的 metadata,统一放在 claude_code.* 下。每个 Hermes 或 JSONL 专属字段都放在这一个 key 下面(例如 claude_code.uuidclaude_code.requestIdclaude_code.service_tier)。Litefuse 的标准字段(sessionIduserIdtagsreleaseusageDetails)保持在顶层。稀疏存储:源 JSONL 行中不存在的字段,metadata 中也完全不会出现,不会用 null 占位。
  • 未完成的回合会被延迟。 Claude Code 的 Stop hook 可能在 Agent 仍在循环中(在两次工具派发之间)时触发。如果某个回合最后一个助手行不是 text block(比如还在等下一个工具结果的 tool_use),hook 会 延迟 发送 —— 它会回退自己的字节偏移量,等下一次 Stop 触发,那时候收尾的 text“Final response”通常已经到达。每个会话维护一个 emitted_user_uuids 集合,确保已发送过的回合不会因为回退而被重复发送。代价:如果 Agent 真的在没有最终文字回复的情况下结束了一个回合(比如在工具循环中被强制中止),那么这一回合不会产生 trace。

快速开始

前置条件

  • Python ≥ 3.10(Langfuse SDK v4 的要求)。可以用 python3 --version 检查。macOS 自带的 python3 通常是 3.9 —— 通过 Homebrew 安装更新版本(brew install python@3.13,或任意 3.10+ 的小版本),并确保它作为 python3 出现在 PATH 最前面。
  • https://litefuse.cloud 创建一个 Litefuse 项目,拿到 public 与 secret key。

创建 virtualenv 并安装 Langfuse v4

把 hook 的依赖隔离在专用的 venv 里:

python3 -m venv ~/.claude/hooks/.venv
~/.claude/hooks/.venv/bin/pip install --upgrade pip
~/.claude/hooks/.venv/bin/pip install 'langfuse>=4,<5'

下载 hook 脚本

mkdir -p ~/.claude/hooks
curl -fsSL https://litefuse.ai/integrations/claude-code/litefuse_hook.py \
  -o ~/.claude/hooks/litefuse_hook.py
chmod +x ~/.claude/hooks/litefuse_hook.py

源码也可以在同一 URL 直接浏览 —— 部署前可以先看一遍。

配置 ~/.claude/settings.json

加入 Stop hook 与 Litefuse 凭据:

{
  "env": {
    "TRACE_TO_LANGFUSE": "true",
    "LANGFUSE_PUBLIC_KEY": "pk-lf-xxx",
    "LANGFUSE_SECRET_KEY": "sk-lf-xxx",
    "LANGFUSE_BASE_URL": "https://litefuse.cloud"
  },
  "hooks": {
    "Stop": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "\"$HOME\"/.claude/hooks/.venv/bin/python3 \"$HOME\"/.claude/hooks/litefuse_hook.py"
          }
        ]
      }
    ]
  }
}

如果想按项目使用,把同样的 env 块放进 <project>/.claude/settings.local.json 即可。

验证

在 Claude Code 中发送一条消息,然后查看 hook 的日志:

tail -f ~/.claude/state/litefuse_hook.log
# Expected: "Processed N turns in X.XXs (session=...)"

在 Litefuse 中打开对应项目 —— 每条用户消息会变成一个具有上面所示结构的 trace。

环境变量

变量必填说明
TRACE_TO_LANGFUSE设为 "true" 启用 hook。任何其他值都会让脚本短路退出。
LANGFUSE_PUBLIC_KEYLitefuse 项目 public key(pk-lf-...)。
LANGFUSE_SECRET_KEYLitefuse 项目 secret key(sk-lf-...)。
LANGFUSE_BASE_URL默认 https://cloud.langfuse.com。设为 https://litefuse.cloud(或你自托管的 URL)。
LITEFUSE_USER_ID覆盖 trace 的 user_id。回退到 getpass.getuser(),再回退到 hostname。
CC_LANGFUSE_DEBUG设为 "true" 启用详细的 hook 日志。
CC_LANGFUSE_MAX_CHARSspan 输入/输出的截断阈值(字符数)。默认 1000000(约 1MB 文本)。

Trace metadata 参考

所有 Claude Code 专属字段在 observation metadata 中都放在 claude_code.* 命名空间下。JSONL 字段名原样保留(按原 transcript 中的 camelCase / snake_case 写法)。Litefuse 的标准字段(sessionIduserIdtagsreleaseusageDetails)通过 OTel trace 属性保持在顶层 —— 不会重复出现在 claude_code 下。

Trace root span —— claude_code.*(来自用户消息行):

  • uuidparentUuidpromptIdisMetaorigin —— 行身份
  • cwdgitBranchversionentrypointuserTypeisSidechainpermissionMode —— Claude Code 环境上下文
  • sessionIdturn_numbertranscript_pathmodels_usedsource —— 衍生字段
  • user_text_truncateduser_text_orig_len —— 在用户 prompt 被截断时设置
  • user_content_text_blocksuser_content_image_blocksuser_content_image_media_types —— 在用户发送了图片时设置

user message event —— claude_code.*

  • uuidparentUuidpromptId —— 与 trace root 相同的身份字段
  • user_content_*user_text_* —— 与 trace root 相同的内容 / 截断摘要

Generation observation —— claude_code.*(来自助手行):

  • uuidparentUuidrequestId —— JSONL 行身份
  • id(Anthropic message id)、stop_reasonstop_sequencestop_details —— Anthropic 消息字段
  • service_tierspeedinference_geoiterations —— Anthropic usage 元数据
  • cache_creation(ephemeral_1h / ephemeral_5m 拆分)、server_tool_use —— 仅信息性字段;计入 usage_details
  • diagnostics —— Anthropic 的 cache_miss_reason 等;当 cache_creation_input_tokens 异常大时很有用
  • context_managementcontainer —— Anthropic 一侧的字段,存在时才出现
  • step_indexis_last_in_message —— 衍生字段
  • assistant_text_truncatedassistant_text_orig_len、… —— 仅在文本被截断时出现

Tool observation —— claude_code.*

  • nametool_use_id —— 来自 tool_use block
  • uuidparentUuidsourceToolAssistantUUID —— 来自携带 tool_resultuser
  • is_error —— 来自 tool_result block
  • toolUseResult —— 扁平摘要;字段因工具类型而异:
    • Bashcommandstdout_lenstderr_lenhas_stderrinterrupted
    • WebFetch / WebSearchcodecodeTextbytes
    • Task(子 agent):agentIdagentType
    • BashOutputbackgroundTaskIdassistantAutoBackgrounded
    • Glob / Grep / ReadappliedLimitappliedOffsetitemCount
  • input_truncatedinput_orig_lenoutput_truncatedoutput_orig_len —— 截断信息

Event observation —— claude_code.*(来自 system 行 —— API 错误、重试、hook 结果):

  • 所有 system 行字段原样保留:subtypelevelcauseerroruuidparentUuidstopReasontoolUseIDhookCounthookErrorshookInfosretryAttemptmaxRetriesretryInMspreventedContinuationhasOutput
  • stop_hook_summary 子类型被有意过滤掉了(它记录的是这个脚本自身的执行)。

工作原理

每次 Stop hook 触发时,脚本会:

  1. 从会话 JSONL transcript 中读取自上次 offset 以来的新字节(状态缓存在 ~/.claude/state/langfuse_state.json,以 sha256(session_id::transcript_path) 为 key)。
  2. 解析行;按 uuid 去重(transcript 在会话恢复时偶尔会重复行)。
  3. 把行分组成回合:每个真正的 user 消息开启一个回合;后续的助手行 + tool_result 行 + 非 summary 类型的 system 行附加到该回合。
  4. 拿每个回合的 user_msg.uuid 与该会话的 emitted_user_uuids 集合比对(也缓存在 state 中)。已发送的回合直接跳过 —— 防止偏移量回退(见第 6 步)时的重复发送。
  5. 判断最后一个回合是否完整:如果最后一个助手行包含 text block(即 Claude Code 的“Final response”收尾标志),该回合即视为完整并发送。否则 —— 通常因为 Agent 仍在循环中、最新的助手行是等待下一轮的 tool_use —— hook 延迟 处理。
  6. 延迟时:把 offset 回退到读取前的值,下一次 Stop 触发会重新读取相同字节。那时缺失的 text 行通常已经到达,回合也就可以发送了。emitted_user_uuids 集合保证回退期间不会重发之前已完成的回合。
  7. 每个完整回合通过 Langfuse SDK v4 的 OTel 层向 Litefuse 发送一条 trace,使用 JSONL 中显式的 start/end 时间戳,让时间线反映每个 block 实际到达的时刻。
  8. 保存新的 offset + emitted_user_uuids,下一次 hook 调用从这里继续。

该 hook 是 fail-open 的:任何意外错误都会写入 ~/.claude/state/litefuse_hook.log,脚本以 0 退出,因此永远不会阻塞 Claude Code 的主循环。

故障排查

Litefuse 中没有出现 trace。tail -f ~/.claude/state/litefuse_hook.log 看看日志。空日志意味着 hook 没在运行 —— 确认 TRACE_TO_LANGFUSE=true 已设置,并且 settings.jsoncommand 路径正确(venv 的 python3 加 hook 脚本)。

hook 日志显示 Langfuse client init failed venv 里的 Python 没装好 langfuse。重新执行:

~/.claude/hooks/.venv/bin/pip install --upgrade 'langfuse>=4,<5'

hook 日志显示 Missing session_id or transcript_path; exiting. Claude Code 没有在 stdin 上传入预期的 payload。确认你的 Claude Code 版本会发出 Stop hook payload(见 Claude Code hooks)。

token 总数看起来不对。 hook 只把 usage_details 附加到每条 Anthropic 消息的最后一行 —— 在 Litefuse 中查看单个 generation observation 即可验证:只有 Decision to call tool: X (#N) / Final response (#N) 行带有 token 计数;同一条消息的 Thinking (#N) / Text response (#N) 不会带。

有一个回合从未出现为 trace。 看 hook 日志 —— 如果你看到 Processed N turns, deferred 1 (in-progress),最近的回合因为最后一个助手行不是 text block 而被暂时挂起。下一次 Stop 触发时会重新评估它。如果该回合在工具循环中被强制中止(永远不会有 text 收尾行),延迟就变成永久的,不会发送 trace。在 Claude Code 中再发送一条后续消息,被延迟的回合会被重新检查:如果它后面已经出现了更新的用户消息,followup 去重路径就会无视完整性条件直接发送(一个旧回合后面跟着新的用户消息,已经“尽可能完整”了)。

手动测试 hook:

TRACE_TO_LANGFUSE=true \
LANGFUSE_PUBLIC_KEY="pk-lf-..." \
LANGFUSE_SECRET_KEY="sk-lf-..." \
LANGFUSE_BASE_URL="https://litefuse.cloud" \
~/.claude/hooks/.venv/bin/python3 ~/.claude/hooks/litefuse_hook.py
# Errors appear in the log file.

启用调试日志:

# Add to settings.json env block:
"CC_LANGFUSE_DEBUG": "true"

资源

这个页面对你有帮助吗?