graphify 语义抽取 Subagent 规范解析:让文档、论文与图片进入可查询知识图谱的 JSON 契约
graphify 语义抽取 Subagent 规范解析:让文档、论文与图片进入可查询知识图谱的 JSON 契约
graphify 的核心管线分两层:AST 结构化抽取(确定性、零成本)覆盖代码符号,而语义抽取(LLM 驱动)负责把文档、论文、图片等非结构化语料转成知识图谱片段。本文深入讲解驱动第二层的“子代理提示词规范”——一份以 graphify/skills/trae/references/extraction-spec.md(及其 skillgen 生成产物 tools/skillgen/expected/graphify__skills__trae__references__extraction-spec.md)为代表的规范文档。读完你将掌握:语义子代理的逐字提示词结构、节点/边/超边 JSON 模式、确定性节点 ID 规则、离散置信度标尺、source_file 溯源契约,以及这些规则在 graphify/skill-trae.md 的 B0–B3 分块流水线中如何被组装执行。
一、这份规范在管线中的位置:Step 3 Part B
graphify 的抓取(skill)工作流在“Step 3 - Extract entities and relationships”一分为二(见 graphify/skill-trae.md):
- Part A — 结构化抽取:对代码文件做确定性 AST 解析,免费、无需 LLM,产物写入
graphify-out/.graphify_ast.json; - Part B — 语义抽取:仅当语料中至少有一个文档(doc)、论文(paper)或图片(image)分块时才运行;每个语义子代理拿到的是规范文档中的逐字提示词(需替换
FILE_LIST、CHUNK_NUM、TOTAL_CHUNKS、DEEP_MODE、CHUNK_PATH五个占位符),并将结果 JSON 写入自己的分块文件graphify-out/.graphify_chunk_NN.json。
两个关键约束贯穿全文:
- 纯代码语料跳过 Part B。一个典型的
/graphify .只含代码时,AST 已覆盖全部需要,语义规范文件“永远不被读取”,并先写入一个空语义文件以免 Part C 合并时报FileNotFoundError。 - 图构建过程不需要 API Key。语义层只有在用户已设置
GEMINI_API_KEY/GOOGLE_API_KEY时才可选走后端(graphify.llm.extract_corpus_parallel,默认模型gemini-3-flash-preview);未设置时由宿主 Agent 自身充当 LLM。graphify 不读取 Anthropic/OpenAI 等任何其他提供商的 Key。
需要说明:本文所述“引用文档”是各平台 SKILL 目录下同名规范文件的一份快照(Trae 平台对应的 expected 生成物与 graphify/skills/trae/references/extraction-spec.md 内容完全一致)。它同时也是各 Skill 平台共享的抽取契约。
二、三层证据置信度:EXTRACTED / INFERRED / AMBIGUOUS
规范要求子代理对每一条关系边都要先定性,再给定量分值,规则如下(规范原文逐字继承):
- EXTRACTED:关系在源文件中显式存在——导入、调用、引用、
"see §3.2"这类互引; - INFERRED:合理推断——共享数据结构、隐含依赖;
- AMBIGUOUS:不确定——标记供复核,而不是省略。
AMBIGUOUS 的核心价值在于“宁缺毋滥”的反面:抽取是丢失信息的单向过程,把可疑关系标注出来交给下游复核,比悄悄丢掉更能保住召回率。这一三层模型同样内建在 graphify/llm.py 的 _EXTRACTION_SYSTEM 系统提示词中(见 EXTRACTED: relationship explicit in source 等三行规则)。
三、按文件类型差异化抽取:与 AST 分工,绝不重复劳动
规范明确规定语义子代理不要重抽代码的导入边——AST 已经拿到了;代码文件要专注产出 AST 找不到的语义边。这保证了 file_type:"code" 节点由模型补充,而结构边仍由确定性子系统生成,两者在 Part C 合并(对应 graphify/extract.py 中“AST-extracted nodes from semantic/LLM nodes… replaces its AST edges without evicting the semantic edges”的合并逻辑)。
代码文件(code)
聚焦三类语义边:调用关系、共享数据、架构模式。两条铁律:
calls方向永不可反:source必须是调用方(发出调用的函数/类),target必须是被调方。calls绝不跨语言:Python 函数不能calls一个 JS/TS/Go/Rust/Java 符号,反之亦然。跨语言调用边是“幻影伪影”(phantom artifacts),一旦出现就会被识别为伪造关系。
文档 / 论文文件(document / paper)
抽取具名概念、实体、引文。关于设计理由(为什么做这个决策、权衡取舍、设计意图),规范有一个容易被误用、但又强调过两遍的规则:
把
rationale(理由)作为相关概念节点上的一个属性存储——绝不要为它单独创建 rationale 节点或片段节点。只有当某事物本身是具名实体或概念时才创建节点。
也就是说不存在“理由节点”这种半实体。同时规范允许使用 file_type:"rationale" 为“概念状节点”(思想、原则、机制、设计模式)归类——注意这与“rationale 属性”是两回事:前者是给概念本身的文件类型标签,后者是挂在具名概念节点上的理由正文。这与 graphify/llm.py _EXTRACTION_SYSTEM 中的指示一致:Rationale … store as a rationale attribute on the relevant node. Do NOT create separate rationale nodes。
图片文件(image):用视觉理解“它是什么”,不只是 OCR
规范对图片的抽取要求明显高于文本 OCR,按图型分类给出指令:
| 图型 | 要抽取的内容 |
|---|---|
| UI 截图 | 布局模式、设计决策、关键元素、用途 |
| 图表 | 指标、趋势/洞察、数据来源 |
| 推文/帖子 | 主张作为节点、作者、提及的概念 |
| 示意图 | 组件及其连接关系 |
| 研究图 | 它证明了什么、方法、结果 |
| 手写/白板 | 想法与箭头,不确定的识别标记为 AMBIGUOUS |
这是纯视觉理解任务:AST 无法解析像素,因此语义子代理是图片进入知识图谱的唯一通道。LLM 后端路径中,图片通过 _ImageRef 封装并按后端能力选择 vision 通道(见 graphify/llm.py 的 _partition_semantic_files、_build_image_refs 等函数)。
四、file_type 六枚举:契约内别无他值
任何节点上的 file_type 必须是以下六个值之一,其余全部非法、会被拒绝:
code | document | paper | image | rationale | concept
该枚举在下游消费端有真实语义:合并/裁剪逻辑会区分代码节点与语义节点(例如 graphify/extract.py 中对 file_type == "rationale" 与命名空间的处理、graphify/llm.py 的证据绑定只针对 file_type == "code" 的节点做源码定位)。模型一旦输出自定义标签,整个分块的合并行为就会退化,因此规范用“会拒绝”而非“建议遵守”表述。
五、--mode deep:激进的 INFERRED 模式
当用户以 --mode deep 发起抓取时(graphify/skill-trae.md 要求把这个开关一路传到每个子代理:DEEP_MODE=true),规范指示子代理对 INFERRED 边更激进:补充间接依赖、共享假设、潜在耦合;拿不准的标成 AMBIGUOUS 而不是省略。
注意 deep 模式不是放开想象力的开关。在 graphify/llm.py 的 _DEEP_EXTRACTION_SUFFIX 中措辞更收敛:只允许为具体的架构信号(共享数据契约、显式生命周期耦合、源码中可见的多步流程依赖)增加 INFERRED 边,并要求“避免宽泛的概念相似边”。可见 deep 模式的精髓是“深”而非“野”。
六、语义相似边:跨结构的非平凡关联
若分块内两个概念解决同一问题、代表同一思想,却没有任何结构链接(无导入、无调用、无引用),规范允许加一条 semantically_similar_to 边,标为 INFERRED,并给出 0.6–0.95 的相似度。规范示例:
- 两个都校验用户输入、但从不互相调用的函数;
- 代码里的类与论文里描述同一算法的概念;
- 两个用不同方式处理同一失败模式的错误类型。
同样有限制:只在相似确实非平凡且跨领域时才加,不为显然相似的东西添加。这是图谱避免“近重复聚集噪声”的重要防线。
七、超边(hyperedges):二元边表达不了的分组
当 3 个及以上节点明显共同参与某个共享概念、流程或模式,而二元边两两表达又说不清时,加入顶层 hyperedges 数组。规范示例:
- 实现同一协议/接口的所有类;
- 认证流程中的全部函数(即便它们并不两两互调);
- 论文某节中构成一个完整思想的所有概念。
并给出克制性上限:每个分块最多 3 条超边,只在分组信息超出二元边总和外才值得使用。超边的三个关系取值是 participate_in | implement | form,同时也要带 confidence/confidence_score。
八、YAML frontmatter 透传
若文件带 YAML frontmatter(--- ... ---),规范要求把 source_url、captured_at、author、contributor 复制到该文件产出的每一个节点上。这一规则让“某网页正文某段主张”具备可追溯的出处元数据——后续导出 HTML 图谱或做审计时,节点能直接回链到采集来源。
九、置信度标尺:每边必带、绝不 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。
规范还解释了为何如此设计:模型遵循离散标尺远好于连续区间。生产观测到明显的双峰分布(>50% 堆在 0.5、>40% 堆在 0.85+),说明区间引导正被模型坍缩成二值判断。离散取值 + 禁用 0.5 就是针对这一退化模式的工程性对抗。这与 graphify/llm.py 顶层系统提示词中的打分规则完全同源(同见 INFERRED edges: pick exactly ONE value 注释)。
十、确定性节点 ID:与 AST 抽取器对得上,否则就是幽灵重复
节点 ID 是全篇对工程质量要求最高的部分,因为 ID 必须与 AST 抽取器生成的 ID 完全一致:
- 只允许小写
[a-z0-9_],不含点号、斜杠; - 格式:
{stem}_{entity}stem= 完整仓库相对路径去掉扩展名,每一级目录都保留,逐段小写、非字母数字字符替换为_;entity= 符号名,同样规范化;
- 用每一级目录而非仅直接父目录,从而区分不同目录下的同名文件。
规范给出的正例对照表:
| 源文件 + 符号 | 期望 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 |
并给出反面教材:只留文件名(session_validatetoken)或只留直接父目录(auth_session_validatetoken)都会产生 孤儿幽灵重复节点。如果要对旧版“仅直接父目录”格式的项目重新抽取,规范要求用户运行 graphify extract --force 干净重建。最关键的纪律是:永远不要给 ID 追加分块号、序号或任何后缀(禁止 _c1、_c2、_chunk2),ID 必须由标签本身确定性决定——同一实体无论被哪个分块处理,都必须产出同一 ID。
这一确定性约束在实现层有对应物:graphify/ids.py 的 normalize_id(graphify/ids.py)通过 casefold + NFKC 迭代至不动点、再过滤到 [a-z0-9_],保证幂等、大小写折叠稳定、仅含 \w 与下划线。整条管线对“同一实体稳定映射同一 ID”的追求贯穿 graphify/extract.py(例如把 AST 文件节点与语义分块合并时避免拆出幽灵节点的注释),这正是 Part B 分块能并行、Part C 又能无歧义合并的前提。
十一、完整输出 JSON 模式(逐字继承)
子代理必须输出唯一合法的 JSON,无解释、无 Markdown 围栏、无前言。模式原文如下:
{
"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
}
字段要点归纳:
- 边关系取值枚举:
calls | implements | references | cites | conceptually_related_to | shares_data_with | semantically_similar_to | rationale_for; - 超边关系枚举:
participate_in | implement | form; - 节点顶层另有可选的
rationale字段承载“为什么”(见第三节);LLM 后端模式(graphify/llm.py)中的 schema 还补充了rationale键与面向后端的提示变体; input_tokens/output_tokens由子代理填充(LLM 后端会回填真实 usage,见 graphify/skill-trae.md 对占位零值的说明)。
十二、source_file 溯源契约:逐字照抄,绝不改写
凡节点、边、超边,source_file 都必须设为来源文件在 FILE_LIST 中出现时的原样路径——逐字且为绝对路径:
- 不要缩写成 basename;
- 不要重新相对化;
- 不要剥掉任何目录前缀;
- 不要改分隔符——引擎会在下游统一分隔符并相对化到构建根目录。
原因规范解释得很直白:FILE_LIST 条目需逐字符复制,才能让全量构建与增量 --update 站在同一基线上,使 build_merge 的“重抽即替换”(replace-on-re-extract)命中既有节点而不是不断累积重复。若每个分块都按自己的“本地视角”改路径,同源文件的多次抽取就会变成永不重合的重复节点。
十三、落盘纪律:绝对路径 CHUNK_PATH
子代理产出 JSON 后,必须用 Write 工具写到规范给定的确切绝对路径 CHUNK_PATH。规范特别强调:不允许相对路径——Write 会相对一个未定义的 cwd 解析相对路径,文件将被静默丢失。这正是把“Part C 合并”与“B3 缓存回写”建立在磁盘文件存在性之上的原因(见 graphify/skill-trae.md:以文件存在作为成功信号,缺失则告警“子代理可能被派发成只读 Explore 类型”)。
十四、从提示词到流水线:Trae 平台上的 B0–B3 装配
规范文档并非孤立存在,它在 graphify/skill-trae.md 的调度代码中按 B0–B3 被实例化:
- B0 缓存检查:对
detect结果中document/paper/image三类文件调用graphify.cache.check_semantic_cache,传入prompt_file=SPEC_PATH(规范文件的绝对路径)。由于缓存条目按提示词归属(issue #1939),graphify 升级改过提示词后旧条目会重新抽取,提示词未变则复用。只对uncached列表派发子代理。 - B1 分块:每块 20–25 个文件;每张图片独占一块(视觉需要独立上下文);同目录文件尽量合块以增加抽取跨文件关系的机会。
- B2 单消息并行派发:对每个块调用一次
Task,且全部在同一个响应里发出以并行执行;把规范提示词(替换好 5 个占位符)作为任务描述,子代理各自写到graphify-out/.graphify_chunk_NN.json。Trae 平台的一个关键差异是:不支持 PreToolUse 钩子,因此 AGENTS.md 规则才是常驻的机制(见 graphify/skill-trae.md)。 - B3 收集 / 缓存 / 合并:以磁盘文件为成功信号收集各块,无效 JSON 告警跳过;回填真实 token 数后把
_graphify_chunk_*合并为_graphify_semantic_new.json,再经save_semantic_cache入缓存(同样带 SPEC_PATH 戳记),最终与缓存命中合并进.graphify_semantic.json供 Part C 使用。超过一半块失败即停止并提示用general-purpose子代理重跑。
整条链路里,本规范(extraction-spec)既是被缓存体系“指纹化”的对象(提示词变了缓存即失效),又是并行子代理唯一的行为契约——前后一致、单一事实来源。
十五、工程要点自查清单
把规范浓缩成可执行的验收标准:
- 输出纪律:仅输出合法 JSON,无解释、无 Markdown 围栏、无前言;
- 定性先行:每条边先定 EXTRACTED/INFERRED/AMBIGUOUS,AMBIGUOUS 宁标勿删;
- 与 AST 分工:代码文件不重抽 import,只补语义边;
calls方向 source=调用方,且严禁跨语言; - rationale 不建点:设计理由挂到具名概念节点的
rationale属性上;只有概念本身才可用file_type:"rationale"; - 图片靠视觉:按图型抽取本质而非做 OCR;
- 打分禁用 0.5:EXTRACTED=1.0;INFERRED 只在 {0.95,0.85,0.75,0.65,0.55} 中取值,不匹配则标 AMBIGUOUS(0.1–0.3);
- ID 确定性:
{完整相对路径去扩展名}_{实体}、全小写[a-z0-9_]、不加任何分块后缀;旧格式工程用graphify extract --force重建; - source_file 逐字:来自
FILE_LIST原样绝对路径,保证全量与增量共享基线; - 落盘用绝对路径:写到
CHUNK_PATH,相对路径会静默丢文件。
十六、延伸阅读
- 平台技能全流程:graphify/skill-trae.md(Step 3 及 B0–B3 代码);各 Agent 平台的同名技能文件(
graphify/skill-agents.md、graphify/skill-claude.md、graphify/skill-codex.md等)均复用同款规范,各有平台派发差异; - LLM 后端系统提示词与模式:graphify/llm.py(
_EXTRACTION_SYSTEM、_DEEP_EXTRACTION_SUFFIX、extract_corpus_parallel); - 语义缓存归属机制:graphify/cache.py(
check_semantic_cache/save_semantic_cache的prompt_file参数); - ID 规范化实现:graphify/ids.py(
normalize_id的 casefold + NFKC 迭代);AST 侧结构化抽取见 graphify/extract.py 与 graphify/extractors。
一言以蔽之:这份 extraction-spec 是 graphify“无向量库、全图可解释”主张在语义层的落地契约——用确定性的 ID 与离散置信度,把 LLM 的自由文本输出约束成可合并、可缓存、可增量的图谱事实。
更多推荐

所有评论(0)