graphify 语义提取的手工分发模式:无子代理 API 主机(VSCode)上的 Step B2 手动粘贴工作流
graphify 将代码、文档、论文、图片乃至视频统一抽取为可查询的知识图谱,其中「语义抽取(semantic extraction)」阶段(Step 3 Part B)负责用 LLM 提炼 AST 结构性抽取无法覆盖的关系。本文聚焦该阶段在没有并行 Agent/Task API 的主机(典型如 VSCode 宿主)上的特殊工作流——manual-paste 手工分发模式:不依赖任何自动化工具,通过手动开启子会话、粘贴子代理回传的 JSON、并按约定落盘到分块文件来驱动整条抽取流水线。读完本文,你将掌握该模式的完整运作机制、精确的 Shell 落盘命令、分块文件命名与装配约定,以及它与其余六种自动分发片段(agent-tool / task-tool / codex / opencode 等)在设计上的取舍关系。
一、背景:语义抽取为什么需要「分发(Dispatch)」
graphify 的抽取管线分为两条腿(见 fragments/core/core.md 与渲染产物 graphify/skill-vscode.md):
- Part A — 结构性抽取(AST):对代码文件做确定性、零成本的语法树解析,输出到
graphify-out/.graphify_ast.json,全程不消耗 LLM token。 - Part B — 语义抽取(LLM):只处理
document、paper、image三类非代码文件(代码已由 AST 覆盖,见 graphify/skill-vscode.md),把每个文件内的命名实体、概念、引用与跨文件关系提炼成节点/边/超边。
Part B 的成本与并发度直接取决于如何把海量文件切块并分发给 LLM 子代理。graphify 的 skills 生成器(skillgen)将这一环节做成可插拔的 @@DISPATCH@@ 槽位,每种宿主(Claude Code、Codex、OpenCode、Aider、VSCode……)在 tools/skillgen/platforms.toml 中声明自己采用哪种「分发策略」:
| 分发片段 | 目标平台 | 子代理并发方式 |
|---|---|---|
agent-tool-disk.md |
claude、kilo、copilot、kiro、pi | 同一消息内多次调用 Agent 工具实现并行,子代理写盘 |
agent-tool-disk-powershell.md |
windows | 同上,但落盘命令翻译为 PowerShell 语法 |
task-tool-disk.md / -trae |
droid、amp、agents、trae | 基于 Task 工具的磁盘收集式并行 |
codex-agenttask.md |
codex | spawn_agent + wait_agent + close_agent,内存汇总 |
opencode-mention.md |
opencode | @agent 并行 + .graphify_semantic_new.json 汇总 |
manual-paste.md(本文) |
vscode | 无自动化 API,人工开子会话、粘贴回传 JSON |
其中 platform.vscode 显式声明 dispatch = "manual-paste",即本片段的唯一消费方。它由 gen.py 在渲染时读取并替换进核心模板的 @@DISPATCH@@ 槽位,最终产出的完整文档是 graphify/skill-vscode.md 中 Step B2 那一整节。
二、为什么需要「手动粘贴」:无并行 Agent API 主机的降级方案
manual-paste 片段的定位在标题与引言中写得很直白(原文):
No automated subagent tool: this host has no parallel Agent/Task API, so extraction is driven by hand. Dispatch a subagent per chunk however the host allows (a fresh conversation, a parallel pane), then paste each response back.
也就是说,当宿主不具备「同一条消息内发起多个并行子代理」的能力时,skillgen 不会让抽取停下来,而是退化为由宿主自身作为编排者:按块(chunk)开启独立对话或并行面板充当子代理,再把每个子代理返回的 JSON 内容手工粘贴回主会话,并写入对应的分块文件。这里存在一个隐含的分工原则(见核心模板对宿主的说明):
- 纯代码语料库直接走「快路径」(fast path):写空语义文件后跳到 Part C,从不读取抽取规范文件;
- 只有包含文档/论文/图片的分块才需要在 Step B2 加载并分发子代理;
- 即便是在无子代理能力的主机上,也不应向用户索要 API key 或阻塞等待——
GEMINI_API_KEY/GOOGLE_API_KEY存在则由 graphify/llm.py 的extract_corpus_parallel承担语义抽取;否则由宿主会话亲自作为 LLM 完成任务。
三、Step B2 分步执行:分块、分发、粘贴、落盘
3.1 上游状态:先过缓存、再切分块
Step B2 只在两种前置条件满足后才会执行,构成典型的四步流水线:
- Step B0(缓存检查):调用 graphify/cache.py 的
check_semantic_cache对比文件与抽取提示词指纹,命中结果写入graphify-out/.graphify_cached.json,未命中的文件清单写入graphify-out/.graphify_uncached.txt。只有清单中的文件才需要被分发(见 graphify/skill-vscode.md)。 - Step B1(切块):读取
.graphify_uncached.txt,按 每块 20–25 个文件 切分;每张图片独占一个块(视觉理解需要独立上下文);尽量把同目录文件聚到同一块,以便子代理更容易提取跨文件关系。 - Step B2(分发+粘贴,即本文核心):为每一块分发一个子代理,回收其 JSON 响应并写入该块文件。
- Step B3(收集合并):检查分块文件是否在磁盘上存在(成功信号),合并进
.graphify_semantic_new.json,经save_semantic_cache写缓存后与缓存结果合成.graphify_semantic.json(见 graphify/skill-vscode.md)。
3.2 手工落盘命令(原文核心代码段)
每当一个子代理的 JSON 回传后,需要把它保存为该块的分块文件,供 Step B3 的 glob 收集。原文给出的命令是:
# After pasting a subagent's JSON for chunk N, save it (replace N and PASTED_JSON):
PROJECT_ROOT=$(pwd) # cwd — where Part C globs graphify-out/ (NOT .graphify_root/scan dir, #1392)
cat > "${PROJECT_ROOT}/graphify-out/.graphify_chunk_0N.json" <<'CHUNK_JSON'
PASTED_JSON
CHUNK_JSON
这段命令有几个必须遵守的细节:
PROJECT_ROOT=$(pwd):以当前工作目录为准。其目的是让 Part C 后续的 glob 能在正确的graphify-out/下取到文件,而不是.graphify_root/scan扫描目录(原文以 issue#1392标注这一历史教训——扫描根目录与输出目录曾经被混淆,导致收集器找不到分块文件)。- heredoc 使用带引号的定界符
<<'CHUNK_JSON':单引号关闭了 Shell 对内容的一切变量展开与命令替换,保证子代理返回的 JSON 原样写入,避免其中包含的$、反引号等字符被意外解释。 - 定界符
CHUNK_JSON本身需保持唯一且不与正文冲突;PASTED_JSON 只是占位符,实际替换为粘贴的完整 JSON。 - 文件名采用零填充序号
0N:即N=1写.graphify_chunk_01.json,以此类推。这是因为 Step B3 的收集脚本使用sorted(glob.glob('graphify-out/.graphify_chunk_*.json'))按字典序排序(见 graphify/skill-vscode.md),若文件名不带零填充,chunk_10.json会排在chunk_2.json之前导致序号错乱。
3.3 逐个块重复,直至全部落盘
「Repeat for every chunk」是硬性要求:每个块的 JSON 必须在 Step B3 运行之前落入各自独立的 graphify-out/.graphify_chunk_NN.json。Step B3 以「文件存在于磁盘」作为成功判定信号,因此遗漏任何一个块都会造成该块抽取结果被静默丢弃。
3.4 子代理提示词模板:只在此处、按需加载
分发子代理时,宿主并不是自由发挥,而是把精确的抽取提示词逐字传给每个子代理。提示词出处为 references/extraction-spec.md(VSCode 平台装配到 graphify/skills/vscode/references/extraction-spec.md,模板源头在 tools/skillgen/fragments/references/shared/extraction-spec.md)。提示词中需要被替换的占位符有四个:
FILE_LIST— 本块负责的文件清单;CHUNK_NUM— 当前块序号;TOTAL_CHUNKS— 总块数;DEEP_MODE— 若原始调用携带--mode deep,则必须向每个子代理传DEEP_MODE=true,让其对 INFERRED 边更激进(见 tools/skillgen/fragments/core/core.md)。
规范文件的核心约束包括:只输出符合 schema 的 JSON(无解释、无围栏);边必须携带 EXTRACTED / INFERRED / AMBIGUOUS 证据标记与 confidence_score;calls 边必须同语言内且源为调用方;file_type 只能是 code/document/paper/image/rationale/concept 六值之一;超边仅当 3 个以上节点共同参与同一概念时才使用且每块至多 3 条;图片用视觉理解「是什么」而非仅 OCR;语义相似但无结构链接的概念补 semantically_similar_to 边。
特别值得注意的是加载条件:仅当至少一个分块包含文档、论文或图片时才读取该规范文件;纯代码语料库在进入 Part B 之前就已跳过整个语义抽取,永远不会加载它。这既节省 token,也避免子代理在无事可做时收到无关指令。
四、与其他分发模式的差异:manual-paste 的独有取舍
把 manual-paste 片段与同目录其他分发片段对照,可以清晰看出它在设计上的三个独特点:
- 无并行原语、人工串并行:
agent-tool-disk强调「同一消息内多次调用 Agent 工具,否则就退化为顺序执行并失去意义」;codex-agenttask使用spawn_agent/wait_agent/close_agent并要求multi_agent = true配置。而 manual-paste 明确放弃任何 API 依赖,改用「新开会话 / 并行面板」的人工方式——这是对无 API 主机在功能完整性上的兜底。 - 磁盘是唯一事实源:Step B3 通过检查
.graphify_chunk_NN.json是否存在来判断子代理是否成功,因此「粘贴 + 落盘」这一步绝不能省。Codex 变体则相反——在内存中累计结果后统一写.graphify_semantic_new.json,磁盘成功检查不适用(见 codex-agenttask.md)。manual-paste 位于「磁盘收集」阵营,与 agent-tool-disk / task-tool-disk 共享同一套 B3 检查协议。 - 提示词注入方式相同、槽位不同:所有分块文件的 schema、节点 ID 规则、置信度量规、超边与视觉规则都由抽取规范统一约束,manual-paste 与自动模式的区别仅在「谁、以什么方式把提示词交给子代理并取回结果」。
五、装配机制:这一片段如何变成可用的 SKILL.md
了解片段如何进入最终产物,有助于排查分发故障。装配链如下:
- tools/skillgen/platforms.toml 定义
[platform.vscode]:bucket = "split"、core = "core"、skill_dst = "graphify/skill-vscode.md"、refs_dst = "graphify/skills/vscode/references"、dispatch = "manual-paste"、extraction = "verbose"、shell = "posix"。 - tools/skillgen/gen.py 的
_render_core按平台声明读取fragments/dispatch/manual-paste.md,将其文本替换进核心模板的@@DISPATCH@@槽位;若替换后仍残留未填充的@@xxx@@槽位会直接报错,防止产出残缺技能。 - 若平台 shell 为
powershell(如 windows),则整段 body 还会经 PowerShell 翻译器处理——manual-paste 本身只面向 POSIX(VSCode 宿主),因此保留了catheredoc 原生写法。 - 最终渲染产物就是 graphify/skill-vscode.md 第 253–273 行的 Step B2 完整正文。仓库中 tools/skillgen/expected/graphify__skill-vscode.md 保存了用于防漂移的期望快照。
这意味着:分发的行为完全由片段决定,而片段又由 platform 声明选择。当你在 VSCode 宿主上运行 graphify 并观察到语义抽取以「人工分发」方式进行时,根因就在 platforms.toml 的 dispatch = "manual-paste" 这一行。
六、实操清单与常见问题
把整条手动分发工作流压缩为可直接照做的清单:
- 确认语料含非代码文件:检测结果中
document/paper/image数量为零时走快路径,直接写空语义文件进入 Part C,无需执行本文任何步骤。 - 先缓存后分发:运行 Step B0 得到
.graphify_uncached.txt,只对清单文件分发。 - 切块:每块 20–25 文件、图片独占一块、同目录优先聚拢。
- 逐块分发:为每块开启新会话/并行面板,逐字传入抽取提示词,替换
FILE_LIST / CHUNK_NUM / TOTAL_CHUNKS / DEEP_MODE。 - 回贴落盘:将子代理返回的 JSON 用
cat > ... <<'CHUNK_JSON'heredoc 写入graphify-out/.graphify_chunk_NN.json,序号需零填充,PROJECT_ROOT取当前工作目录。 - 校验后合并:确认每个分块文件都真实存在且含合法
nodes/edges,再运行 Step B3 的合并脚本;失败或缺失的块应告警跳过而非整体中止,但超过一半块失败则应停止并提示用户。
容易踩的坑集中在三点:heredoc 定界符漏加引号导致 JSON 被变量展开污染;块文件名未按 0N 零填充造成 Step B3 字典序排序错乱;以及把 PROJECT_ROOT 误设为 .graphify_root/scan 而非输出根目录(#1392),使 Part C 的 glob 扑空。
结语
manual-paste 是 graphify skills 体系中对「宿主能力下限」的优雅处理:即便没有并行子代理 API,语义抽取仍能通过「人工开启子会话 + 粘贴回传 + 规范化落盘」完整运行,并与其他分发模式共享同一套缓存、合并与置信度协议。对技能使用者而言,理解这段 Step B2 的落盘约定与命名规则,能显著减少在 VSCode 等主机上遇到的「分块文件缺失」「合并结果为空」等疑难问题;对 skillgen 的二次开发者而言,它也是一份如何为一个无 Agent API 平台编写专属分发片段的参考样例。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0627
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00