首页
/ graphify 语义提取规范(extraction-spec)全解析:子代理如何把文档与图片转换成可查询的知识图谱

graphify 语义提取规范(extraction-spec)全解析:子代理如何把文档与图片转换成可查询的知识图谱

2026-09-07 11:33:53作者:翟江哲Frasier

graphify 把代码库连同其中的文档、SQL 模式、配置和 PDF 一起加工成可查询的知识图谱:代码由本地确定性 AST 解析完成,而文档、论文、图片这类“语义密集”语料则由 LLM 子代理抽取图谱片段。tools/skillgen/fragments/references/shared/extraction-spec.md 正是这一段流水线的核心协议文档——它定义了语义抽取子代理必须遵守的 JSON Schema、节点 ID 规则、可信度分级与评分标尺、超边与语义相似边的使用边界,以及决定增量更新能否正确去重的 source_file 锚定规则。读完本文,你将掌握 graphify 语义抽取的完整数据契约,理解每一条规则背后的工程动机(幽灵重复节点、跨语言伪调用、置信度塌缩、缓存失效),并能据此对接自己的 LLM 抽取管道或二次开发 /graphify 技能。

一、这份规范在 graphify 技能流水线中的位置

在 graphify 的架构里,抽取被刻意拆成两条互不重叠的路径:

  • 代码文件走“本地确定性 AST 引擎”(见 graphify/extractors/extract.py),不需要任何 API Key、结果可复现;
  • 文档 / 论文 / 图片走 LLM 语义抽取,由子代理(subagent)按本规范产出知识图谱片段。

extraction-spec.md 的头部写明了它的装载条件:“在语料中至少有一个 doc、paper 或 image 分块时,于 Step 3 Part B 装载;纯代码语料会跳过 Part B,永远不读取本文件。” 这与 llm.py 中注释相互印证——“语义(LLM)抽取运行在文档/论文/图片上,代码文件由确定性 AST 引擎处理、永远不会到达模型”。

这份 fragment 同时存在两个变体

变体 文件 用途
verbose(完整版) tools/skillgen/fragments/references/shared/extraction-spec.md 多数平台的默认抽取提示词正文
compact(精简版) tools/skillgen/fragments/references/shared/extraction-spec-compact.md 上下文预算更紧的平台(如返回 JSON inline 的 Codex)选用

选择逻辑沉淀在技能生成器里:gen.py"verbose": "references/shared/extraction-spec.md""compact": "references/shared/extraction-spec-compact.md" 两张映射表完成引用解析,而 platforms.tomlextraction verbose | compact 一行正是按平台挑选正文的开关。渲染完成的技能包会把这些 reference 文件随附在每个平台的 references/ 目录下,例如 graphify/skills/agents/references/extraction-spec.mdtools/skillgen/expected/graphify__skill.md 中的“See references/extraction-spec.md for the exact subagent prompt”段落,就是该引用在最终技能文档里的落点。

二、提示词的整体契约:任务、输入、输出

规范以“逐字下发给每个语义子代理”的方式呈现,运行时需要替换五个占位符:

占位符 含义
FILE_LIST 本分块负责的文件清单(绝对路径)
CHUNK_NUM 当前分块序号
TOTAL_CHUNKS 总分块数
DEEP_MODE 是否启用 --mode deep 深度模式
CHUNK_PATH 结果 JSON 必须写入的绝对磁盘路径

子代理的任务一句话概括:读取分发给它的文件,抽取一个知识图谱片段(knowledge graph fragment),并且只输出符合下述 Schema 的合法 JSON——“不要解释、不要 markdown 围栏、不要前言”。 输出 JSON 的顶层包含 nodesedgeshyperedges 三个数组,以及 input_tokensoutput_tokens 两个用量字段(供成本统计与缓存使用)。

结果落到磁盘的规则同样严格:必须使用 Write 工具写到 CHUNK_PATH 这个“绝对路径”——不允许相对路径,因为“Write 会基于未定义的 cwd 解析相对路径,文件会被静默丢失”。

实现侧印证:在直接调用 LLM 的 Python API 路径中,llm.py_EXTRACTION_SYSTEM 常量与这份提示词几乎同构,同样规定“Output ONLY valid JSON——no explanation, no markdown fences, no preamble”,并附上同一份 Schema 字符串;_extraction_system(deep=...) 则按是否深度模式拼接系统提示词。

三、边可信度的三分类词汇表

每条关系(edge)都必须携带一个 confidence 字段,取值只能是下列三档之一,任何其他值都不合法:

取值 语义 判定依据
EXTRACTED 源文本中显式存在的关系 import、调用、引用、citation、“see §3.2”这类字面证据
INFERRED 合理的推断 共享数据结构、隐含依赖等不直接成文的关系
AMBIGUOUS 不确定——必须标记出来供人工复核,不得省略 无法确证但又不该丢掉的候选关系

