用 Litefuse 追踪 Kimi Code
Kimi Code 是月之暗面(Moonshot AI)的终端编码 agent(kimi CLI,运行 Kimi-k2 系列模型)。Kimi Code 没有插件或 hook 机制,但它为每个会话写一份完整的事件日志——wire.jsonl。本集成是一个轮询采集器:一个零依赖(纯标准库)的单文件 Python 脚本,由 launchd(macOS)或 cron(Linux)每 30 秒运行一次,解析新增的 wire 事件,把 span 直接发往 Litefuse 的 OTLP 端点。不改 Kimi Code 源码、不装 SDK、不建虚拟环境。
采集器每个用户回合生成一条 trace:每次 LLM API 调用一个 generation、每次工具执行一个 tool observation、每次子 agent 委派一棵完整子树。
给 AI —— 自动安装
如果你此刻正在和 AI agent 对话(用 Kimi Code 本身也行),粘贴这句话,agent 会端到端完成安装:
Read https://litefuse.ai/SKILL.md and follow the instructions to install and configure Litefuse for Kimi Code.
skill 会向你索取 Litefuse API Key(没有账号会引导注册),然后就地完成全部配置。想手动一步步装,继续往下看。
采集内容
| 数据 | 形式 | 说明 |
|---|---|---|
| 用户 prompt | trace input | 文本;含图片/视频时在 metadata 记 block 数 |
| 每次 LLM API 调用 | generation observation | plan (n tools) #N / response / think #N,按模型行为命名;输出保留 thinking / text / toolCall 块结构 |
| 工具执行(输入+输出) | tool observation | tool: bash (grep) #N——名字带关键信息,完整参数在 input |
子 agent(Agent 工具) | 子树 | tool (1 subagent) #N → subagent 容器 → 子 agent 自己的 plan/tool/response 步骤,从子 agent 自己的 wire.jsonl 解析;子 agent 用量汇入父 trace |
| Token 用量 | generation 的 usage_details | Anthropic 风格 key(input / output / cache_read_input_tokens / cache_creation_input_tokens),由 Kimi 的 inputOther / output / inputCacheRead / inputCacheCreation 映射而来 |
| 模型名 | generation 的 model 属性 | 如 kimi-code/kimi-for-coding——Litefuse 据此计算成本 |
| 首 token 延迟 | generation 的 completion_start_time | 来自 Kimi 的 llmFirstTokenLatencyMs |
工具失败(isError) | tool observation,level=ERROR | 带 status_message 预览 |
被取消的回合(turn.cancel) | 根 span level=WARNING | status message 为 turn cancelled by user |
| 被中断的回合 | 根 span level=WARNING | LLM 调用从未完成的回合(如凭据过期、进程被杀) |
| 会话分组 | trace session_id | Kimi Code 会话 id(session_<uuid>) |
| 用户身份 | trace user_id | $LITEFUSE_USER_ID,回退到系统用户名 |
| 工作目录 | trace metadata(agent_cwd) | 来自 Kimi Code 的会话索引 |
Trace 结构
一个含子 agent 委派的回合产出如下结构的 trace(真实示例):
Kimi Code — Turn 7 (AGENT 根 span,携带 trace header)
├── plan (3 tools) #1 (generation——usage、真实延迟、TTFT)
├── tool: read (overview.txt) #2
├── tool: read (events.jsonl) #3
├── tool: read (queue.jsonl) #4
├── plan (1 tool) #5
├── tool (1 subagent) #6 (tool——父进程视角的委派)
│ └── subagent (AGENT 容器——从子 agent 的 wire.jsonl 解析)
│ ├── plan (1 tool) #1 (容器内编号从 #1 重计)
│ ├── tool: read (overview.txt) #2
│ ├── plan (1 tool) #3
│ ├── tool: bash (grep) #4
│ └── subagent response (generation——子 agent 的最终回答)
└── response (generation——最终回答,结束回合)设计说明:
- 一个用户回合 = 一条 trace,只发完整回合。 采集器只发已结束的回合——最后一个 step 以
end_turn收尾、回合被取消、或已被更新的 prompt 取代。进行中的回合原地保留、下次轮询重读,因此一个回合永远不会被劈成两条 trace,每个 span 恰好发送一次。 - 诚实的时间语义。 Kimi 的
step.end时间戳包含工具执行(它标记 agent step 的结束,不是 LLM 调用的结束)。采集器把 LLM 的真实时长重建为step.begin + llmFirstTokenLatencyMs + llmStreamDurationMs,每个 tool span 则从它的tool.call事件计时到tool.result事件。权限审批的等待会表现为 generation 与 tool 之间的真实空隙——这正是实际发生的事。 - 每个 agent 容器一个步骤计数器。
#N是 generation 与 tool 共用的单一时间序列;每个子 agent 容器从#1重计。tool 的agent_plan_stepmetadata 指向发起它的 generation 的agent_step_index。 - 子 agent 子树。
Agent工具的结果首部携带agent_id:标识,采集器据此定位<session>/agents/agent-<n>/下子 agent 自己的wire.jsonl。委派工具 span 包裹容器是有意设计:工具 span 时长 − 容器时长 = 委派的真实开销。解析是递归的——不过注意 Kimi Code 目前不给子 agent 提供Agent工具,所以实践中不会出现超过两层 agent 的树。 - 确定性 ID。 trace 与 span 的 ID 由会话 id、回合号和事件 UUID 派生——状态重置后重跑是 upsert 而不是重复。
- 打平的
agent_*metadata。 所有集成字段以agent_前缀放在 metadata 顶层(agent_step_index、agent_plan_step、agent_duration_ms……)——与其他 Litefuse agent 集成共用同一套 key,一个看板查询跨所有集成生效。 - 回合内重建的 generation input。
wire.jsonl不记录每次 API 调用的完整请求体,generation 的 input 由当前回合内的消息重建(metadata 标注agent_input_scope: "turn");不含跨回合历史与 system prompt。
Trace 何时出现?
采集器每 30 秒轮询一次,且只上传已结束的回合,所以 trace 在回合给出最终回答后约 30 秒内出现——回合进行中什么都看不到。长回合要留意这一点(多子 agent 的委派可能跑好几分钟才有东西出现)。这是与 Pi 这类事件型集成的刻意差异——后者每个 observation 结束即发,而 Kimi Code 没有进程内扩展点可以做到这件事。
两个特例:中途被放弃的回合会在更新的 prompt 出现时立即发出(root 标 WARNING);闲置超过 30 分钟(可配置)的未完回合按”被中断”发出。
快速开始
前置条件
- Python ≥ 3.8——任何
python3都行,包括 macOS 自带的。采集器零第三方依赖:无 SDK、无虚拟环境、无 pip install。 - 已安装 Kimi Code(存在
~/.kimi-code/)。 - 一个 https://litefuse.cloud 项目及其 public + secret key。
下载采集器脚本
mkdir -p ~/.kimi-code/hooks
curl -fsSL https://litefuse.ai/integrations/kimi-code/litefuse_hook.py \
-o ~/.kimi-code/hooks/litefuse_hook.py
chmod +x ~/.kimi-code/hooks/litefuse_hook.py同一 URL 也可以直接浏览源码——部署前欢迎先读一遍。
配置 ~/.kimi-code/litefuse.env
采集器在 launchd/cron 下以空环境运行,所以凭据放在专用 env 文件里,每次运行时读取(改完不需要重启任何东西):
cat > ~/.kimi-code/litefuse.env <<'EOF'
TRACE_TO_LITEFUSE=true
LITEFUSE_PUBLIC_KEY=pk-lf-xxx
LITEFUSE_SECRET_KEY=sk-lf-xxx
LITEFUSE_BASE_URL=https://litefuse.cloud
EOF
chmod 600 ~/.kimi-code/litefuse.env注册定时任务
macOS(launchd)——每 30 秒运行:
cat > ~/Library/LaunchAgents/com.kimi.litefuse.plist <<EOF
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key><string>com.kimi.litefuse</string>
<key>ProgramArguments</key>
<array>
<string>/usr/bin/env</string>
<string>python3</string>
<string>$HOME/.kimi-code/hooks/litefuse_hook.py</string>
</array>
<key>StartInterval</key><integer>30</integer>
<key>RunAtLoad</key><true/>
</dict>
</plist>
EOF
launchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/com.kimi.litefuse.plistLinux(cron)——每分钟运行:
(crontab -l 2>/dev/null; echo "* * * * * python3 \$HOME/.kimi-code/hooks/litefuse_hook.py") | crontab -验证
在 Kimi Code 里发一条消息,等回答完成再等一个轮询周期,然后看采集器日志:
tail -f ~/.kimi-code/state/litefuse_hook.log
# 预期出现:"Emitted 1 turn(s) in X.XXs -> https://litefuse.cloud"在 Litefuse 打开项目——每个用户回合就是一条上文结构的 trace。
注意:首次运行时采集器会补齐所有既有会话,给每个历史回合各发一条 trace。想从干净状态开始的话,不要删 ~/.kimi-code/state/litefuse_state.json(那会重发全部历史)——直接开一个新的 Kimi 会话即可。
从 v1 升级
旧版 hook 依赖 Langfuse Python SDK,并把每个回合压成 Kimi Response (step N) 式的合成 generation。v2 不需要 SDK,并遵循统一的 trace 结构:
- 用新脚本覆盖旧脚本(如有自定义先备份)。
launchd/cron条目可以保留——只要脚本路径不变。 - 保留
~/.kimi-code/litefuse.env,原有 key 继续可用。LANGFUSE_*名字仍作为 fallback,但LITEFUSE_*优先。 - v2 重命名了 observation(
plan (n tools) #N/response取代Kimi Response (step N),小写tool: bash (…) #N取代Tool: Bash #N),metadata 打平为agent_*key——记得更新保存过的看板过滤器。 - v2 只发完整回合;如果 v1 留下了消费到一半的会话,v2 首次运行会从保存的 offset 自动重新同步。
环境变量
所有变量都放在 ~/.kimi-code/litefuse.env(或进程环境——env 文件不会覆盖已设置的变量)。LITEFUSE_* 优先;同名 LANGFUSE_* 作为生态兼容的 fallback。
| 变量 | 必填 | 说明 |
|---|---|---|
TRACE_TO_LITEFUSE | 是 | 必须为 true,否则采集器什么都不做。 |
LITEFUSE_PUBLIC_KEY | 是 | Litefuse 项目 public key(pk-lf-...)。 |
LITEFUSE_SECRET_KEY | 是 | Litefuse 项目 secret key(sk-lf-...)。 |
LITEFUSE_BASE_URL | 否 | 默认 https://litefuse.cloud。别名:LITEFUSE_HOST。 |
LITEFUSE_TRACING_ENVIRONMENT | 否 | trace 的 Litefuse environment。默认 production;实验用 development。 |
LITEFUSE_USER_ID | 否 | 覆盖 trace user_id。回退到系统用户名。 |
LITEFUSE_EXTRA_TARGETS | 否 | 额外目标的 JSON 数组([{"publicKey", "secretKey", "baseUrl", "environment"}]),双写多个 Litefuse 实例(如自托管 + cloud)。 |
KIMI_LITEFUSE_DEBUG | 否 | 设为 "true" 输出详细日志。 |
KIMI_LITEFUSE_MAX_CHARS | 否 | span input/output 截断阈值(字符数)。默认 1000000。 |
KIMI_LITEFUSE_STALE_MINUTES | 否 | 未完回合判定为”被中断”的闲置分钟数。默认 30。 |
KIMI_LITEFUSE_BATCH_BYTES | 否 | OTLP 请求体分批上限。默认 800000;超限批次遇 HTTP 413 还会自动对半重试。 |
KIMI_LITEFUSE_STATE_DIR | 否 | 覆盖状态目录(~/.kimi-code/state)。主要用于测试。 |
Metadata 参考
所有集成字段都是带 agent_ 前缀的顶层 metadata key(跨 Litefuse agent 集成共用)。源数据中不存在的字段完全不出现,绝不用 null 占位。
Trace 根:agent_turn_number、agent_session_id、agent_cwd、agent_model、agent_provider、agent_transcript_path、agent_api_calls、agent_tool_calls、agent_steps、agent_duration_ms;prompt 含媒体时有 agent_image_blocks;被取消的回合有 agent_cancelled;截断标记(agent_prompt_truncated + _orig_len)。
Generation:agent_turn_number、agent_step_index、agent_provider、agent_stop_reason、agent_api_duration_ms、agent_time_to_first_token_ms、agent_stream_duration_ms、agent_tool_call_count、agent_thinking_chars、agent_step_uuid、agent_input_scope、截断标记。
Tool:agent_turn_number、agent_step_index、agent_plan_step(join 条件:tool.agent_plan_step == generation.agent_step_index)、agent_tool_name(原始大小写,如 Bash)、agent_tool_call_id、agent_duration_ms、agent_is_error、委派时有 agent_subagent_id、截断标记。
Subagent 容器:agent_subagent: true、agent_subagent_id、agent_subagent_type、agent_subagent_status,以及该次运行的 agent_api_calls / agent_tool_calls / agent_steps / agent_duration_ms。
工作原理
每次定时运行时脚本会:
- 加载
~/.kimi-code/litefuse.env,然后从~/.kimi-code/session_index.jsonl列出全部会话。 - 从每个会话的
agents/main/wire.jsonl读取上次 offset 之后的新行(状态在~/.kimi-code/state/litefuse_state.json,以sha256(session_id::wire_path)为 key,文件锁保护)。 - 以
turn.prompt为边界把事件切分成回合,组装 step(step.begin/content.part/tool.call/tool.result/step.end/usage.record)。 - 只发已结束的回合(最终
end_turn、turn.cancel、被更新的 prompt 取代、或闲置超过阈值);offset 停在任何进行中的回合之前,下次完整重读。 - 把
Agent工具的结果解析到子 agent 的wire.jsonl,递归展开子 agent 子树。 - 把所有 span 以 OTLP/HTTP JSON 发到
<base_url>/api/public/otel/v1/traces(按端点请求体上限分批、413 对半重试、Basic 认证、10 秒超时)。trace header 随每个 span 携带。
采集器是 fail-open 的:任何意外错误只写 ~/.kimi-code/state/litefuse_hook.log 并以 0 退出,绝不影响 Kimi Code 本身。
排障
Litefuse 里看不到 trace。 先 tail ~/.kimi-code/state/litefuse_hook.log。日志为空说明定时任务没在跑脚本——检查 launchctl print gui/$(id -u)/com.kimi.litefuse(或你的 crontab)。静默退出且无日志通常是 TRACE_TO_LITEFUSE 不为 true,或 ~/.kimi-code/litefuse.env 缺 key。出现 send failed: 则是 key 或网络问题。
最新一个回合不见了。 它多半还没结束——采集器只上传完整回合,然后等下一个 30 秒轮询。多子 agent 的回合可能要几分钟才出现。
某条 trace 的根标着 WARNING “interrupted”。 那个回合确实没有完成——Kimi 被杀、被重启,或 LLM 调用挂死(登录过期是典型:回合死在第一次调用上,你 /login 后重发,死掉的那次就被记录成一条独立的中断 trace)。这不是采集错误。
成本显示 0。 Litefuse 按模型名计算成本;在 Litefuse 项目的 Settings → Models 里添加与模型名匹配的价格条目(如 kimi-code/kimi-for-coding)。
手动测试采集器(使用 development environment,不污染生产数据):
LITEFUSE_TRACING_ENVIRONMENT="development" \
KIMI_LITEFUSE_DEBUG=true \
python3 ~/.kimi-code/hooks/litefuse_hook.py
tail ~/.kimi-code/state/litefuse_hook.log相关资源
- Kimi Code
- Litefuse Cloud
- 采集器脚本源码:
litefuse_hook.py