ARTICLE DETAIL

资讯详情

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

Seedeep 可视化:让 Claude Code 执行过程从黑盒变成可检查的图

Seedeep 可视化:让 Claude Code 执行过程从黑盒变成可检查的图 Claude Code 这类终端 AI 编程代理能自己读文件、改代码、执行命令但你真正用它处理复杂任务时总会遇到一个尴尬时刻它到底在做什么你看得并不清楚。Seedeep 的作者也是这样因为看不清 Claude Code 的行为于是选择把整个过程画出来。这个项目叫 Seedeep名字本身就说明意图看透深层的执行过程。这篇文章会围绕 Seedeep 的设计思路讲清楚 Claude Code 的过程数据从哪里来、如何理解它、如何把它变成可检查的图并用一个最小实现演示从会话轨迹到可视化图表的完整链路。最后会结合实际使用中的场景说明这种可视化能力能用来排查哪些问题。1. 为什么 Claude Code 的过程会变成黑盒Seedeep 要解决什么问题1.1 Claude Code 的循环执行模式Claude Code 在命令行里的工作方式和常规脚本有本质区别。它不是一次性执行完一个固定流程而是进入一个循环读取用户目标调用一个或多个工具观察工具结果再决定下一步。常见动作包括读取文件、写入文件、编辑代码、执行 Shell 命令、搜索代码等。这个循环按“模型调用 - 工具执行 - 结果回传”的模式不断推进。一次稍微复杂的任务比如“修一个接口报错并补上测试”可能产生几十上百次工具调用。每次工具调用的输入、输出都独立存在但用户看到的只是终端里滚动的日志。问题就在这。日志里每条记录本身没有错但人脑很难在一条条滚动信息里维护一张“正在发生什么”的地图。你看到一次文件写入不知道它发生在整个任务的哪个阶段看到一条命令失败不知道它是由哪次决策触发的。1.2 黑盒感来自哪里把这段体验拆开黑盒感主要来自三个地方。第一文本流把结构化信息压平了。Claude Code 的日志本质上是一串线性的文本输出可工具之间的调用关系、文件之间的依赖关系是图结构不是线性结构。文本流天然不适合表达图。第二上下文在跳跃。Agent 可能在几秒内从读路由文件跳到改测试文件再跳到执行 npm 命令。日志里这些动作是连续的但逻辑上是分层的。读者很难判断“改测试文件”是为了验证“路由修改”还是独立任务。第三错误和决策之间的因果链不直观。一条命令失败后Agent 通常会读取错误信息再修改代码再重新执行。这个循环如果重复三轮日志里就是大段重复内容。人眼很难快速看出“它在这里卡住了”。Seedeep 的思路是把这些线性的执行记录重新组织成一张图节点是事件、文件、命令和错误边是调用关系、依赖关系和因果关系。这样人不必靠脑补还原过程可以直接看图。1.3 Seedeep 要回答的六个问题在画图之前先想清楚图要回答什么问题。这比选择渲染技术更重要。参考 Seedeep 项目的出发点围绕 Claude Code 的一次会话至少需要能回答以下问题问题日志方式图方式Agent 调用了哪些工具搜索Tool关键字按节点类型统计并分组每个工具调用的输入输出是什么翻上下文点击节点查看详细信息文件被哪些操作修改过对比 diff文件节点直接连接读写工具命令失败后发生了什么看后续输出从命令节点出发的错误路径整个任务花了多少时间凭感觉时间轴上的间隔清晰可见是否陷入了重复循环人工观察图中出现环结构这些就是可视化基座。Seedeep 做的是把“能回答问题的图”构建出来而不是简单地把日志变成彩色面板。注意图的价值不在渲染效果而在它能否帮你快速回答“刚才发生了什么、为什么变成现在这样、下一步该查哪里”。2. 先拿到 Claude Code 的事件数据可视化才有可操作的基础2.1 事件数据从哪里来要画图第一步是拿到结构化的执行数据。Claude Code 本身会保存会话信息。常见来源包括本地会话目录、claudeCLI 的日志参数或者调试模式下输出的会话记录。不同版本的数据位置和格式可能不一样。先别急着写解析代码建议按下面的顺序确认在终端输入claude --help查看当前版本暴露的参数。看是否有--debug、--log、--json之类输出日志的选项。检查本机的~/.claude目录下是否有项目级会话记录。如果使用 VS Code 插件确认插件是否将日志写到了扩展目录或共享会话文件。这里不写死具体路径是因为 Claude Code 迭代很快。硬编码路径的脚本很可能换一个版本就失效。更稳妥的办法是在项目启动时通过参数指定日志输出位置然后只解析自己关心的文件。2.2 准备一个最小可重复项目学习环境下建议用一个小项目做实验。这里以一个简单的 Node.js 项目为例任务目标是让 Claude Code 修改 README 中的安装命令并运行测试。准备内容一个包含README.md和package.json的目录。README.md中有npm install字样。package.json中有简单的 test 脚本。已经安装并登录好 Claude Code。任务描述尽量简单“把 README 里的 npm install 改成 npm ci然后运行 npm test”。这个任务会触发读文件、编辑文件、执行命令三类典型事件非常适合验证可视化工具。在未开启日志的情况下先运行一次记录终端输出然后开启日志再运行一次。两者对比能帮助你理解日志参数的实际作用。2.3 定义统一 JSONL 事件格式Claude Code 的真实日志格式未必适合直接画图。因此 Seedeep 这类工具通常会先做一层解析和统一把原始日志转成内部事件流。为了方便演示下面定义一种 JSONL 格式每行一条 JSON 事件包含时间戳、事件类型、调用 ID、输入输出等字段。{ts:2025-01-01T10:00:01.000Z,type:user,content:把 README 里的 npm install 改成 npm ci} {ts:2025-01-01T10:00:02.100Z,type:tool_call,tool_call_id:call_1,tool_name:Read,input:{file_path:README.md}} {ts:2025-01-01T10:00:03.200Z,type:tool_result,tool_call_id:call_1,content:# Demo\nInstall with npm install.} {ts:2025-01-01T10:00:04.400Z,type:tool_call,tool_call_id:call_2,tool_name:Edit,input:{file_path:README.md,replacement:npm ci}} {ts:2025-01-01T10:00:05.000Z,type:tool_result,tool_call_id:call_2,content:README.md updated} {ts:2025-01-01T10:00:06.100Z,type:command,command:npm test,exit_code:0} {ts:2025-01-01T10:00:07.300Z,type:assistant,content:修改完成测试通过}这个 JSONL 格式里tool_call_id是连接“调用”和“结果”的关键字段。没有它工具节点和结果节点无法闭合。ts字段提供时间坐标content字段提供展示信息。这里要说明这不是 Claude Code 官方导出的标准格式。如果你的日志格式不同解析层要做字段映射。比如原始字段叫tool_use_id或message_id就映射成内部统一的tool_call_id。2.4 验证事件数据的完整性拿到日志文件后先做三件事避免后续画图出错。第一检查 JSONL 行数。可以把文件按行拆分统计非空行数。如果一次任务只产生十几行说明日志级别可能没开全。第二检查tool_call_id是否能闭合。统计所有tool_call的 ID再统计所有tool_result引用的 ID。有对应却不闭合的事件通常是解析 bug 或日志截断。第三检查时间戳顺序。用脚本把ts字段转换成时间对象确认整体单调递增。如果出现大面积乱序说明日志来源不直接需要按 ID 重新排序。const fs require(fs); const lines fs.readFileSync(session.jsonl, utf8).split(\n).filter(Boolean); const callIds new Set(); const resultIds new Set(); let prevTs 0; let outOfOrder 0; for (const line of lines) { const evt JSON.parse(line); if (evt.type tool_call) callIds.add(evt.tool_call_id); if (evt.type tool_result) resultIds.add(evt.tool_call_id); const t new Date(evt.ts).getTime(); if (t prevTs) outOfOrder; prevTs t; } console.log(total events:, lines.length); console.log(tool calls:, callIds.size); console.log(tool results:, resultIds.size); console.log(out of order:, outOfOrder);这段脚本是一个最小校验器。如果tool calls和tool results数量差异过大先处理数据源再继续做图。3. Seedeep 的图画法节点、边和三种常用视图3.1 节点类型可视化的第一步是定义图画什么。参考 Seedeep 的视角节点不能只代表“一行日志”。它应该代表一个独立的对象。常用节点类型包括节点类型含义示例user用户给 Agent 的目标修复登录接口assistantAgent 的文本决策我先读取路由文件tool_call一次工具调用Read、Edit、Bashtool_result工具返回结果文件内容、命令输出file被读写的文件src/routes.tscommand被执行的命令npm testerror错误信息exit code 1、529 报错每个节点需要有稳定的 ID。对工具调用节点可以使用tool_call_id对文件节点可以使用相对路径对命令节点可以使用命令字符串加执行顺序。节点上还应该保留原始输入输出片段。否则图只是一张空壳点击之后看不到细节。3.2 边类型边表达的是节点之间的关系。Seedeep 要画的不是普通流程图而是过程轨迹图所以边的类型必须能表达因果关系。callassistant 节点调用 tool_call 节点。read/writetool_call 节点读取或写入 file 节点。resulttool_call 节点产生 tool_result 节点。spawnassistant 决策执行 command 节点。failedcommand 或 tool_result 产生 error 节点。retryerror 节点被后续 tool_call 节点再次处理。这些边类型不是随便定的。它们对应你在复盘时最常问的问题“这个文件是谁改的”“这个错误是谁触发的”“改成这样之后又执行了什么”3.3 三种视图时间线、工具依赖、文件变更同一份图数据可以有多种呈现方式。Seedeep 的价值之一是根据排查目的切换视图。时间线视图适合回答“一小时内发生了什么”。节点按时间从上到下排列边的长度代表耗时。工具依赖视图适合回答“这条链路是怎么串起来的”。文件变更视图适合回答“这次会话动了哪些文件”。下面用一个文本结构示意同一个任务在不同视图下的表达。注意这不是 Mermaid而是给画图代码用的 JSON 图结构。{ nodes: [ {id: n0, type: user, label: 修改 README 并运行测试}, {id: n1, type: tool_call, label: Read README.md}, {id: n2, type: file, label: README.md}, {id: n3, type: tool_call, label: Edit README.md}, {id: n4, type: command, label: npm test} ], edges: [ {from: n0, to: n1, rel: call}, {from: n1, to: n2, rel: read}, {from: n0, to: n3, rel: call}, {from: n3, to: n2, rel: write}, {from: n0, to: n4, rel: spawn} ] }实际画的图可能是一组方块加连线也可能是一组纵向节点加曲线。只要阅读者能区分read、write、call和failed就是合格的图。3.4 粒度选择图不是越细越好。工具参数可能包含几十行 JSON直接画在节点上会让图无法阅读。实际使用时要根据查看范围决定粒度。单次会话排错时节点粒度到文件、命令、错误就够了。跨会话分析时可以把单一工具调用聚合为工具类型把单一文件读写聚合为文件热度。Seedeep 这种工具应该支持缩放和过滤而不是把全部细节永远展示在第一屏。4. 最小实现用 Node.js 把 JSONL 会话轨迹渲染成 HTML 图4.1 项目结构与实现思路理解了图模型后可以写一个最小实现。这里用 Node.js 完成不需要数据库不需要后端服务。项目结构如下seedraw/ package.json parse.js render.js sample.jsonl output/ index.html实现思路分为两步先用parse.js把 JSONL 转成节点和边组成的图数据再用render.js把图数据渲染成自包含 SVG 的 HTML 文件。解析和渲染分离方便你替换真实日志格式。4.2 解析模块parse.js负责读取 JSONL、统一字段、构造节点和边。这里省略一部分异常处理重点放在字段映射思路上。const fs require(fs); function buildGraph(filePath) { const lines fs.readFileSync(filePath, utf8).split(\n).filter(Boolean); const nodes []; const edges []; const toolCallMap new Map(); lines.forEach((line, idx) { let event; try { event JSON.parse(line); } catch (err) { return; } const eventId n${idx}; nodes.push({ id: eventId, type: event.type || unknown, label: event.content || event.tool_name || event.type, ts: event.ts || }); if (event.type tool_call) { const callId event.tool_call_id || call_${idx}; toolCallMap.set(callId, eventId); if (event.input event.input.file_path) { const fileId file_${event.input.file_path}_${idx}; nodes.push({ id: fileId, type: file, label: event.input.file_path, ts: event.ts || }); edges.push({ from: eventId, to: fileId, rel: access }); } } if (event.type tool_result event.tool_call_id) { const fromId toolCallMap.get(event.tool_call_id); if (fromId) { const resultId result_${idx}; nodes.push({ id: resultId, type: tool_result, label: String(event.content || ).slice(0, 60), ts: event.ts || }); edges.push({ from: fromId, to: resultId, rel: result }); } } if (event.type command) { const cmdId cmd_${idx}; nodes.push({ id: cmdId, type: command, label: event.command || , ts: event.ts || }); edges.push({ from: eventId, to: cmdId, rel: spawn }); if (event.exit_code ! 0) { const errId err_${idx}; nodes.push({ id: errId, type: error, label: exit ${event.exit_code}, ts: event.ts || }); edges.push({ from: cmdId, to: errId, rel: failed }); } } }); return { nodes, edges }; } module.exports { buildGraph };这段代码的核心是两件事把tool_call_id存成映射让tool_result能找回自己的调用节点把文件、命令、错误作为独立节点挂到对应边上。真实日志字段不同时只需要修改这里的字段名。4.3 坐标计算与 SVG 渲染图数据有了之后需要变成坐标。为了演示采用“纵向时间线”布局每个事件占一行同一行的节点按 ID 顺序排好连线用 SVG 的贝塞尔曲线连接。render.js负责计算坐标并生成 HTML。为了让文件可以独立打开不使用外部 CDN直接生成内联 SVG。const { buildGraph } require(./parse); const fs require(fs); function escapeHtml(str) { return String(str).replace(/[]/g, (c) { return { : amp;, : lt;, : gt;, : quot;, : #39; }[c]; }); } function typeColor(type) { const colors { user: #2563eb, tool_call: #0d9488, tool_result: #475569, file: #7c3aed, command: #b45309, error: #dc2626 }; return colors[type] || #334155; } function renderSvg(graph) { const rowH 80; const height graph.nodes.length * rowH 60; const x 80; const width 900; const nodeIndex new Map(graph.nodes.map((n, i) [n.id, i])); let paths ; graph.edges.forEach((edge) { const i nodeIndex.get(edge.from); const j nodeIndex.get(edge.to); if (i undefined || j undefined) return; const y1 i * rowH 40; const y2 j * rowH 40; const midY (y1 y2) / 2; paths path dM ${x} ${y1} C ${x} ${midY}, ${x} ${midY}, ${x} ${y2} fillnone stroke#94a3b8 stroke-width1.5/; }); let rects ; graph.nodes.forEach((node, idx) { const y idx * rowH 15; const color typeColor(node.type); const label escapeHtml(node.label || node.type); const ts node.ts ? escapeHtml(node.ts) : ; rects rect x60 y${y} width680 height50 rx6 fill${color} opacity0.92/ text x80 y${y 25} fill#fff font-size13${label}/text text x80 y${y 44} fill#e2e8f0 font-size11${node.type} ${ts}/text; }); return svg width${width} height${height}${paths}${rects}/svg; } const graph buildGraph(sample.jsonl); const html !doctype html html head meta charsetutf-8 titleSession Graph/title style body { margin: 40px; background: #0f172a; color: #e2e8f0; font-family: monospace; } .detail { max-width: 1200px; } /style /head body h1Claude Code Session Graph/h1 ${renderSvg(graph)} /body /html; fs.writeFileSync(output/index.html, html); console.log(rendered to output/index.html);代码中的坐标计算不复杂。每个节点按数组下标换算y坐标边的起点和终点用目标节点的y坐标计算。真实场景中还需要处理同一文件中多个节点重叠的问题以及大量边交叉的问题。最小实现先保证能看清过程再逐步优化布局。4.4 运行与验证先用第一节的 sample.jsonl 作为输入然后运行npm init -y node parse.js node render.js如果没有单独运行 parse也可以直接执行node render.js因为 render 内部依赖 buildGraph。验证结果是output/index.html文件生成用浏览器打开后能看到纵向事件节点以及节点之间的曲线连接。正常结果应该满足三条user节点在最上方。tool_call和tool_result成对出现并且有边连接。如果命令失败能看到command节点连接error节点。此时最小闭环已经跑通。下一步就是把sample.jsonl替换成真实会话日志并调整字段映射。5. 三个实际排查场景模型配置、循环修改、529 错误5.1 模型不被当前版本识别很多人在使用 Claude Code 时会配置第三方模型配置后可能看到类似信息deepseek-v4-pro is not a model this version of claude code recognizes。这个报错出现在请求发出之前。也就是说Claude Code 在调用模型前会校验模型名当前版本不认识这个名字就会直接拒绝。在 Seedeep 的图里这个事件会表现为一个没有对应结果节点的tool_call或请求错误节点并且不会产生后续工具调用。检查方式确认settings.json或其他配置里的model字段是否写错。确认配置的模型名是否在当前 Claude Code 版本的模型清单中。查看环境变量里是否有旧的ANTHROPIC_MODEL覆盖了配置。使用claudeCLI 的配置查看命令确认运行时实际生效的模型名。处理建议先使用 Claude Code 支持的默认模型跑通一个最小会话再切换第三方模型。这样能判断问题是出在模型名配置还是出在模型兼容性。不要直接修改日志解析代码去绕过校验模型名不被识别时图上看不到后续动作就要回到配置层排查。5.2 文件被反复修改形成“死循环”Agent 在处理复杂任务时可能反复修改同一个文件每次修改后又发现新问题再改一轮。日志里这种循环很长人眼很难判断它是在收敛还是在原地打转。在 Seedeep 的图里把文件节点合并后你可以快速数出每个文件被读写多少次。如果README.md被连续Read - Edit - Read - Edit五次并且中间没有新信息引入这就是一个明显的循环结构。处理建议在任务指令中限制修改范围例如“不要改动测试文件以外的内容”。为 Claude Code 设置最大轮次或最长执行时间。检查自定义 skill 或 prompt 中是否存在互相矛盾的指令。可视化在这里的作用是快速暴露“同一节点的重复访问”。如果图里出现多个连接到相同文件节点的密集边就应该中断任务检查约束条件。5.3 529 错误与限流定位Claude Code 在调用 API 时可能遇到 529 错误这个错误通常跟服务过载或限流相关。错误本身在日志里很好搜但难的是判断它发生在哪个阶段、影响范围多大。Seedeep 的时间线视图可以处理这个问题。图里每个tool_call到tool_result之间有时间跨度。如果某次调用的跨度异常长或者等待后出现错误节点就说明这次请求可能被限流。检查方式在图中找到error类型节点查看它连接的是哪个命令或工具。比较工具调用时间戳确认错误前是否有大量请求集中发生。查看是否有重试逻辑错误节点是否被后续tool_call处理。处理建议降低单次任务中的工具调用并发增大重试退避时间关注 API 使用量接近配额时优先减少不必要的信息收集步骤。Seedeep 这类工具可以帮助你把“耗时大”和“出现错误”这两个信号可视化比直接翻日志更直观。6. 常见问题与排查清单6.1 拿不到日志或日志不完整运行一次任务后发现没有任何可解析的日志文件。先确认是否开启了日志参数。不同的 Claude Code 版本参数不同用claude --help查看。如果使用插件确认插件日志是否输出到会话目录。如果使用自定义脚本调用底层接口需要自己包裹一层日志。再确认日志写入时机。JSONL 文件往往是流式追加的任务被中断时最后一部分可能没有写入。强制结束进程前先等待文件句柄释放。6.2 事件字段不一致工具结果对不上解析时发现tool_result找不到对应的tool_call。这通常是字段名不一致导致的。Claude Code 不同版本可能把工具调用 ID 命名为id、tool_use_id或message_id。处理办法是写一个兼容层在解析前统一字段名。不要在每个使用tool_call_id的地方都做判断集中处理会更清晰。6.3 图太大太乱一次复杂任务产生几千个节点直接把所有节点渲染在一张图里SVG 会非常大阅读也没有意义。处理办法是按需过滤。先展示漏斗图用户目标 - 文件 - 命令 - 错误。点击某个文件节点再展开这个文件相关的事件。Seedeep 的思路不是把所有信息同时塞进画布而是提供可缩放的图结构。6.4 可复用排查清单现象检查点处理方式没有日志CLI 参数、插件目录、会话数据路径先查看帮助再开启对应日志参数JSONL 解析失败是否多行 JSON、字段是否一致使用逐行 JSON 解析器并统一字段映射tool_result无对应调用ID 字段命名写兼容层字段映射再重建哈希表图节点过多节点粒度过细按文件、命令、错误三层过滤模型名不被识别model 配置、环境变量、版本清单先回退默认模型再检查第三方模型名529 错误时间戳、调用频率降低并发增加重试退避7. 从“画出来”到“用起来”扩展方向和最佳实践7.1 从静态图到实时面板最小实现每次只能读取一个 JSONL 文件生成一个 HTML。实际工程中可以监听日志文件变化实时追加节点和边。这样 Seedeep 就从“事后复盘工具”变成了“执行过程监控面板”。实现时需要注意增量解析。不要在文件变化后重新读整个文件而应该记录上次读取到的字节位置只解析新增部分。这样即使任务持续几小时也能保持低资源占用。7.2 从看单次会话到看项目趋势把多张会话图聚合起来可以看到项目级趋势。比如统计哪些文件被修改次数最多哪些命令最常失败哪些错误导致重试最多。这种聚合对评估代码仓库结构非常有价值。聚合后的数据仍然适合用图表达。文件是节点修改频率是节点大小命令失败是红色高亮工具调用依赖是边。Seedeep 的扩展方向完全可以朝着“项目行为看板”走。7.3 与测试、成本、权限体系联动可视化执行过程之后还可以叠加更多数据。测试结果把测试命令的退出码在图上标注失败路径用实线高亮。成本数据记录每次模型调用的 token 用量节点高度代表成本。权限审计记录文件读写路径检查是否访问了预期之外的目录。这些能力不是画图工具本身必须包含的但图提供了一个统一的观察框架。只要数据能落到事件流上就能进入图模型。7.4 可直接落地的最佳实践一开始就定义统一事件格式不要直接拿原始日志画图。字段统一层是后续所有功能的基础。保存原始日志的完整内容图节点只显示摘要。排查时点开节点看原文比重新跑一次任务更高效。图布局要支持过滤。默认视图只显示用户消息、文件操作、命令、错误其他工具调用折叠。使用自包含 HTML 输出。这样可以直接把图发给同事不需要搭建本地服务。对时间戳做基准校验。事件乱序时先排序再画图否则因果链会错乱。可视化工具不只是给开发者的也可以作为 AI 行为审计材料。生产环境保留会话轨迹和对应图有助于追溯自动化改动来源。Seedeep 这类项目的核心启发是Agent 的行为复杂度已经超过人脑直接跟踪的能力工具的目的不是美化日志而是把非线性执行过程重新组织成人能一眼理解的图结构。如果你也在使用 Claude Code建议先从一个最小会话开始把过程数据导出来再按工具、文件、命令三类做一次时间线图。跑通这个闭环之后接下来做实时面板、聚合统计还是测试联动都会自然很多。
返回列表