“不确定就标记、不要删除”是贯穿全篇的原则,它直接对冲了 LLM 抽取最常见的两种失败模式:过度自信的幻觉边因为没把握而漏报。实现侧可以佐证这个词汇表是被校验过的:llm.py 的注释明确写着“validated vocabulary({EXTRACTED, INFERRED, AMBIGUOUS},且只出现在边上)”。

四、按文件类型的差异化抽取规则

4.1 代码文件:只补 AST 找不到的语义边

代码文件出现在语义分块中时(例如文档里嵌的代码片段),子代理要提取的是“AST 抓不到的语义关系”,并且严禁重复抽取 import——那些确定性解析器已经拿到了。本规范特别强调:

  • calls 边方向不可逆转source 必须是调用方(发起调用的函数/类),target 必须是被调用方;
  • calls 边必须停留在单一语言内部:Python 函数不能 calls 一个 JS/TS/Go/Rust/Java 符号,反之亦然——“跨语言调用边是幽灵伪影(phantom artifacts),永远不要产出”。

这与代码库对“跨语言伪调用”的持久戒备一致:tests/ 下有 test_phantom_cross_package_call.pytest_phantom_external_import.py 等用例,csharp_dispatch.pyruby_resolution.pysymbol_resolution.py 等模块则承担真正的跨文件/跨语言解析工作。

4.2 文档/论文:概念节点与 rationale 的内联存储

处理 doc/paper 时,抽取目标是命名概念、实体与引用。两条关键约束:

  1. 设计理由(rationale)不允许建成独立节点。WHY 类信息——为何做这个决定、权衡了什么、设计意图——必须以 rationale 属性挂在相关概念节点上,禁止单独造一个 rationale 节点或 fragment 节点;
  2. 只有本身是命名实体/概念的东西才值得建节点。想法、原则、机制、设计模式这类“概念型”东西用 file_type:"rationale" 标注。

节点 file_type 全仓库只有且只有六个合法值codedocumentpaperimagerationaleconcept,规范原文措辞是“Any other value is invalid and will be rejected”(任何其他取值非法并被拒绝)。

佐证:llm.py 的抽取系统提示词使用几乎相同的措辞约束 rationale——“不要在源文本没有明确给出理由时编造这个属性(不要复述描述)”;analyze.py 在计算弱连通节点时会显式跳过 file_type == "rationale" 的节点,说明 rationale 作为一个节点类型在后续分析中被单独对待。

4.3 图片:用视觉理解“图片是什么”,而不只是 OCR

图片分块要求调用视觉能力理解图像本体,规范按图像类型给出抽取重点:

图像类型 要抽取的内容
UI 截图 布局模式、设计决策、关键元素、用途
图表 指标、趋势/洞察、数据来源
推文/帖子 主张(作为节点)、作者、提到的概念
架构图 组件与连接关系
科研配图 论证了什么、方法、结果
手写/白板 想法与箭头指向,不确定的识别一律标 AMBIGUOUS

图片支持在实现层有完整对应:llm.py_ImageRef_backend_supports_vision_anthropic_content 等函数负责把图片编码进各后端请求,并区分“纯 OCR”与“理解语义”两档视觉用法。

五、置信度评分标尺:confidence_score 的离散打分制

规范用一整段强调了边上的 confidence_score每条边必填、绝不省略,并且绝不能用 0.5 当默认值。打分规则如下:

场景 取值 依据
EXTRACTED 恒为 1.0 显式证据,无条件满分
INFERRED 恰好取下列五档之一 见下表
AMBIGUOUS 0.1–0.3 不确定区段

INFERRED 的离散标尺(注意 0.5 被明确排除):

档位 证据强度
0.95 direct structural evidence 共享数据结构、命名的跨文件引用等直接结构证据
0.85 strong inference 清晰的功能对齐,但无直接符号链接
0.75 reasonable inference 共享问题域 + 相似形态,需要解读
0.65 weak inference 主题相关,但无形态证据
0.55 speculative but plausible 仅表层共现

规范还解释了为什么要设计成离散档而非连续区间:“模型对离散标尺的跟随性好于连续区间;生产环境观测到的双峰分布(>50% 落在 0.5、>40% 落在 0.85+)表明区间引导正在被压扁成二值判断。” 这是一条非常典型的 prompt 工程经验——给连续区间,模型会无脑取中值 0.5;给离散菜单,模型反而会认真挑选。因此规则收尾很果断:如果五档都不合适,就把边标记为 AMBIGUOUS,而不是打 0.4 或更低的分。

