graphify extraction-spec 子智能体抽取规范详解:用确定性 Prompt 把文档、论文与图片沉淀为可查询知识图谱
这篇技术指南以 graphify 随各 AI 编程助手 Skill 分发的
references/extraction-spec.md(其生成产物对应仓库中的 tools/skillgen/expected/graphify__skills__opencode__references__extraction-spec.md)为讲解对象。全文围绕该规范展开:它在 graphify 语义抽取流水线(Step 3 Part B)中的角色、文件类型六值枚举、三种证据置信度、精确置信度评分刻度、确定性节点 ID 规则、超边与相似边契约,以及写回磁盘的路径纪律。读完你将能复现 graphify 训练语义子智能体的完整 Prompt 设计,并理解每个约束在extract、build、semantic_cleanup等引擎模块中的落地点与防护动机。
一、这份 reference 是什么:语义抽取子智能体的唯一作业指导书
graphify 把一个代码库(连同其文档、SQL Schema、配置与 PDF)变成可查询知识图谱的流程分三步:Part A 由本地确定性 AST 解析器抽取代码结构,Part B 由"语义子智能体"(semantic subagent)抽取 AST 看不见的语义层(文档概念、论文观点、图片内容、跨文件设计意图),Part C 合并两个来源。
extraction-spec.md 正是 Part B 子智能体收到的逐字 Prompt。规范开门见山地写明它的装载条件:
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.
即:只有当语料中存在文档(doc)、论文(paper)或图片(image)分块时才加载本文件;纯代码语料直接跳过 Part B,根本不读它——代码结构由 AST 负责,无需语义子智能体。每个语义子智能体收到的 Prompt 在原文基础上一字不改,仅替换五个占位符:
| 占位符 | 含义 |
|---|---|
FILE_LIST |
本分块负责读取的文件列表 |
CHUNK_NUM / TOTAL_CHUNKS |
当前分块序号 / 总分块数 |
DEEP_MODE |
是否处于 --mode deep 激进推理模式 |
CHUNK_PATH |
抽取结果 JSON 必须写到的绝对路径 |
这一编排逻辑在各平台 Skill 中保持一致:例如 graphify/skill-opencode.md 的 Part B 明确要求"把该 Prompt 逐字传给每个子智能体,替换 FILE_LIST、CHUNK_NUM、TOTAL_CHUNKS、DEEP_MODE"。仓库中每个平台目录都镜像同一份 reference 文件(如 graphify/skills/opencode/references/extraction-spec.md),而 tools/skillgen/expected/ 下则沉淀了 skillgen 工具为各平台生成的等价产物,方便直接校对。
二、输出总契约:Only JSON,无 Markdown、无前言、无解释
Prompt 的第一条铁律是输出纪律:
Output ONLY valid JSON matching the schema below — no explanation, no markdown fences, no preamble.
子智能体的全部产出是一份顶层包含 nodes、edges、hyperedges、input_tokens、output_tokens 五个键的 JSON 片段,随后由编排方合并进整图。为什么要有这条纪律?因为 graphify 的语义缓存、增量更新与 build_merge 的"重抽取即替换"机制都依赖机器可解析、字段可对齐的分块输出——任何额外文字都会污染解析器。语义分块 JSON 在落盘前还会经过 graphify/semantic_cleanup.py 的 validate_semantic_fragment() 校验,该函数对不可信的智能体输出设有硬边界:默认单片段 ≤ 25 MB、节点 ≤ 10,000、边 ≤ 100,000、超边 ≤ 10,000、超边成员 ≤ 256、语义 ID ≤ 256 字符,防止失控或恶意响应耗尽内存或借节点 ID 逃逸 graphify-out 目录。
三、节点契约:六种 file_type 与 "rationale 不得喧宾夺主"
规范要求每个节点携带 file_type,且取值必须严格落在以下六个枚举值之一,任何其他值都会被拒绝:
| file_type | 适用对象 |
|---|---|
code |
代码符号(函数、类、变量等) |
document |
文档中的命名实体/概念 |
paper |
论文中的概念与引用 |
image |
图片内容所表达的实体 |
rationale |
概念类节点:思想、原则、机制、设计模式 |
concept |
其余通用概念 |
注意 rationale 的用法非常微妙,规范给出了两条并行指令防止滥用:
- "为什么"要存成属性,而不是节点:针对设计取舍、决策理由(WHY)这类说明性文字,"store as a
rationaleattribute on the relevant concept node — do NOT create a separate rationale node or fragment node"。即理由文本应附着在它解释的概念节点上,绝不能为了存一段理由而单独建一个"理由节点"。 - 只有当某物本身是可命名的实体/概念时才建节点;若这个可命名概念属于 idea、principle、mechanism、design pattern 这类"思想型"对象,就给它打
file_type:"rationale"。
引擎侧同样内置了对应防护。语义清理器 graphify/semantic_cleanup.py 把"句子化"的 rationale 节点转化为关联节点上的属性,并定义 VALID_SEMANTIC_FILE_TYPES = frozenset({"code", "document", "paper", "image", "rationale", "concept"}),与 Prompt 中的六值完全一一对应;而 AST 抽取器本身在处理 Python/JS/TS 的 docstring 与 // WHY: 注释时,也以 file_type:"rationale" + rationale_for 关系建模(见 graphify/extract.py 的 Python 通道与 graphify/extract.py 的 JS/TS 通道),两条路径的语义最终殊途同归。
此外,graphify/build.py 还维护了一张 _FILE_TYPE_SYNONYMS 容错表,把子智能体常见的漂移写法归位(markdown→document、tool/library→code、pattern/principle/constraint→concept 等),无法归位的兜底为 concept——这解释了规范中"Any other value is invalid and will be rejected"为何能真正落地:Prompt 端从严要求,构建端对已知别名做了一次有界的语义纠偏。
四、边契约:三种证据置信度与 "calls 永远单向、永不跨语言"
每条边都要声明证据性质 confidence,取三个层级之一:
| 层级 | 定义 | 典型信号 |
|---|---|---|
EXTRACTED |
源文本中显式存在的关系 | import、调用、引用、"see §3.2"这类字面线索 |
INFERRED |
合理推断的关系 | 共享数据结构、隐含依赖 |
AMBIGUOUS |
不确定——标注待复核,不许省略 | 解读存疑的关联 |
规范特别强调 AMBIGUOUS 的意义是"flag for review, do not omit":宁可把存疑关系标记出来送审,也不要悄悄丢掉,因为下游 review/analyze 流程正是靠 AMBIGUOUS 边、弱 INFERRED 关系等信号生成架构复核问题(见 graphify/analyze.py 对"无 AMBIGUOUS 边、无 INFERRED 关系"语料的提示逻辑)。
在边类型上,schema 允许八种关系:calls | implements | references | cites | conceptually_related_to | shares_data_with | semantically_similar_to | rationale_for。其中有两条硬约束:
- 方向不可逆:
calls边的 source 必须是调用方(正在发起调用的函数/类),target 必须是 callee,绝不能颠倒。 - 永不跨语言:
calls边必须停留在同一种语言内——Python 函数不能calls一个 JS/TS/Go/Rust/Java 符号,反之亦然,因为"跨语言调用边"是幽灵产物(phantom artifacts)。
第二条约束在构建端有实质性防护:graphify/build.py 定义了 _EDGE_LANG_FAMILY 语言族表,把 Python、JS/TS、JVM 系、C/C++/ObjC 系等按真实互操作边界分组,在边循环里丢弃"Python import time 误绑定到 time.ts"或"跨语言 INFERRED calls"这类幽灵边;对应回归面见 tests/test_cross_language_call_resolution.py。
代码文件的抽取重点是 AST 找不到的语义边(调用关系、共享数据、架构模式),规范同时叮嘱:"Do not re-extract imports — AST already has those."——不要重复抽取 AST 已有的 import 结构。
五、多模态输入:图片要用"视觉理解"而非单纯 OCR
当语料含图片时,规范为子智能体定义了按图种区分的理解任务,核心是"understand what the image IS - do not just OCR":
| 图片种类 | 应抽取的内容 |
|---|---|
| UI 截图 | 布局模式、设计决策、关键元素、用途 |
| 图表(Chart) | 指标、趋势/洞见、数据来源 |
| 推文/帖子 | 将"主张"抽为节点,连同作者与涉及的概念 |
| 示意图(Diagram) | 组件与连线关系 |
| 科研配图 | 它证明了什么、方法、结果 |
| 手写/白板 | 想法与箭头;不确定的识读标为 AMBIGUOUS |
这意味着图片节点不是"图"本身,而是图里承载的概念与实体——它们与文档概念一样进入统一的知识图谱节点池,并参与后续的边与超边合并。
六、DEEP_MODE:--mode deep 下的激进推理
当用户以 graphify extract --mode deep 运行时,DEEP_MODE 占位符被置真,规范要求子智能体在 INFERRED 边上更激进:补出间接依赖、共享假设、潜在耦合;拿不准的一律标 AMBIGUOUS 而非省略。
在 CLI 层,graphify/cli.py 的 extract 用法串包含 [--mode deep],_VALID_MODES = {"deep"}(graphify/cli.py),开启后打印 "deep mode enabled: richer semantic extraction"(graphify/cli.py)。--mode deep 还会切换语义缓存的命名空间:普通语义抽取命中 cache/semantic/,deep 抽取命中独立的 cache/semantic-deep/(graphify/cache.py 相关注释),两者互不污染。正因如此,在热缓存的未变更代码库上跑 --mode deep 若只按 manifest 变更门控会静默空转(一次派发零文件),graphify/cli.py 专门把语义通道扩展到全部存活的 doc/paper/image 文件,由 deep 专属缓存决定命中/未命中——这是把"deep 更激进"真正落到运行语义的关键补丁。
七、semantically_similar_to:跨结构的语义相似边
规范为"两个概念解决问题相同、思想相同,却没有任何结构链接(无 import、无调用、无引用)"的场景引入了 semantically_similar_to 边,一律标 INFERRED,并配 0.6–0.95 的置信分。给出的合法样例:
- 两个都校验用户输入、却从不互相调用的函数;
- 代码里的某个类与论文里描述同一算法的概念;
- 两个以不同方式处理同一失败模式的错误类型。
同时强调节制:"Only add these when the similarity is genuinely non-obvious and cross-cutting. Do not add them for trivially similar things."——相似必须是非显然、跨切面的,平凡相似禁止添加。这条边正是 LLM 相比纯结构分析能带来的独特增量:把"长得很像但从不互见"的知识连成图谱,供后续检索与影响分析使用。
八、Hyperedges:三人成组的语义超边
当 3 个或更多节点显然共同参与某个共享概念、流程或模式,而两两成对的边不足以表达时,规范要求把它们收进顶层 hyperedges 数组。官方示例:
- 实现同一协议/接口的全部类;
- 认证流程中的所有函数(即使它们并不两两互调);
- 论文某节构成一个完整思想的所有概念。
超边关系的合法取值为 participate_in | implement | form。规范同样强调克制:"Use sparingly — only when the group relationship adds information beyond the pairwise edges",且每分块最多 3 条超边。超边机制在构建端有专门的合并与剪枝处理,对应回归测试见 tests/test_build_merge_hyperedges_and_prune.py。
九、confidence_score 的精确刻度:禁用 0.5,杜绝二值塌缩
这是全规范中最讲究的一处设计。规则要点:
- 每条边都必须有
confidence_score,不得省略,永远不得拿 0.5 当默认值; EXTRACTED边恒定1.0;INFERRED边从离散刻度中精确取值:0.95直接结构证据(共享数据结构、具名跨文件引用)0.85强推断(功能取向清晰、无直接符号链接)0.75合理推断(共享问题域 + 形状相似,需解读)0.65弱推断(主题相关、无形状证据)0.55猜测性但合理(仅表层共现)
- 若以上没有合适的值,宁可标 AMBIGUOUS 也不要给 0.4 或更低;
AMBIGUOUS边取0.1–0.3。
规范用生产观测解释了为什么放弃连续区间而采用离散刻度:
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.
即连续刻度在真实模型上会塌缩成二值(半数堆在 0.5、四成堆在 0.85+),离散评分刻度更能引导模型按档位对齐。这与引擎侧完全自洽:graphify/export.py 的注释明确记载 INFERRED 的默认值曾是 0.5、后来按 references/extraction-spec.md 删除;graphify/extract.py 与 graphify/extractors/engine.py 在生成推断边时也注释了"0.85 = strong inference on the extraction-spec rubric"。对应测试面见 tests/test_confidence.py。
十、节点 ID 生成规范:确定性优先,杜绝孤儿幻影节点
节点 ID 是全图一致性的命门。规范规定格式为:
{stem}_{entity}
其中 entity 是符号名;stem 是去掉扩展名后的完整仓库相对路径,路径每个段都小写、非字母数字字符替换为下划线后以 _ 连接——必须保留每一层目录,而不是只留直接父目录,否则不同目录下的同名文件会互相冲突。官方示例:
| 文件 + 符号 | 正确 ID |
|---|---|
src/auth/session.py + ValidateToken |
src_auth_session_validatetoken |
lib/utils/helpers.py + parse_url |
lib_utils_helpers_parse_url |
tests/test_foo.py + _helper |
tests_test_foo_helper |
docs/v1/api/README.md + getUser |
docs_v1_api_readme_getuser |
顶层文件 setup.py + my_func |
setup_my_func |
两条最容易踩的坑被单独加粗:
- ID 必须与 AST 抽取器生成的 ID 完全一致。只写文件名(如
session_validatetoken)或只写直接父目录(如auth_session_validatetoken)都会制造"孤儿幻影重复节点"(orphan ghost-duplicate nodes)。如果旧格式工程需要重抽,用户应执行graphify extract --force干净重建。 - 绝对禁止追加任何分块号/序号后缀(不得有
_c1、_c2、_chunk2之类)。ID 必须仅由实体名确定——无论由哪个分块处理,同一实体必须永远产出同一 ID,这是分块并行与增量更新不产生重复节点的前提。
这一规则在引擎侧的根因是三个独立的 ID 生产者必须意见一致。graphify 以 graphify/ids.py 作为节点 ID 规范化的唯一事实来源,其模块文档开宗明义列出三方:① AST 抽取器(extract._make_id,确定性、逐语言);② 语义子智能体(LLM,即遵循本 spec 的 ID 规则);③ 图构建器(build._normalize_id,在 LLM 发出的 ID 带轻微大小写/标点漂移时对齐端点)。历史上 ID 漂移是反复出现的 bug 类别(Unicode 塌缩、同名文件冲突、AST 与 LLM 文件节点不一致等),因此该模块把归一化配方收敛到一处,并保证幂等、大小写无关稳定(graphify/ids.py)。这解释了为什么 spec 用一整段规定 ID 的拼写纪律——它不是风格建议,而是跨抽取器的握手协议。
十一、source_file 逐字透传规则与增量合并语义
规范对每个 node、edge、hyperedge 的 source_file 字段下达了"逐字透传"命令:
set source_file to the path of the originating file EXACTLY as it appears in FILE_LIST — verbatim and absolute. Do NOT shorten to a basename, do NOT re-relativize, do NOT strip any directory prefix, and do NOT change separators.
即把 FILE_LIST 中的条目逐字符复制进 source_file。原因是下游流水线会在构建根处统一规范化分隔符并相对化路径,而"逐字绝对路径"保证全量构建与增量 --update 站在同一基线上,让 build_merge 的重抽取替换(replace-on-re-extract)命中已有节点而非累积出一个重复节点。语义缓存也以 source_file 为粒度逐文件持久化,省略该字段会令刚抽取的语义文档从 manifest 掉队(graphify/cli.py 中 manifest 相关逻辑对此有专门注释)。
十二、写回磁盘:必须用绝对路径 CHUNK_PATH
规范的最后一步是写盘纪律:
Then write the JSON to disk using the Write tool at this exact absolute path (no relative paths — Write resolves relative paths against an undefined cwd and the file will be silently lost): CHUNK_PATH
子智能体必须把结果 JSON 写到编排方传入的绝对路径 CHUNK_PATH。之所以强调"不用相对路径",是因为在多数 Agent 宿主中,Write 工具的相对路径会基于一个未定义的工作目录解析,文件会被"静默丢失"——分块结果丢失不会立即报错,只会在合并时表现为语义层缺失,是极难排查的失败模式。平台编排方(如 graphify/skill-opencode.md 的 Part B)会在派发前推导好该绝对路径(典型命名如 ${PROJECT_ROOT}/graphify-out/.graphify_chunk_0N.json),再随 Prompt 一并下发。
十三、附:完整抽取 JSON Schema(来自规范的原始单行结构)
规范要求子智能体严格按下列 schema 产出(以下为原文结构,字段含义按本章与前述小节解读):
{"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(按第十节的确定性规则)、label(人类可读名)、file_type(六值枚举)、source_file(FILE_LIST 逐字透传)、source_location(通常为 null,AST 层填充行号)、source_url/captured_at/author/contributor(默认 null)。 - edges:
source/target为节点 ID;relation八值枚举;confidence三值;confidence_score遵循第九节刻度;source_file逐字透传;weight默认 1.0。 - hyperedges:
id为 snake_case、label为可读标签、nodes为成员 ID 数组(≥3)、relation三值、confidence/confidence_score/source_file与边同规则。 - input_tokens / output_tokens:由编排方或子智能体填写的 token 统计字段。
十四、元数据透传与工程实现对照
规范还包含一条与常见博客抓取场景强相关的规则:若文件带 YAML frontmatter(--- ... ---),须把其中的 source_url、captured_at、author、contributor 复制到该文件产出的每个节点上,让同一来源的实体共享来源元数据。
子智能体产出的片段并不会直接进图,而要经过多层工程处理:graphify extract 的语义通道读取 CLI 派发(graphify/cli.py 将 doc/paper/image 归为 semantic_files),结果经 graphify/build.py 的 merge_raw_extraction 与 AST 层合并(graphify/cli.py 的合并调用),期间 graphify/semantic_cleanup.py 负责清理句子化 rationale 节点、剔除非法 file_type 并对不可信片段做大小与 ID 边界校验。可验证该契约的测试还包括 tests/test_semantic_cleanup.py、tests/test_semantic_fragment_sanitize.py 与 tests/test_build_merge_hyperedges_and_prune.py。
十五、小结:从 Prompt 纪律到图的确定性
把这份 extraction-spec 通读下来,可以提炼出 graphify 语义层设计的四条主线:
- 把 LLM 当作受约束的抽取器而非自由写作者:严格 JSON、无前言、无 Markdown 围栏,是后续机器化合并与缓存的前提。
- 用离散刻度驯服置信度塌缩:宁可 AMBIGUOUS,不用 0.5 摆烂——这是把不可靠的模型判断变成可排序、可审阅、可量化的图数据的核心手段。
- 用确定性 ID 与逐字 source_file 对齐多生产者:分块并行、增量更新、重抽取替换之所以不会产生幽灵重复节点,靠的是"ID 仅由实体确定"与"source_file 逐字透传"这两条硬纪律。
- 激进模式有独立的缓存命名空间与运行语义:
--mode deep不只是改一行 Prompt,还牵动 cache namespace、语料门控宽度与构建端防护。
对于想在自己系统中复刻"文档→知识图谱"管线的开发者,这份 reference 本身就是一份高质量的工程样板:它把如何给子智能体写抽取任务、如何给模型判断配置信度、如何保证跨进程结果可合并这三类通用难题,压缩成了一段可逐字复用的 Prompt 与配套的引擎校验边界。
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 StartedRust0629
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