Trace Collector
用途
本 skill 同时承担两个独立流程:
- Flow A — Init skill:把本地 skill 目录上报到 prompt-engine 后端,做版本登记(生成
v1/v2/...)。 - Flow B — Collect trace:用户在当前对话中执行完一个 target skill 后,本 skill 收集本次 session 的完整执行过程,生成 trace + token 报告 + 产物拷贝,并自动上传到后端。脚本会自动锚定
use_skill调用为起点,剥离之前的对话历史,因此无需特意新开对话。
触发词路由
| 用户意图 | 触发词 | Flow |
|---|---|---|
| 注册/更新 skill 到平台 | init skill / 上报 skill / 注册 skill |
Flow A |
| 收集当前 session 的 trace | 收集 trace / collect trace / 分析执行过程 / token 报告 / token 开销 |
Flow B |
⚠️ 检测到触发词后只跑对应 Flow,不要混合执行。
平台 / Python 命令说明
下面所有示例都写成 python3 scripts/xxx.py,是 Linux / macOS 的写法。
脚本本身已做跨平台适配(CodeBuddy IDE 数据目录、日志目录、控制台 UTF-8 输出等),
直接用本地 Python 3.8+ 跑即可,无需 WSL。
⚠️ Windows PowerShell 执行规范(必读)
Windows 上执行 Python 脚本有两个常见坑,必须在第一次调用前就处理好,不要盲目重试:
1. 定位 Python 可执行文件
Windows 上 python / python3 / py 可能都不在 PATH 中,或者 PATH 中的是 Microsoft Store 的桩程序(会弹 Store 页面并返回 exit 1)。第一步先用 where.exe python 找到真实路径:
where.exe python
# 典型输出:
# C:\Users\xxx\AppData\Local\Microsoft\WindowsApps\python.exe ← Store 桩,不可用
# C:\Program Files\python\python.exe ← 真实 Python
- 如果
where.exe python返回多条,优先选 非WindowsApps的那条 - 如果只有
WindowsApps的,改用where.exe python3或where.exe py - 如果都找不到,用 IDE 的
install_binary工具安装 Python
2. PowerShell 执行带空格路径的程序必须用 & 运算符
Python 路径通常含空格(如 C:\Program Files\python\python.exe),在 PowerShell 中直接写带引号的路径会被解析为字符串表达式而非命令。必须用 &:
# ✅ 正确
& "C:\Program Files\python\python.exe" scripts\xxx.py --arg1 val1
# ❌ 错误 — PowerShell 会报 "意外的标记" 语法错误
"C:\Program Files\python\python.exe" scripts\xxx.py --arg1 val1
3. 推荐的完整执行模板
# 第一步:找 Python(只需做一次)
where.exe python
# 记住非 WindowsApps 的那条路径,假设是 C:\Program Files\python\python.exe
# 后续所有脚本调用统一用:
& "C:\Program Files\python\python.exe" "<SKILL_DIR>\scripts\xxx.py" --arg1 val1
> 关键:确定 Python 路径后,后续所有步骤直接复用,不要每次重新探测。
Flow A:Init skill(上报本地 skill 目录)
> ⚠️ Windows 用户:如果当前 OS 是 Windows,执行所有 python3 命令前,先完成上方「Windows PowerShell 执行规范」——用 where.exe python 找到非 WindowsApps 的 Python 路径,后续所有 python3 替换为 & "完整路径"。不要直接敲 python3。
A.1 准备 api_key
向用户索要 api_key(必须手输,不要从环境变量/本地文件读):
> "请提供 prompt-engine 平台的 api_key(Bearer token)。"
A.2 调脚本
cd <SKILL_DIR>
python3 scripts/init_skill.py \
--skill-dir <要上报的 skill 目录> \
--api-key <api_key>
脚本做:
1. POST /api/v1/skill/task/init → 拿 version_no + snapshot_cos_prefix
2. 遍历 skill 目录(自动跳过 .git / __pycache__ / node_modules 等)
3. 对每个文件:POST /api/v1/skill/upload/sign → PUT <signed_url> 上传到 COS
4. 输出版本号 + 上传文件数 + 总大小
A.3 上报失败处理
任何一步失败 → 脚本会 print ERROR 并 exit 1。不做降级。让用户检查后重新运行 init_skill.py(脚本会再升一个版本号;这是正常的,旧版本不会丢失)。
Flow B:Collect trace(收集执行 trace + 自动上传)
> ⚠️ Windows 用户:如果当前 OS 是 Windows,执行所有 python3 命令前,先完成上方「Windows PowerShell 执行规范」——用 where.exe python 找到非 WindowsApps 的 Python 路径,后续所有 python3 替换为 & "完整路径"。不要直接敲 python3。
产出目录结构
每次运行产出一个 <run_dir>/,结构如下:
<run_dir>/
└── trace/
├── trace_raw.jsonl 保真源,本地留存,不上传
├── trace_compact.json 紧凑视图(LLM 食材),本地留存,不上传
├── trace_steps.outline.json LLM 产物(薄结构),本地留存,不上传
├── trace_steps_usage.json IDE log per-LLM-call 真值(中间产物),本地留存,不上传
├── trace_steps.json ⭐ step 双层结构(前端 + 优化分析双消费) ← 上传
├── facts.json ⭐ token 优化诊断事实包(后端 LLM 输入) ← 上传
└── output/ target skill 自己生成的产物(拷贝) ← 整目录上传
├── _manifest.json 拷贝清单(含原路径 / 角色 / size)
└── <target skill 的产物文件/目录>
上传范围(Flow B Step 9):上传是白名单——只 2 个 ⭐ 文件 + output/ 整目录。trace_raw.jsonl / trace_compact.json / trace_steps.outline.json / trace_steps_usage.json 留本地复盘用,不上传。
核心架构决策:
- LLM 只写 outline(语义判断 + seq 索引 + target_skill_outputs 标注),不接触事件内容
hydrate_steps.py从 raw 字节级透传 args / result,前端两层展开- target skill 产物的"哪些重要"由 LLM 在 Step 4 标,
stage_output.py按名拷贝 - Token 真值来源:从 CodeBuddy IDE log 解析每次 LLM 调用的真实 usage(cache 命中率 / 真实金额 / 真实耗时 / 上下文水位 / 自动压缩事件 / bash 字节)。
trace_steps_usage.json是从 IDE log 解析的中间产物,注入到trace_steps.json后即可丢弃。
何时使用 Flow B
- 用户在当前对话中执行完一个 skill,想收集执行 trace 用于复盘 / 评测 / 上报
- 用户说「收集 trace」「collect trace」「分析执行过程」「token 报告」「token 开销」
前置要求
- 用户已经执行完 target skill(trace 收集的是「之前发生的事」)
- session 中至少出现过一次
use_skill/load_skill/skill_loader工具调用作为起点锚点;脚本会自动定位最近一次调用,剥离之前的对话历史,因此不要求新对话
> 多次执行同一 skill 的处理:若同一 session 内多次调用 use_skill,脚本只收集最近一次对应的 trace 段,并在输出 warnings 中提示。如需收集更早那次,请在该次执行完后立即触发 trace-collector,而不要等到又跑了几轮再来。
>
> 未检测到 use_skill 时的兜底:脚本不会硬性失败,而是保留全部历史并在 warnings 中提示。这通常意味着当前 session 不是标准的 IDE skill 执行流(如 WorkBuddy 等异构环境工具名不同),需要人工核对 trace 内容再决定是否上传。
Flow B 工作流程(10 步,严格执行)
Step 1:定位 session messages 目录
cd <SKILL_DIR>
python3 scripts/extract_trace.py --find-session
Step 2:提取原始 trace
cd <SKILL_DIR>
python3 scripts/extract_trace.py --session <session_path> --runs-base <SKILL_DIR>/runs
脚本会自动嗅探 target skill 名,把 run 目录命名为 <target_skill>-<时间戳>
(如 recap-script-generator-20260615-145241;嗅探不到时用 unknown-skill-<时间戳>),
并在输出 JSON 的 output_dir 字段回显最终路径。
⚠️ 后续所有步骤的 <run_dir> 都使用这里回显的 output_dir(不要再自己拼目录名)。
自动切分边界(无需用户介入):
- 头部锚点:定位 session 中最晚一次
use_skill/load_skill/skill_loader工具调用,回溯到最近一条 user 消息作为 trace 起点,剥离之前的所有历史。 - 尾部锚点:切掉 session 最后一条 user 消息(= trace-collector 自身的触发消息)及之后所有事件。
输出 JSON 含 segmentation 段(skill_start_found / head_trimmed_messages / tail_trimmed_messages / use_skill_invocations)和 warnings 段(多次 use_skill / 未找到 use_skill 等提示)——⚠️ 跑完 Step 2 后先扫一眼 warnings,确认切分符合预期再往后跑。
输出:
- <run_dir>/trace/trace_raw.jsonl
Step 2.5:⭐ 从 IDE 日志解析 per-LLM-call 真值
cd <SKILL_DIR>
python3 scripts/parse_ide_log.py --run-dir <run_dir> --session <session_path>
<session_path> 用 Step 1 --find-session 输出的 session_path(即 messages 目录路径)。
脚本会自动:
- 从 <session_path>/../index.json 拿 workspace hash 嗅探 IDE log 文件
- 从 index.json.requests[] + <run_dir>/trace/trace_raw.jsonl 的 msg_id 求交集,拿到 trace 范围内的 requestId 集合
- 输出 <run_dir>/trace/trace_steps_usage.json,含:
- 每个 agent 的每个 LLM call 的真实 usage(input/output/total/cache 命中/cache 写入/credit/duration)
- compact_events:CompactStrategy 决策事件、自动压缩事件(含被压 message 体量明细)
- fetch_events:每次 LLM 请求的发送字节数(含 gzip 压缩率)
- bash_events:每条 bash 命令的 exit / stdout 字节 / stderr 字节(按 call_id 关联)
- biggest_message:session 最大单条 message(来自 IDE history:parse)
- meta:model_id / max_input_tokens / compact_threshold
> 关于 IDE log 落盘:trace-collector 触发时,IDE log 通常已经把 target skill 的所有 agent 事件 flush 完了;实测未遇到过缺数据的情况。如果个别 step 显示异常(¥0 / 0 calls),过几秒重跑 Step 2.5 起的后续步骤即可(幂等,见文末「调试 Tips」)。
参数:
- --ide-log <path>:跳过 session 嗅探,直接指定 IDE log 文件(高级 debug 用)
- --no-filter:不按 requestId 过滤,保留 IDE log 里所有 agent
Step 3:生成紧凑视图
cd <SKILL_DIR>
python3 scripts/build_compact.py <run_dir>/trace/trace_raw.jsonl <run_dir>/trace/trace_compact.json
输出 <run_dir>/trace/trace_compact.json,~50KB,给下一步的 LLM 读。
Step 4:⭐ 主 agent 智能切分 → 写 outline
这步由你(主 agent)智能完成。一次性产 steps outline + 标 target_skill_outputs,共享 SKILL.md + compact 上下文。
4.1 嗅探 target skill 目录
从 <run_dir>/trace/trace_compact.json 找第一个满足下列任一条件的事件,取其 path 的父目录为 <target_skill_dir>:
- type == "tool_pair" 且 tool 在 {read_file, Read} 中且 args.filePath / args.file_path 以 /SKILL.md 结尾
- user 消息正文 regex 匹配 (?P<path>(/[^\s]+)?/skills?/[a-zA-Z0-9_\-]+) 的最早路径
若都没找到 → 走 4.5 fallback,不要 STOP。
4.2 读 target skill 上下文(只读 SKILL.md)
Read <target_skill_dir>/SKILL.md
Glob <target_skill_dir>/references/* (只列名,不 Read)
Glob <target_skill_dir>/scripts/* (只列名,不 Read)
⚠️ 不要 Read references/scripts 内容——切分依据是 SKILL.md 内的 ### 步骤 N: 标题 + trace 中的脚本/文件名匹配,references 内容不需要进入上下文。
4.3 Read compact 并做语义判断
Read <run_dir>/trace/trace_compact.json 全文。然后做以下判断:
A. step 切分:
- target SKILL.md 显式 ### 步骤 N:xxx → 按这些步骤切分
- tool 的 command / filePath 含某 step 的脚本名 / 文件名 → 该 step
- ⚠️ text / user / reasoning 事件也要列入 event_seqs:归到前后 tool 所属同一 step(hydrate 脚本有自动补全兜底——未分配的 text/user 会按时间戳自动归入最近 step——但你仍应主动列全以确保归属准确)
- 连续 LLM 推理 + 一次落盘的多个步骤 → 智能合并(如 step_2_3_4)
- 异常归到 step.anomalies(retry / SKILL.md bug / 自主补救)
B. IDE 元工具识别:
- 一般只有 use_skill;不同 IDE 可能有别的(如 load_skill、skill_loader)
- 不需要单独标注;hydrate_steps.py 会自动跳过 IDE 工具
C. target skill 产物标注(给 stage_output.py 用):
观察 trace 中 target skill 最终生产了哪些文件/目录,把绝对路径列到 outline 的 target_skill_outputs。
识别规则(取并集):
- write_to_file / Write 的 filePath(注意只保留最终版本;中间被覆盖的也算)
- 跑 Bash / execute_command 生成的产物文件(从 reasoning 文本 / SKILL.md 流程图判断该 step 生成了什么)
- target SKILL.md 的"## 输出"或类似段落里声明的最终交付物路径
⚠️ 不要列:
- 临时文件(命名含 .tmp / __pycache__ / cache_ 等)
- target skill 自己代码库内的 scripts/references 文件
- session jsonl / trace-collector 自己的产物
4.4 落盘 outline
<run_dir>/trace/trace_steps.outline.json — schema 参考 examples/trace_steps.outline.example.json:
{
"session_id": "...",
"collected_at": "<ISO>",
"target_skill": { "name": "...", "skill_md_path": "...", "auxiliary_sources_read": [] },
"user_input": "<净化后真实 user_query>",
"final_response": "<识别的最终输出>",
"warnings": [],
"steps": [
{
"step_id": "step_0",
"step_title": "...",
"step_summary": "...",
"anomalies": [],
"event_seqs": [0, 1, 2, 3, 8, 9, 10] // 列所有相关 seq:tool_call + text + user + reasoning;hydrate 自动配对 tool_result(不需要列 tool_result seq)。即使遗漏 text/user,hydrate 也会自动按时间归入最近 step
}
],
"target_skill_outputs": [
{
"path": "/abs/path/to/recap_script.json",
"role": "最终战报 JSON(含 segments / clips / title)",
"kind": "file"
},
{
"path": "/abs/path/to/recap_work/100000_xxx",
"role": "工作目录(含 part1/part2/title 等中间产物)",
"kind": "dir"
}
]
}
target_skill_outputs 字段说明:
- path: 绝对路径
- role: 简短角色描述(人读)
- kind: "file" 或 "dir"(stage_output.py 会按 path 实际类型校验)
4.5 Fallback(嗅探不到 target skill)
仍要产出 outline,但:
- target_skill.name = null,target_skill.skill_md_path = null
- warnings += ["未识别 target skill;步骤按 trace 结构粗切"]
- steps 用 "阶段 1 / 阶段 2" 占位
- target_skill_outputs = [](无法可靠识别)
Step 5:hydrate 出 trace_steps.json(前端 + 优化分析双消费)
cd <SKILL_DIR>
python3 scripts/hydrate_steps.py \
<run_dir>/trace/trace_raw.jsonl \
<run_dir>/trace/trace_steps.outline.json \
<run_dir>/trace/trace_steps.json
脚本字节级透传 args_full / result_excerpt(4KB),自动配对 tool_result,coverage check 保所有事件不丢。
⭐ 精确数据注入:脚本同时读 trace_steps_usage.json(来自 Step 2.5),把 per-LLM-call 真值注入到每个 step:
step.llm_calls[]:每次 LLM 调用的真实 usage(含 cache 命中/写入/未命中、credit、duration、payload 字节)step.step_totals:该 step 的 footprint / cache_hit_rate / credit (含 USD/RMB 换算) / duration / payload 汇总step.bash_summary:该 step 内 bash 命令数 + stdout/stderr 字节 + 失败数event[tool_pair].bash:每条 bash 命令的 exit / stdout / stderr 字节(按 call_id 关联)- 顶层
session_meta/compact_events/biggest_message/engine_anomalies
这些字段供 skill 优化分析直接消费;后续 Step 6 会在此基础上算出 facts.json 作为后端 LLM 诊断的事实包。
Step 6:生成 token 优化诊断事实包(facts.json)
cd <SKILL_DIR>
python3 scripts/compute_facts_pack.py <run_dir>
输出:
- <run_dir>/trace/facts.json — 后端 LLM 的事实包输入(~10-15 KB)
facts.json 含(schema_version=v1):
- meta — session_id / target_skill_name / model_id / total_steps / total_llm_calls / total_agents / biggest_message
- overview — total_input_tokens / output / cache_hit / cache_miss / cache_write / footprint / cache_hit_rate / total_credit
- Q1_bucket_estimates — A(canonical_A_per_call + per_agent + cross_agent_history_carry)/ B(top_persisted_payloads 按 persist_score 排序)/ C_call_count / D_output
- Q2_attribution — top_dominant_calls(按 credit 排)+ top_input_deltas(相邻 call Δ>10K)
- Q4_wasted_actions — bash_failures / false_success / repeat_failure_fingerprints / edit_chains_3plus / files_written_unused
- Q5_skill_md_analytics — skill_md(bytes / chinese_ratio / tokens_estimate)+ auxiliary_sources_read[]
- warnings — 自检告警(如 skill_md 体积异常、actually_compacted 事件)
facts.json 是把后端 LLM prompt 里所有 Σ / 排序 / Δ / hash / groupby 等算术预算好的中间结果,让 LLM 拿到后只做语义判断和反事实推理,不再做算术(详见 docs/token-optimization-methodology.md)。
注意:
- trace_steps.json / trace_steps_usage.json / trace_steps.outline.json 任一缺失会报错退出(请先跑 Step 5)
Step 7:拷贝 target skill 产物到 trace/output/
cd <SKILL_DIR>
python3 scripts/stage_output.py <run_dir>
脚本读 <run_dir>/trace/trace_steps.outline.json 的 target_skill_outputs 字段,逐项拷贝到 <run_dir>/trace/output/:
- 文件 → trace/output/<basename>
- 目录 → trace/output/<dirname>/...(保留内部结构)
- 同名冲突自动加 -2 / -3 后缀
- 源不存在 → manifest 标 missing,不失败
输出:
- <run_dir>/trace/output/ — target skill 生成的产物拷贝
- <run_dir>/trace/output/_manifest.json — 拷贝清单(含原路径 / 角色 / size / 是否成功)
注意:
- 若 outline 的 target_skill_outputs 为空,output/ 只含空 manifest,不失败
- 拷贝是 deep copy,原文件后续被改/删不影响本次 run
Step 8:上传到 prompt-engine 后端
向用户索要 api_key(必须手输):
> "请提供 prompt-engine 平台的 api_key(Bearer token),用于上报本次 trace。"
cd <SKILL_DIR>
python3 scripts/upload_trace.py \
--run-dir <run_dir> \
--api-key <api_key>
脚本做:
1. GET /api/v1/skill/task/info → 拿 task_id / created_by / active_version
2. 生成 trace_id = tr_<run_dir basename>,拼 COS 前缀 prompt-engine/formal/skill/{created_by}/{task_id}/traces/{trace_id}/
3. 列上传白名单 = 2 个 ⭐ 文件 + trace/output/ 下全部文件
4. 对每个文件:POST /api/v1/skill/upload/sign → PUT <signed_url> 到 COS
5. 全部上传成功后 → POST /api/v1/skill/trace/upload 提交 metadata(从 trace_steps.json + facts.json 抽:model / total_tokens / credit / duration_ms / cos_keys)
失败处理:任意一步失败 → 脚本 print ERROR 并 exit 1,不做降级。让用户重新跑 upload_trace.py 即可(trace_id 由 run_dir 决定,幂等键也用同值,重复上报后端会覆盖)。
Step 9:输出总结
═════ trace-collector: collection done ═════
session : <session_path>
run dir : <run_dir>/
trace/
trace_raw.jsonl N events, X KB (保真源,本地)
trace_compact.json M events, Y KB (LLM 食材,本地)
trace_steps.outline.json K steps + outputs list (LLM 产物,本地)
trace_steps_usage.json A agents, C LLM calls (IDE log 解析中间产物,本地)
trace_steps.json ⭐ K steps, Z KB (已上传)
facts.json ⭐ Q1-Q5 事实包 (已上传)
output/
_manifest.json F entries (copied / missing)
<target skill 产物> total S KB (已上传)
segmentation:
target_skill : <name or unknown>
total_events : N
matched_steps : K
coverage : 100%
warnings : <无>
tokens:
requests : R
llm_calls : C
total_cost : ¥X.XX (≈ $Y.YY / N credit)
cache_hit_rate : Y%
context_peak : T tokens (% of limit)
top hotspot : <step_title> (<pct>%)
next:
- 前端:fetch trace/trace_steps.json → 折叠 / 展开
- 优化:facts.json 已包含 Q1-Q5 事实包;后端用户在前端点「生成 Token 报告」时由后端 LLM 跑 methodology 出诊断报告
- 上报:Step 8 已自动完成(后端 skill_traces 表新增一条 trace_id 记录,COS 存 trace/ 下白名单文件)
前端展开模型
| 层 | 字段 | 来源 |
|---|---|---|
| L1 折叠态 | args_summary + ok + duration | trace/trace_steps.json |
| L2 内联展开 | args_full(完整)+ result_excerpt(4KB) | trace/trace_steps.json |
详见 FRONTEND.md。
反例黑名单
❌ 绝不做的事:
- 在 outline 里塞 args_full / result_excerpt / function_call — outline 是薄结构只 seq 索引;事件内容由 hydrate 脚本透传
- 嗅探不到 target skill 就 STOP — 应走 4.5 fallback 分支
- CHECKPOINT 询问用户 target skill 位置 — 自动嗅探即可
- outline 漏 seq — hydrate 脚本会 coverage check 并 warning,但你应主动覆盖完整
- 把 trace-collector 自己的产物列入 target_skill_outputs — 那是本 skill 的产物,不是 target skill 的
- 把 target skill 代码库内的 scripts / references 列入 target_skill_outputs — 那是源码不是产物(源码靠 Flow A
init_skill.py整体上报) - 跑 Flow B 时混进 Flow A 的指令(或反之)— 两个流程严格分流,按触发词路由
- 从环境变量 / 本地文件读 api_key — 必须每次手输(init 和 upload 都是)
- 上传失败时自作主张降级 — 失败 → print ERROR 退出,让用户重新跑
upload_trace.py(trace_id 幂等) - 给脚本加
--server参数指向其他后端 — 后端地址是脚本内SERVER常量硬编码,永远走正式环境http://aiengine.woa.com(当前 IAS 只在 80 端口配了转发规则,443 暂未启用;待 443 补齐后再切回 https,由维护者修常量,不接受运行时覆盖);用户没指定时不要脑补任何"测试 / 内网 / debug"环境。脚本仍接受--server但会静默忽略,老调用方不会炸。
调试 Tips
- compact 视图 ~50KB 是设计目标;超出可降低
RESULT_EXCERPT_BYTES - hydrate 后 steps.json 比 outline 大 10-20 倍(args.content 占大头),这是预期
- stage_output.py 可单独重跑(修改 outline 后再跑一次即重新拷贝)
- parse_ide_log.py 可单独重跑(IDE 日志事后还在硬盘,可重新解析)
- upload_trace.py 可单独重跑(trace_id 由 run_dir basename 决定,幂等 key 同值;如果之前部分文件已 PUT,重跑会重新 PUT,后端 metadata 记录被覆盖)
⚠️ 报告数据看起来不全(例如某些 step 显示 ¥0 / 0 calls)
可能根因:trace-collector 触发时 IDE log 缓冲区还没 flush 完所有 agent_end。实测极少见(触发时通常已 flush 完),但真遇到时,过几秒重跑后三步 + 上传即可:
python3 scripts/parse_ide_log.py --run-dir <run_dir> --session <session_path>
python3 scripts/hydrate_steps.py \
<run_dir>/trace/trace_raw.jsonl \
<run_dir>/trace/trace_steps.outline.json \
<run_dir>/trace/trace_steps.json
python3 scripts/compute_facts_pack.py <run_dir>
python3 scripts/upload_trace.py --run-dir <run_dir> --api-key <api_key>
这几步是幂等的,跑多少次都不会污染数据,每次都用最新的 IDE log 状态生成报告 + 重新上报。