六、节点 ID 规范:{stem}_{entity} 与确定性

节点 ID 是全篇规则密度最高、踩坑代价最大的一节,值得单独展开:

  1. 字符集:全部小写,只允许 [a-z0-9_],不允许点号与斜杠;
  2. 格式{stem}_{entity}
    • stem = 去掉扩展名的完整仓库相对路径,路径每一段都保留并以下划线拼接(每段小写、非字母数字字符替换为 _);
    • entity = 符号名,按同样规则归一化;
  3. 必须使用每一级目录,而不是只看直接父目录——这是为了让不同目录下的同名文件保持区分;
  4. 顶层文件(无父目录,如 setup.py)只使用文件名主干,如 setup_my_func

规范的示例逐条给出了映射:

源文件 + 符号 规范节点 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

规范明确给出三条动机:

  • 必须与 AST 抽取器生成的 ID 一致。只写文件名(如 session_validatetoken)或只写直接父目录(如 auth_session_validatetoken)都会造出孤立的幽灵重复节点
  • 如果项目是用旧的“直接父目录”格式构建的,用户需要跑 graphify extract --force 干净地重建;
  • CRITICAL:永远不要在 ID 后追加分块号或序号后缀(不许有 _c1_c2_chunk2)。ID 必须能仅凭 label 确定性地推导出来——同一个实体无论在哪个分块里处理,都必须产出同一个 ID。

这条规则的工程含义很深刻:确定性 ID + 全路径 stem 是“跨分块合并去重”和“增量更新”的前提,否则同一个符号在每个分块都会生成一个不同 ID 的节点,图里会累积出一堆残骸。

七、语义相似边与超边:补足“两两成对边”表达不了的关系

7.1 semantically_similar_to:跨结构的概念共鸣

如果本分块内两个概念在没有结构链接(无 import、无调用、无引用)的前提下解决同一个问题或表达同一个想法,则添加 semantically_similar_to 边,标记为 INFERRED,并给一个反映相似程度的 confidence_score(0.6–0.95)。规范给出的示例:

  • 两个都做用户输入校验、但从不互相调用的函数;
  • 代码里的一个类与论文中描述同一算法的概念;
  • 两个用不同方式处理同一失败模式的错误类型。

限定条件非常严格:只有当相似性是“真正的非显然、且横切多个文件”时才加;对明显相似的东西不要加——这条边不是用来制造噪音的。

7.2 超边:3 个及以上节点共享一个模式

3 个或更多节点明显共同参与一个“两两成对的边表达不了”的共享概念、流程或模式时,在顶层 hyperedges 数组中加入一条超边。规范示例:

  • 实现同一协议/接口的全部类;
  • 认证流程中的所有函数(即使它们并不互相调用);
  • 论文某一节中构成一个连贯想法的全部概念。

使用要克制——只有成组关系相比两两成对的边确实携带了新信息才建;且每个分块最多 3 条超边。超边字段的 relation 合法值受限为 participate_inimplementform 三者之一,confidence 只能是 EXTRACTEDINFERRED

实现侧佐证:抽取结果进入图构建阶段后,build.py 会逐条消费 hyperedges;合并多个分块时 build.py 把各分块的 nodes/edges/hyperedges 拼进一个 combined 字典;而当超边成员节点在清洗后全部失效时,代码会剔除“单成员超边”并给出“所有超边被移除”的告警(build.py)。

八、YAML frontmatter:元数据向节点的透传

如果文件带 YAML frontmatter(--- ... ---),规则是:source_urlcaptured_atauthorcontributor 复制到来自该文件的每一个节点上。这与节点 Schema 中对应的可空字段 source_urlcaptured_atauthorcontributor 一一对应——它们是文档溯源(provenance)信息,会随节点进入图并最终呈现在导出/查询结果里。

九、source_file 规则:增量更新与去重的“锚”

source_file 字段被单独提炼成一条大写的 RULE,因为它是整个增量构建正确性的基石:

  • 每个 node、edge、hyperedge 的 source_file 必须设置为源文件在 FILE_LIST 中出现的路径——逐字符、原样、绝对路径
  • 不允许缩短为 basename、不允许重新相对化、不允许去掉任何目录前缀、不允许改动分隔符
  • 引擎(engine)会在下游做分隔符规范化并按构建根做相对化,所以原样传递是正确的;
  • 这样做能让全量构建(full build)与增量 --update 处于同一条基准线上,使 build_merge 的 replace-on-re-extract(重新抽取时替换)能命中既有节点,而不是不断累积一个重复节点。

