graphify extraction-spec 详解:语义提取子代理的 Prompt 契约与确定性节点 ID 规范
本文围绕 extraction-spec.md 展开。该文档是 graphify(把任意代码库连同文档、SQL、配置转成可查询知识图谱的 /graphify 技能)在构建流水线 Step 3 Part B 中下发给每个“语义提取子代理”的完整 prompt 模板:它规定了子代理必须遵守的边级置信度准则、file_type 枚举、语义相似边与超边(hyperedge)规则、节点 ID 的确定性生成格式,以及最终输出的 JSON Schema 与 source_file 逐字(verbatim)路径规则。读完本文,你能理解 graphify 如何用一个“纯文本契约 + 漂移守卫测试”保证 LLM 语义提取结果与 AST 确定性提取在同一个图上无缝合并,并能复现其节点 ID、置信度评分与增量更新不产生重复节点的关键机制。
1. 文档定位:何时加载、由谁消费
extraction-spec.md 开头的加载条件写得很明确:
Load this in Step 3 Part B when the corpus has at least one doc, paper, or image chunk. A pure-code corpus skips Part B and never reads this file.
也就是说,它只在语料中至少存在一个文档、论文或图片分块(chunk)时才被加载;纯代码语料直接跳过 Part B,永远不会读这个文件。这与 skill-claw.md Step 3 的流程一致:Part A(AST 确定性提取)与 Part B(语义子代理)并行调度,而纯代码语料走 Fast path——先写一个空的 .graphify_semantic.json 让 Part C 合并阶段有输入,然后直接进入 Part C。
每个语义子代理都会逐字收到这份 prompt,其中 5 个占位符由调度方替换:
| 占位符 | 含义 |
|---|---|
FILE_LIST |
本分块负责处理的文件路径列表(必须是 verbatim 绝对路径,见第 8 节) |
CHUNK_NUM / TOTAL_CHUNKS |
当前分块编号 / 总分块数 |
DEEP_MODE |
是否以 --mode deep 运行(决定推断边的激进度) |
CHUNK_PATH |
结果 JSON 的落盘绝对路径(完整版 spec 要求,例如 ${PROJECT_ROOT}/graphify-out/.graphify_chunk_0N.json) |
从源码结构看,这份 claw 目录下的拷贝并非手工维护的独立文件。仓库里所有宿主(claude、codex、opencode、kilo、copilot、claw、droid、trae、kiro、pi、windows 等)的 references/extraction-spec.md 都由 skillgen 从单一源头 extraction-spec.md(完整版)与 extraction-spec-compact.md(紧凑版,即本文引用的 claw 版本所对应的源)渲染生成,tools/skillgen/expected/ 下还留有各宿主的黄金输出用于 skillgen --check 校验。这种“一份源、多宿主分发”的机制是后文所有跨宿主行为一致性的前提。
2. 子代理的总则:只输出 JSON,且遵守三级置信度
prompt 的第一段规则就锁死了输出形态:
You are a graphify extraction subagent. Read the files listed and extract a knowledge graph fragment.
Output ONLY valid JSON matching the schema below - no explanation, no markdown fences, no preamble.
随后是三条核心的边级置信度(confidence tier)规则:
- EXTRACTED:源文件中明确存在的关系(import、call、citation);
- INFERRED:合理推断(共享结构、隐含依赖);
- AMBIGUOUS:不确定——标记出来,不要省略(flag it, do not omit)。
这三档不是装饰:它们贯穿到下游。例如 export.py 中为缺失 confidence_score 的边提供了回退默认值 _CONFIDENCE_SCORE_DEFAULTS = {"EXTRACTED": 1.0, "INFERRED": 0.55, "AMBIGUOUS": 0.2}——其中 INFERRED 回退用的是准则集 {0.55, 0.65, 0.75, 0.85, 0.95} 的最弱值 0.55 而非 0.5(源码注释明确指出 0.5 是被 spec 明文禁止的“抛硬币”默认值,参见 #2813),AMBIGUOUS 回退到区间 0.1–0.3 的中点 0.2。也就是说,spec 里的每一句话都能在合并/导出代码里找到对应的实现或护栏。
3. 分文件类型的提取规则
3.1 代码文件:只补 AST 找不到的语义边
Code files: semantic edges AST cannot find. Do not re-extract imports. When adding
callsedges: source is the caller, target is the callee, never reversed; keepcallswithin one language.
三条约束各有原因:
- 不要重提 import——import 边由确定性 AST 提取器(Part A)负责,语义子代理重复输出会造成重复边;
calls方向不可反——source 恒为调用方,target 恒为被调方。完整版 spec 还进一步强调calls边必须留在单一语言内部:“a Python function cannotcallsa JS/TS/Go/Rust/Java symbol … cross-language call edges are phantom artifacts, never emit them”,因为跨语言“调用”在没有运行时桥接时只是幻象边(phantom edge);- 语义边聚焦 AST 表达不了的东西(共享数据、架构模式等)。
3.2 文档/论文文件:六种 file_type 与 rationale 属性
对 doc/paper 文件,spec 要求提取命名概念、实体与引用,并对 file_type 做了硬枚举约束:
file_typeMUST be one of exactly these six values:code,document,paper,image,rationale,concept. Any other value is invalid and will be rejected.
同时规定“决策理由”(WHY decisions were made)必须作为 rationale 属性挂在相关节点上,而不是单独建一个 rationale 节点;概念性节点(思想、原则、机制)用 file_type:"rationale",命名概念用 file_type:"concept"。这个“属性而非节点”的设计避免图谱被碎片化理由节点污染,仓库中 rationale 相关测试 也围绕该约定验证行为。
3.3 图片文件:用视觉理解“它是什么”,而非 OCR
Image files: use vision — understand what the image IS, not just OCR
完整版 spec 给出了更细的分类指导:UI 截图提取布局模式与设计决策;图表提取指标与趋势;推文/帖子提取论断与作者;架构图提取组件与连接关系;手写/白板内容把不确定的读法标为 AMBIGUOUS。这条规则对应 graphify 对图片语料的一等公民处理(graphify/skills/claw/references/ 同目录体系下的视觉处理路径)。
3.4 DEEP_MODE 与语义相似边
- DEEP_MODE(
--mode deep时):激进地输出 INFERRED 边——间接依赖、共享假设、潜在耦合;拿不准的标 AMBIGUOUS 而不是丢弃。skill-claw.md 明确要求在 Step 3 开始前记住--mode deep是否出现,并把DEEP_MODE=true传给 Part B 的每个子代理,“do not lose it”; - 语义相似边:当两个概念解决同一问题或表达同一思想、但没有任何结构性链接(无 import、无 call、无 citation)时,加一条
semantically_similar_to边,标 INFERRED,confidence_score取 0.6–0.95,且仅限“非显而易见的跨文件关联”(Non-obvious cross-file links only)。完整版 spec 还举了三个例子:两个互不调用的用户输入校验函数;代码里的类与论文里描述的同一算法;以不同方式处理同一失败模式的两个错误类型。
3.5 超边(Hyperedges)
Hyperedges: if 3+ nodes share a concept, flow, or pattern not captured by pairwise edges, add a hyperedge to a top-level
hyperedgesarray. Use sparingly. Max 3 per chunk.
超边捕捉“3 个及以上节点共同参与但成对边表达不了”的群体关系,relation 取值 participate_in | implement | form,每分块最多 3 条。值得注意的历史背景(见 CHANGELOG):早期 graphify extract --backend <…> 的原生 LLM prompt(llm.py 中的 _EXTRACTION_SYSTEM)只在输出 Schema 里展示了空 "hyperedges":[]、从未解释什么是超边,导致所有原生后端静默产出 0 条超边,而 skill 路径(本 spec 完整文档化超边)却能产出——两条提取路径的 prompt 发生了漂移。修复后,原生 prompt 携带了相同的“3 个及以上节点共同参与”指令与填充示例,使同一语料下两条路径的超边行为一致。这是“spec 即契约、多路径必须对齐”这一工程原则的又一例证。
3.6 YAML frontmatter 传播
If a file has YAML frontmatter (
--- ... ---), copysource_url,captured_at,author,contributoronto every node from that file.
四个溯源字段会被复制到该文件产出的每个节点上,保证节点级可溯源(provenance)。
4. 离散置信度评分准则(confidence rubric)
spec 中最“反直觉”也最关键的一段,是对 confidence_score 的强制要求:
confidence_score is REQUIRED on every edge — never omit it, never use 0.5 as a default. EXTRACTED = 1.0 always.
| 置信档位 | 允许的取值 | 语义 |
|---|---|---|
| EXTRACTED | 恒为 1.0 |
源文件中显式存在的关系 |
| INFERRED | 只能从五档中选一个:0.95 / 0.85 / 0.75 / 0.65 / 0.55 |
0.95 直接结构证据;0.85 强推断;0.75 合理推断;0.65 弱推断;0.55 推测但可信 |
| AMBIGUOUS | 0.1–0.3 |
不确定,标记而非省略 |
五档 INFERRED 分值的含义(以完整版 spec 的标注为准):
0.95— 直接结构证据(共享数据结构、具名的跨文件引用);0.85— 强推断(清晰的功能对齐,无直接符号链接);0.75— 合理推断(共享问题域 + 相似形态,需要解释);0.65— 弱推断(主题相关,无形态证据);0.55— 推测但可信(仅表层共现)。
为什么用离散档位而不是连续区间?完整版 spec 直接给出了生产观察:“Models follow discrete rubrics better than continuous ranges; the bimodal distribution observed in production (>50% at 0.5, >40% at 0.85+) shows the range guidance is being collapsed to a binary”——即连续区间引导会被模型坍缩成二值分布,而离散准则反而被更忠实执行。如果五档都不适用,应标 AMBIGUOUS 而不是硬选 0.4 或更低。
这个准则同样渗入代码实现:AST 侧各发射点都自带评分,注释中反复引用“the rubric in references/extraction-spec.md”(如 extract.py 中“0.85, not 0.8: the rubric in references/extraction-spec.md …”,以及 extractors/engine.py、extractors/resolution.py 处同样按此准则取值 0.85/0.95)。测试 test_inferred_confidence_rubric.py 则把该准则锁定为回归约束。
5. 节点 ID:确定性、全路径、与 AST 提取器一致
这是整份 spec 里工程密度最高的部分:
Node ID format: lowercase, only
[a-z0-9_], no dots or slashes. Format{stem}_{entity}where stem is the full repo-relative path with the extension dropped, every segment joined with_(each lowercased with non-alphanumeric chars replaced by_) and entity is the symbol name similarly normalized. Use every directory level, not just the immediate parent.src/auth/session.py+ValidateToken→src_auth_session_validatetoken. Top-level files use just the filename stem. This must match the AST extractor's ID. Never append chunk or sequence suffixes — IDs must be deterministic from the label alone.
拆解成可执行的生成规则:
- 字符集:仅小写字母、数字、下划线;
- stem:仓库相对路径去掉扩展名后,保留每一级目录,段与段之间用
_连接;每段小写化、非字母数字字符替换为_; - entity:符号名做同样的归一化;
- 根级文件只用文件名 stem(
setup.py→setup,符号my_func→setup_my_func); - 禁止追加任何 chunk 序号或序列后缀(如
_c1、_chunk2)——同一实体无论被哪个分块处理,必须产生同一个 ID; - 必须与 AST 提取器生成的 ID 完全一致,否则同一个符号会被拆成互不相连的“幽灵重复节点”(ghost-duplicate nodes)。
为什么强调“每一级目录”?extractors/base.py 中 _file_stem 的 docstring 解释了动机(对应 issue #1504):若 stem 只取“父目录 + 文件名”,则 docs/v1/api/README.md 与 docs/v2/api/README.md 都会塌缩成 api_readme,同名文件在不同目录相互碰撞为“last-writer-wins”的单一节点并静默丢图。全路径 stem 使二者分别得到 docs_v1_api_readme 与 docs_v2_api_readme。CHANGELOG 记录了这次破坏性变更:“Breaking — node IDs now include the full repo-relative path (#1504, #1509)”,并说明 AST 提取器、LLM system prompt、extraction-spec 与两处手工复制的 stem helper 已对齐到同一条规则(修复 #1509 的 AST↔LLM 分歧)。
ID 的最终生成由 ids.py 完成:make_id(*parts) 先把各 part 拼接,再交给 normalize_id——后者对输入迭代执行 NFKC(casefold(s)) 直到不动点(带 6 次硬上限),然后 [^\w]+ → _、折叠连续下划线并去首尾(源码注释说明这是为组合附加符序列如希腊 ypogegrammeni 做的收敛处理,见 #2614)。语义子代理产出的 ID 因此能被确定性地与 AST 侧 ID 对齐或重映射。
5.1 漂移守卫:spec 里的每个例子都是可执行测试
正因为 spec 是“LLM 的 ground truth”,它一旦与代码漂移就会制造幽灵节点。test_extraction_spec_ids.py 是一个专门的守卫测试:
- 用正则
`path` + `entity` → `id`(箭头为 U+2192)从所有发行中的 spec 文件里解析出每个 ID 示例(扫描graphify/skills/**与tools/skillgen/fragments/**下所有extraction-spec.md,排除build/与expected/); - 对每个示例调用生产代码本身(
extract._file_stem+_make_id)重放,断言结果与 spec 写死一致; - 若一个都解析不到(文件搬走或示例格式变了)就 loudly fail,防止守卫本身空转;
- 另有一条
test_cautionary_wrong_forms_are_actually_wrong,把 spec 中警告的反例(只用文件名session、只用直接父目录auth)也锁定到代码上,确保警告不会过期。
也就是说,claw 这份 spec 里的例子 src/auth/session.py + ValidateToken → src_auth_session_validatetoken 不只是文档示例,而是 CI 会逐条重放的断言。
6. 输出 JSON Schema 逐字段说明
spec 要求子代理输出恰好如下结构(无其他文本):
{
"nodes": [{
"id": "auth_session_validatetoken",
"label": "Human Readable Name",
"file_type": "code|document|paper|image|rationale|concept",
"source_file": "<FILE_LIST path verbatim>",
"source_location": null,
"source_url": null,
"captured_at": null,
"author": null,
"contributor": null
}],
"edges": [{
"source": "node_id",
"target": "node_id",
"relation": "calls|implements|references|cites|conceptually_related_to|shares_data_with|semantically_similar_to|rationale_for",
"confidence": "EXTRACTED|INFERRED|AMBIGUOUS",
"confidence_score": 1.0,
"source_file": "<FILE_LIST path verbatim>",
"source_location": null,
"weight": 1.0
}],
"hyperedges": [{
"id": "snake_case_id",
"label": "Human Readable Label",
"nodes": ["node_id1", "node_id2", "node_id3"],
"relation": "participate_in|implement|form",
"confidence": "EXTRACTED|INFERRED",
"confidence_score": 0.75,
"source_file": "<FILE_LIST path verbatim>"
}],
"input_tokens": 0,
"output_tokens": 0
}
要点:
- nodes:
id按第 5 节规则;file_type是第 3.2 节的六值硬枚举;溯源四元组(source_url/captured_at/author/contributor)默认 null,仅在 frontmatter 传播规则触发时填充; - edges:
relation八值枚举中,calls、cites等显式关系应标 EXTRACTED(1.0),conceptually_related_to、shares_data_with、semantically_similar_to、rationale_for通常是 INFERRED 并套用第 4 节五档准则;weight默认 1.0; - hyperedges:仅 EXTRACTED/INFERRED 两档(示例中 0.75 落在 INFERRED 准则内),
nodes为成员节点 ID 列表,最多 3 条/分块; - input_tokens / output_tokens:供上层统计成本(skill 流水线会汇总到
cost.json一类产物)。
下游对这份 JSON 有解析护栏:llm.py 中的解析器会强制 nodes/edges/hyperedges 为 dict 列表,并把超边成员引用强制转为可哈希标量 ID(#2486,防止模型把成员写成对象导致后续去重崩溃)。
7. source_file 逐字规则:全量构建与增量更新共用同一基准
spec 的最后一条规则看似琐碎,实则是增量正确性的基石:
source_file RULE: set source_file to the FILE_LIST path for that file VERBATIM (absolute, no shortening to basename, no re-relativizing, no separator change). Keeps full build and --update on one base so build_merge's replace matches instead of duplicating.
即:节点、边、超边的 source_file 必须与 FILE_LIST 中该文件的路径逐字符一致——不得缩成 basename、不得重新相对化、不得改分隔符(路径的正则化与对构建根的相对化由下游引擎统一做)。其直接收益是“全量构建”与 --update 增量重建落在同一节点键基准上:当某文件被重新提取时,build_merge 能替换(replace-on-re-extract)旧节点而不是追加出一个重复节点。CHANGELOG 中 #1366 的修复正是围绕这条规则固化下来的:“the extraction-spec source_file is pinned to the verbatim path, so the full build and incremental updates never drift on node-key base”;更早的 #1344/#1361 还处理了 --update 时误删已变更文件新节点的回归(更新 runbook 现在只修剪真正被删除的文件,变更文件交给 build_merge 的替换机制对账)。
8. 工程闭环:这条 prompt 还参与了缓存命名空间
从源码结构看,这份 spec 不只是运行时下发的文本,它还是语义缓存的命名空间指纹来源之一。cache.py 与 cli.py 中,语义缓存条目按“提取 prompt 的指纹”归入 cache/semantic/p{fingerprint}/(镜像 AST 缓存的 v{version}/ 布局):skill-claw.md 要求调度方把 spec 的绝对路径(SPEC_PATH)在 Step B0 与 B3 之间原样传递——graphify 升级若改变了 prompt,旧 prompt 产出的缓存条目就会被重新提取而不是被回放;prompt 未变则条目保留(#1939)。这解释了为什么 spec 的逐字加载(而非意译)是硬要求:prompt 文本即缓存键的一部分。
9. 小结:一份 spec,四道防线
把 extraction-spec.md 放回 graphify 的整体架构里看,它同时承担四重职责,每一重都有对应实现或测试兜底:
- LLM 输出契约——JSON Schema + 六值
file_type+ 离散置信度准则,由 export.py 的回退默认值与解析护栏兜底; - AST↔LLM 对齐契约——节点 ID 全路径规则必须与 _file_stem/make_id 一致,由 test_extraction_spec_ids.py 逐条重放 spec 示例防止漂移;
- 增量一致性契约——
source_fileverbatim 规则保证全量与--update共用节点键基准,build_merge替换而非复制(#1366); - 缓存一致性契约——spec 文本指纹参与语义缓存命名空间(
cache/semantic/p{fingerprint}/,#1939),prompt 变更自动使旧缓存失效。
如果你想在自己的语料上验证这些行为,可以直接查看 worked/ 目录下 httpx、mixed-corpus 等已构建好的样例(含 graph.json、GRAPH_REPORT.md 与 review.md),对照其中节点 ID、confidence_score 分布与超边结构,检验本文描述的规则在真实产物上的体现。
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 StartedRust0624
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