ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

PyPTO-Gym 算子开发成本监控:`monitor_cost.py` 的日志 Schema、计时模型与阶段解析实战指南

PyPTO-Gym 算子开发成本监控:`monitor_cost.py` 的日志 Schema、计时模型与阶段解析实战指南 PyPTO-Gym 算子开发成本监控monitor_cost.py的日志 Schema、计时模型与阶段解析实战指南【免费下载链接】pypto-gymPyPTO-Gym 是基于 PyPTO 编程框架构建的算子与模型样例仓库项目地址: https://gitcode.com/cann/pypto-gymPyPTO-Gym 仓库中的pypto-op-monitor技能提供了一套只读的算力成本剖析器它解析一次 PyPTO Agent Team「算子生成运行」的编排器orchestrator转录与全部子代理转录把墙钟时间切分为推理 / 工具执行 / 调度等待 / 设置 / 人工等待 / 空闲六个时间桶并按 Agent、Prompt、Stage、整轮运行四个维度汇总 token 用量与 Read/Write/Edit 文件足迹。本文以仓库内的 log-schema.md 为主干结合 monitor_cost.py 源码与 SKILL.md 命令说明完整讲解日志字段布局、计时归因规则、阶段解析算法、子代理生命周期判定以及 Claude Code 与 opencode 两种转录源各自的解析方式。读完本文你将能够读懂monitor_cost.py输出的七节成本报告并能根据日志 Schema 自行扩展或校验提取逻辑。1. 概览一份共享记录两种转录来源pypto-op-monitor的核心定位是「在运行结束或运行进行中安全地计算一次 Agent 团队运行花了多少钱」——它从不触碰算子产物、MEMORY.md或.orchestrator_state.json因此对已完成或进行中的运行都无害。提取器支持两种转录来源Claude Code磁盘上的 JSONL 转录编排器主转录 subagents/子代理转录事件间隔由时间戳差值推断见 第 3 节opencodeopencode export sessionID导出的 JSON含每个消息与每个工具的显式起止时间见 第 4 节。两种来源最终都汇入同一份共享记录parse_transcriptClaude与parse_opencode_sessionopencode分别解析再统一经过_finalize_record收尾工具分类统一走classify_tool其词表由每种来源各自的SourceSpec提供。因此下游的代价模型第 2 节、聚合、渲染以及--json输出对两种来源完全一致——每个来源小节只需要说明「该来源如何提供共享概念」。这个「一份记录、两份解析、一套分类器」的设计在源码中有直接体现SourceSpec是一个 frozen dataclass持有五种工具类别集合dispatch派生子代理、human等待人工、read/write/edit文件交互其余工具一律归为普通tool执行monitor_cost.pyClaude 词表为Task/Agentdispatch、AskUserQuestionhuman、Read/Write/Edit/MultiEdit/NotebookEdit文件opencode 词表为小写task/read/write/edit且没有human等价工具monitor_cost.py_new_record为两种来源生成完全相同的记录骨架_finalize_record统一计算wall_s、active_s并冻结各 defaultdict使两份记录「字节级可比」monitor_cost.py。2. 共享代价模型时间桶、Token 与文件交互2.1 六个时间桶与 Active 定义每份转录编排器或子代理都变成一条记录其墙钟时间被加性切分无重叠为六个时间桶外加 token 与文件交互两个计数维度。主转录自身会作为「伪 Agent」参与解析role 与 stage 均为orchestrator因为生命周期调用——子代理派发、Skill加载、人工提示——都发生在这里。时间桶含义计入 Activereasoning模型生成延迟思考时间的代理指标是tool-exec[name]某个工具的运行时间按工具名分别累计是setup小型 prompt/附件注入间隔是dispatch_wait父代理等待子代理派发结果的时间与子代理运行重叠否user_wait等待人类回复的时间否idle超过--idle-threshold的 reasoning/setup 间隔否Active reasoning tool-exec setup。其余桶被排除dispatch_wait与子代理运行重叠user_wait/idle不属于计算。源码中_finalize_record正是用rec[active_s] rec[reasoning_s] rec[toolexec_s] rec[setup_s]汇总 Active 时间monitor_cost.py。无论来源如何测量时长以下规则共享工具执行从不封顶。一次 verifyBashNPU 编译 运行合法运行 30 分钟以上若加 idle 阈值封顶会破坏占主导地位的真实成本。等待人不算计算。一次答复可能跨越数小时因此归入user_wait绝不进 tool-exec保证工具汇总保持合理Claude 的AskUserQuestionopencode 无带内等价物其user_wait通常为 0。idle 阈值只作用于 reasoning/setup 间隔默认 600 秒可经--idle-threshold覆盖。真实生成是秒级到分钟级长达数小时的「reasoning」间隔实为暂停/离开被重分类为 idle。子代理无人值守运行、几乎不会触发阈值——idle 几乎完全是编排器现象。并行调用下各工具时长之和可能超过墙钟时间重叠这与并发 Agent 的情形一致。2.2 Run Wall操作者生成窗口Run Wall 操作者生成窗口从第一个活动到最后一个派发的 Agent 结束。编排器 SESSION 可以比运行活得更久保持打开、被复用那段空闲尾巴不计入根转录自身的跨度会被裁剪到该窗口使其按 Agent 统计的 wall 与运行对齐。源码中_run_window计算run_end max(子代理 end)再通过_trim_record_end把超出窗口的记录末尾裁掉monitor_cost.py——这也是为什么「编排器会话挂机数小时」不会污染报告的原因。2.3 Token 记账与 Prompt 分组每个 assistant 事件携带四个 token 桶按 Agent 与按 Prompt 分别求和两者都精确对账到运行总量input本次调用新输入的 prompt tokencache_read本次调用重读的缓存上下文——廉价且累计cache_write写入缓存的 tokenoutput生成的 token。恒等式total input cache_read cache_write output。其中cache_read通常压倒其他三项——不断增长的上下文是每次调用都重读而非新数据要看「真正新增的 token」请读input output。本工具不计算美元成本。源码中的字段映射见USAGE_FIELDSClaude 的input_tokens/output_tokens/cache_read_input_tokens/cache_creation_input_tokens被映射为input/output/cache_read/cache_creation四个短名monitor_cost.py。Prompt 分组一个prompt即一次用户轮次user turn。解析器跟踪进行中的轮次 id把每个 assistant 事件及其文件工具调用归属到该轮次并用该轮次的首段文本作为短标签。首个轮次之前的一切落入__preamble__哨兵桶什么都不丢弃。子代理通常只有一个 prompt即它的派发指令→ 按 prompt 统计 ≈ 按 Agent 统计运行中途被再次 message 的子代理则每条消息各一个 prompt。2.4 文件交互Read / Write / Edit按 Agent 与按 Prompt 统计rd/wr/ed 的调用次数、读写行数、编辑 ± 行数以及去重文件数三种操作取并集。关键规则即使调用报错也计入一次调用而大小指标需要成功结果——因此calls ≥ filesWrite会区分新建与覆盖运行级按路径汇总出「最常被触碰的文件」列表一个被反复编辑/重读的文件是上下文抖动信号例如算子的MEMORY.md只有这三种工具计入——Bash cat、附件、技能加载都不算编辑 ± 行数来自结构化 patch/diff而非语义 diff。源码佐证Claude 侧_apply_claude_file_result通过toolUseResult的形状判别操作类型——.file字典 → read、存在oldString→ edit、contenttype ∈ {create, update}→ writemonitor_cost.pyopencode 侧则按toolpart 的名称与state元数据判别第 4.2 节。2.5 阶段解析显式匹配 → 角色回退 → 模块标签每个 Agent 归入一个阶段桶orchestrator、1–7或support两种来源规则相同显式匹配优先描述匹配Stage\s(\d)不区分大小写——覆盖重设计循环如Stage 4 redesign…以及验证器合法地横跨 Stage 4–7。角色回退按 Agent 类型取默认阶段Agent 类型默认阶段pypto-op-planner1 — Planningpypto-op-mathematician2 — Algorithmpypto-op-architect3 — Architecturepypto-op-coder5 — Constructionpypto-op-debugger5 — Constructionpypto-op-optimizer7 — Optimizationpypto-op-verifier无默认——依赖显式描述Explore、general-purpose、其他support模块标签描述中的\bM(\d)\bM1、M12…暴露 Stage-5 的「按模块 coder → verifier → debugger」循环缺失时为null。support容纳spawnDepth ≥ 2的嵌套助手以及任何阶段无法解析的 Agent它们的时长计入总量但不计给任何工作流阶段。验证器刻意不设角色默认值它确实阶段模糊→ 未标注的验证器落入support而非被错误归因。源码中resolve_stage正是这三步的实现先re.search(rStage\s(\d), desc, re.IGNORECASE)未命中则查ROLE_DEFAULT_STAGE字典再查M(\d)模块号monitor_cost.py阶段标题映射见STAGE_TITLE1 Planning、2 Algorithm、3 Design、4 Verification preparation、5 Construction、6 Verification、7 Optimizationmonitor_cost.py。这些 Agent 角色定义可以在仓库的 pypto-op-orchestrator/agents 目录下逐一找到pypto-op-planner.md、pypto-op-mathematician.md、pypto-op-architect.md、pypto-op-coder.md、pypto-op-debugger.md、pypto-op-optimizer.md、pypto-op-verifier.md其团队协作约定见 orchestration.md。2.6 子代理生命周期与两种派发模式每个子代理通过一个全局id→start / id→end 索引与派发它的调用 join该调用可能位于主转录也可能位于另一个子代理中——嵌套助手场景。对每个子代理dispatch_s end(join_id) - start(join_id) # 父代理观察到的时长 self_wall 子代理转录自身跨度同步派发dispatch_s ≥ self_wall − tol父代理被阻塞overhead dispatch_s − self_wall 生成关闭开销约 5 秒没有独立的 close 事件因此两者合并。异步派发dispatch_s self_wall − tol派发立即返回、结果稍后到达父代理未阻塞。这种方式测不到 overhead——报告如实标注「不可测」绝不给出误导性的0.0s。子代理的成本即它自己的按 Agent wall。两种模式在真实运行中都会出现。join id 是meta.toolUseIdClaude见 3.2 节或子会话 idopencode见 4.3 节compute_lifecycle对两者完全一致monitor_cost.py。3. Claude Code 转录源磁盘 JSONL3.1 磁盘布局~/.claude/projects/ project-dir/ # cwd 中所有非 [A-Za-z0-9] 字符替换为 - session-id.jsonl # 主编排器转录 session-id/subagents/ agent-id.meta.json # 被派发 Agent 的静态元数据 agent-id.jsonl # 子代理转录每行一个事件坑project-dir的开头/会变成开头的-这就是为什么--project必须写成--projectname裸的-前缀值会被argparse当作 flag 解析。subagents/目录只有在编排器派发第一个 Agent 之后才会出现——这也被_run_claude用作判断依据找不到子代理日志时给出明确提示monitor_cost.py。目录名编码由encode_project_dir实现即re.sub(r[^A-Za-z0-9], -, path)monitor_cost.py。3.2agent-id.meta.json字段字段示例用途agentTypepypto-op-coder团队角色 → 默认阶段见 2.5 节descriptionStage 5 M1: code latent→QKV阶段 模块的主要来源toolUseIdtoolu_01Dwg…派发该子代理的Agent/Task工具调用 id——生命周期 join 键见 2.6 节spawnDepth11 由编排器派发≥2 嵌套助手→supportload_run逐个读取*.meta.json用这些字段构造AgentContext并调用resolve_stagemonitor_cost.py。3.3 事件到共享模型的映射Claude 转录为换行分隔 JSON每行一个事件。关键键timestampISO-8601 UTC 毫秒带Z后缀用fromisoformat(ts.replace(Z,00:00))解析见parse_tstypeuser/assistant/attachmentmessage.content内容块message.usagetoken 用量promptId/promptSource轮次归属顶层toolUseResult文件操作结果。message.content块包括texttool_use{id, name, input}tool_result{tool_use_id, content}位于user事件中与tool_use.id配对thinking很少持久化。id 配对使归因在并行交错下依然稳健。计时——事件按时间排序间隔t_i − t_{i-1}按后一个事件E_i归入桶E_i是…归入assistant间隔 ≤ idle-thresholdreasoningassistant间隔 idle-thresholdidle带 tool_result 的user工具 ∈{Agent, Task}dispatch_wait带 tool_result 的user工具 ∈{AskUserQuestion}user_wait带 tool_result 的user其他任意工具tool-exec[name]从不封顶user纯 prompt/attachment间隔 ≤ idle-thresholdsetupuser纯 prompt/attachment间隔 idle-thresholdidle源码中_charge_claude_gap实现了这张表一个user事件可能携带多个 tool_result此时间隔在它们之间均分share gap / len(result_ids)并且没有前驱时间戳的 tool_result 也照常计数include_timeFalse只增加调用次数不计时长monitor_cost.py。Token——取自每个 assistant 事件的message.usageinput_tokens→ inputcache_read_input_tokens→ cache_readcache_creation_input_tokens→ cache_writeoutput_tokens→ output。usage还带iterations细分只使用顶层总计它与最后一次迭代相等。文件——操作类型由下一个事件的toolUseResult形状判定与工具名无关调用次数按tool_use块中的名称统计操作toolUseResult形状大小指标read.file {filePath, content, numLines, …}lines numLinesbytes len(content)write{type: create\|update, filePath, content, structuredPatch}lines len(content.splitlines())bytes len(content)type→ 新建 vs 覆盖edit{filePath, oldString, newString, structuredPatch}增删行 structuredPatch各 hunk 中的/-行判据靠特征键消歧.file字典 → read存在oldString→ editcontenttype ∈ {create, update}→ write。编辑行数由_patch_counts从structuredPatch的 hunklines中数出monitor_cost.py。生命周期 join——meta.toolUseId见 2.6 节。_index_claude_block在索引阶段就把每个tool_use的(name, timestamp)写入index.use、每个tool_result的tool_use_id写入index.res跨全部转录共享monitor_cost.py。4. opencode 转录源opencode exportJSONopencode 没有磁盘树opencode export id打印一个 JSON 对象{info, messages[]}。用临时文件导出不要走管道。opencode 把导出流式写到 stdout大会话会撑爆 OS 管道缓冲管道捕获subprocess … capture_output会在 64K/96K 边界截断且rc0不报错。opencode_export()重定向到临时文件shell重定向形态再读回monitor_cost.py。4.1 运行组装一次运行的子代理是子会话而非文件。根ses_…会话是编排器其中每个task工具 part 携带state.metadata.sessionId 子会话 id。load_opencode_run用 BFS 遍历这些子会话逐个导出覆盖任意嵌套层级monitor_cost.py。子会话的info.agent是团队角色info.title例如Stage 5: … (pypto-op-coder subagent)喂给同一套阶段解析见 2.5 节。--list-sessions以只读方式读取 opencode 的 sqlite 存储session表、parent_id IS NULL列出顶层运行及其子会话数monitor_cost.py。4.2 事件到共享模型的映射概念opencode 来源事件messages[]中的一个条目 {info, parts[]}info.role∈user/assistant时间戳info.time.created/.completedstate.time.start/.end——epoch 毫秒÷1000生成段一个assistant消息reasoningassistant 消息跨度completed − created减去其中的工具时间超过阈值按 idle 处理tool-exec[name]各toolpart 的state.time.end − start之和按小写名归键read/bash/…dispatch_wait各taskpart 时长之和setup / idle消息结束到下一消息开始之间的间隔≤ / idle-thresholdthinking 块reasoningpart 的计数wall全部消息与工具时间戳的跨度源码对应parse_opencode_session对每个 assistant 消息用(completed - created).total_seconds() - tools_duration计入 reasoning把消息间间隔经_charge_gap计入 setup/idle并显式累计所有toolpart 时长monitor_cost.py。Token——每个assistant消息携带info.tokens {input, output, reasoning, cache:{read, write}}input→ inputoutput reasoning→ outputreasoning token 是生成输出折入后total才能对账cache.read→ cache_readcache.write→ cache_write。按消息求和与 opencode 自身的session.info.tokens精确相等已验证。文件——按toolpart 名称read/write/edit统计指标取自state操作大小指标路径 state.input.filePathreadlines state.output的content块中N:前缀行数bytes len(output)writelines len(input.content.splitlines())bytes len(content)state.metadata.exists→ 覆盖truevs 新建falseedit增删行 state.metadata.diff中 unified diff 的/-行跳过/---头opencode 不单独上报读取行数所以_oc_read_lines用正则(?m)^\s*\d:统计content内的行号前缀monitor_cost.pyedit 行数由_oc_diff_counts解析 unified diffmonitor_cost.py。4.3 生命周期 join父代理的task工具给出state.metadata.sessionId子会话与state.time.{start,end}。提取器把每个子记录的tool_use_id设为其自身会话 id并从父代理侧填充全局派发索引use[child] (task, start)、res[child] end于是未修改的compute_lifecycle见 2.6 节像 Claude 一样推导派发时长、模式与 overheadmonitor_cost.py。5. 运行方式命令、参数与七节报告monitor_cost.py仅依赖标准库Python 3.8无需第三方包。在 Claude Code 环境下脚本位于技能树的.claude/skills/pypto-op-monitor/scripts/monitor_cost.py在 opencode 下为scripts/monitor_cost.py。python3 MON # 当前 harness/项目最近一次运行 python3 MON --list-sessions # 列出运行新→旧然后退出 python3 MON --session id # 指定运行——Claude UUID 或 opencode ses_… python3 MON --json # 机器可读输出参数默认值用途--source auto\|claude\|opencodeautoauto遇到ses_…id 走 opencode否则在 Claude Code 环境下CLAUDECODE1走 Claude其余情况走 opencode--session id最近一次Claude 会话 UUID或 opencode 运行的根ses_…id--all-sessions关聚合所有运行Claude项目下所有opencode所有运行--list-sessions关列出运行后退出--idle-threshold s600超过该值的 reasoning/setup 间隔计为 idle工具执行间隔从不封顶--json关输出结构化 JSON 而非文本报告--projectdir/--projects-root p由cwd推导 /~/.claude/projects[claude]项目目录 / 日志根目录--opencode-bin p/--opencode-db popencode/~/.local/share/opencode/opencode.db[opencode]导出可执行文件 / 会话发现存储坑Claude 项目目录以-开头argparse 会当作 flag。请使用等号形式--projectname或省略它从cwd自动探测。文本报告按固定顺序呈现七节每节同时携带时间成本与 token 成本input / cache / output / total以及 wall 与 active 时间Overall costs——wall/active、token 拆分、文件交互、工具调用、生成段数、被排除的非工作时间user_wait idle dispatch_waitPer-stage costs——按阶段聚合 reason/tools/wall/active 与 token 四桶Per-agent costs——按 Agent 的日历跨度与真实计算时间Reasoning——各 Agent 的推理时长、生成段数、平均每段时长与thinking块数Tool calls——按工具的运行级汇总calls · total · avg · 占 tool-exec 占比含█条形图与各 Agent 的 top 工具File interactions——rd/wr/ed 的调用、行数、± 行数与去重文件数附「最常触碰文件」列表Agent lifecycle——每个子代理的派发模式sync/async、派发时长、自身 wall、spawnclose overhead 与 token。--json输出同一份数据并额外提供按 prompt 的下钻多轮转录时特别有用totals、tool_cost、top_files、lifecycle_summary、stages每阶段含agents每 Agent 含prompts数组字段如wall_seconds、reasoning_seconds、cache_read等均以秒/ token 数给出monitor_cost.py。文本报告与 JSON 的渲染分别见render_text与to_jsonablemain中的_detect_source实现auto的判定逻辑ses_前缀 → opencodeCLAUDECODE1→ claude否则 → opencodemonitor_cost.py。6. 已知局限请如实报告这些边界原文档与 SKILL.md 都强调「诚实读取」以下局限应随报告一并说明思考时间是代理指标生成延迟。显式thinking/reasoning块很少持久化因此块数仅作透明展示不是时间来源。idle 阈值是启发式。600 秒默认值在观测运行中能干净地分离「人离开」与「真实生成」但一个真正超过该值的超长单次推理突发会被误判为 idle。可用--idle-threshold调优。阶段准确性依赖描述。标注错误的派发会回退到角色映射或support。时长是墙钟而非 CPU——包含被工具与子派发阻塞的时间。Active ≫ wall 是正常的——Agent 并发运行报告总体节同时给出两者。cache-rd按设计占主导——缓存上下文每次调用都以廉价费率重读因此是累计值而非新输入看新 token 请读input output。工具不计算美元成本。文件交互仅覆盖Read/Write/Edit不含Bash cat、附件与技能加载调用即使报错也计数因此calls ≥ filesWrite区分新建与覆盖。Claude 与 opencode 计时来源不同——同一组桶Claude 从事件间隔推断推理/工具时间opencode 读取显式的按消息/按工具跨度reasoning 消息跨度 − 其中的工具时间opencode 工具名小写、reasoning token 折入outputopencode 侧求和与 opencode 自身总量精确对账。7. 源码速查关键符号与文件位置若需扩展或校验提取逻辑以下符号是核心锚点均在 monitor_cost.py 内SourceSpec/classify_tool——工具分类器与双源词表L88-L113USAGE_FIELDS——token 四桶字段映射L74-L79STAGE_TITLE/ROLE_DEFAULT_STAGE/resolve_stage——阶段标题、角色回退与阶段模块解析L53-L61、L269-L274DEFAULT_IDLE_THRESHOLD_S——默认 600 秒 idle 阈值L67PREAMBLE——首个轮次前的哨兵 prompt idL69DispatchIndex/compute_lifecycle——跨转录生命周期 joinL128-L139、L905-L933parse_transcript/parse_opencode_session——双源解析入口L553、L754_finalize_record/_run_window/_trim_record_end——共享记录收尾与运行窗口裁剪L327、L1008opencode_export/list_opencode_sessions——导出防截断与会话发现L594、L883。配套文件技能说明与报告模板见 SKILL.md日志 Schema 与计时模型见本文主题文档 log-schema.md。结合--json的按 prompt 下钻可以进一步定位「哪一轮次、哪个 Agent、哪次工具调用」是成本大头为缩减重复 verify 重跑、降低MEMORY.md编辑抖动、并行化串行阶段等优化提供数据支撑。【免费下载链接】pypto-gymPyPTO-Gym 是基于 PyPTO 编程框架构建的算子与模型样例仓库项目地址: https://gitcode.com/cann/pypto-gym创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表