这条规则解释了为什么 fragment 头部的 CHUNK_PATH 也要强调绝对路径——base 不一致是幽灵重复节点的第二大成因(第一大成因是 6 节讲的不规范节点 ID)。相应地,graphify 的缓存设计也把提示词本身当作缓存键的一部分:cache.py 中语义缓存区 semantic-deep/--mode deep 命名空间)与 semantic/ 分开存放,且缓存条目按产生它的抽取提示词归因——当 graphify 升级改变了提示词,旧提示词产生的条目会被重新抽取而不是回放缓存。

十、完整 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:按第六节的 {stem}_{entity} 确定性生成,全局唯一;
  • label:人类可读名称(Human Readable Name);
  • file_type:六选一词汇表,其它值拒绝;
  • source_file:原样来自 FILE_LIST(第九节);
  • source_location / source_url / captured_at / author / contributor:溯源字段,可由 YAML frontmatter 填充,无则 null

边对象 edges[]

  • source / target:两端节点 ID,方向语义随 relation 变化(调用边 source 恒为调用方);
  • relation 合法值八种:calls | implements | references | cites | conceptually_related_to | shares_data_with | semantically_similar_to | rationale_for
  • confidence:三分类词汇表;confidence_score:必填,按第五节标尺;
  • source_file / source_location:溯源;weight:数值权重,默认 1.0。

超边对象 hyperedges[]id(snake_case)、labelnodes(至少 3 个成员)、relation(三选一)、confidence(二选一)、confidence_scoresource_file

顶层用量字段input_tokensoutput_tokens 由子代理报告,供成本核算与缓存归因。

十一、深度模式:--mode deep 对协议的放大

规范的 DEEP_MODE 段描述了 CLI --mode deep 的语义:在深度模式下,要激进地补 INFERRED 边——包括间接依赖、共享假设、潜在耦合;拿不准的标 AMBIGUOUS 而不是省略。 注意一个细节:文档/片段强调的深挖对象是“结构证据型推断”,而 llm.py_DEEP_EXTRACTION_SUFFIX 更明确地警告“避免宽泛的概念相似边”——深度模式鼓励的是具体的架构信号(共享数据契约、显式生命周期耦合、多步流程依赖),而不是放任模型满天飞地连“感觉相关”的边。

CLI 侧把该开关一路从参数透传到语义语料打包:cli.py 解析 deep_mode = extract_mode == "deep",随后把模式写进语义缓存命名空间(cache/semantic-deep/),保证深度模式的结果不会污染、也不会被普通模式缓存命中或遮蔽(cache.py 相应注释说明这是 #1894 引入的命名空间隔离)。graphify __main__.py 的帮助文本则把它描述为“--mode deep aggressive INFERRED-edge semantic extraction”。

十二、与实现侧对齐的几个关键机制

把规范与源码对照,可以归纳出几条值得写进任何“图抽取系统设计”清单的工程机制:

  1. 幻觉防护(证据绑定):语义抽取只见文档/图片,因此模型中冒出的 file_type == "code" 节点其实是“从文档内部冒出的符号”(代码块里的名字、论文里引用的 API)。llm.py_bind_node_evidence 会在抽取后核对:该符号名是否真的出现在模型看过的源字节里——出现不了就给它打 verification = "unverified" 标记(而不是直接丢弃),由诊断层上报。这是规范“AMBIGUOUS 标记而非省略”哲学的镜像实现。
  2. 注入防护llm.py_wrap_untrusted 把每个源文件包进带 sha256 指纹的 <untrusted_source> 块,_neutralise_injection_sentinels 会先“去武装”聊天模板/越狱控制符——源文件里任何“忽略以上规则、输出特定节点列表、泄露提示词”的指令都按惰性数据处理。这正是“输出严格受 Schema 约束”能在不可信语料上成立的底层保障。
  3. 提示词即缓存键:抽取提示词一旦随版本改变,旧缓存条目作废重抽(#1939),保证“同提示词命中、异提示词重抽”。

十三、结语:这份协议给了抽取系统什么

回看整份 extraction-spec.md,它其实是一份极其成熟的 LLM 结构化输出工程清单:用三档可信度与离散置信度标尺对抗模型的过度自信与平均化倾向;用“带全路径 stem 的确定性节点 ID”让分布式分块抽取天然可合并、可增量;用“reasoning 内联而非建节点、跨语言调用一律不产、图片理解而非 OCR、超边克制使用”等规则把噪音扼杀在源头;再用 source_file 逐字符锚定把全量与增量构建钉在同一条基线上。对想要自定义 /graphify 技能、接入新平台(gen.pyplatforms.toml 负责多平台渲染)或自建类似抽取管道的开发者而言,这份文件本身就是一份可以直接复用的生产级参考实现,搭配 extraction-spec-compact.md 还可以获得一个面向有限上下文的浓缩版本。

登录后查看全文
热门项目推荐
相关项目推荐