graphify 语义抽取子代理规范:extraction-spec 的节点 ID、置信度量规与 JSON 输出契约
2026/9/7 3:42:49 网站建设 项目流程

graphify 语义抽取子代理规范:extraction-spec 的节点 ID、置信度量规与 JSON 输出契约

【免费下载链接】graphifyTurn any codebase, with its docs, SQL schemas, configs, and PDFs, into a queryable knowledge graph. A /graphify skill for Claude Code, Cursor, Codex, and Gemini CLI: local deterministic AST parsing, every edge explained, no vector store.项目地址: https://gitcode.com/GitHub_Trending/graph/graphify

graphify 的/graphify技能将代码库连同文档、论文和图片一并转成可查询的知识图谱,其核心流程分为 AST 结构抽取(Part A)与语义抽取(Part B)两条并行管线。本文以 Codex 平台技能自带的 extraction-spec.md 为主体,完整解析语义抽取子代理的提示词规范——包括置信度三级量规(EXTRACTED / INFERRED / AMBIGUOUS)、节点 ID 的确定性命名格式、source_file逐字引用规则、超边(hyperedge)约束和严格的 JSON 输出模式——并结合 graphify/ids.py、graphify/build.py 的源码,说明这些规范如何在 AST 抽取器、LLM 子代理和图构建器三方之间保证 ID 与源文件的一致性。

读完本文,你可以掌握:如何阅读并修改这份抽取提示词规范、每条规则的工程动机(为什么source_file必须逐字、为什么 INFERRED 分数不允许取 0.5),以及子代理输出如何经过校验、合并与增量替换最终进入graph.json

一、定位:extraction-spec 在构建管线中的加载时机

规范文件开头第一句就明确了自身的使用条件:

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-codex.md 中 Part B 的“快速路径”描述一致:当检测结果为零文档、零论文、零图片时,AST 直接处理代码,语义子代理无事可做,流程会先写一个空的语义结果文件,然后直接进入 Part C 合并。

规范同时规定了提示词的分发方式:每个语义子代理逐字接收同一份提示词(仅替换四个占位符):

  • FILE_LIST:该子代理负责的文件列表;
  • CHUNK_NUM/TOTAL_CHUNKS:当前 chunk 序号与总 chunk 数;
  • DEEP_MODE:是否处于--mode deep深度模式。

在 Codex 平台的执行细节上,skill-codex.md 的 Step B1 要求把未缓存文件按每 20–25 个文件切块(每张图片独占一个 chunk,因为视觉理解需要独立的上下文窗口;同一目录的文件尽量分进同一块,以提高跨文件关系被抽到的概率);Step B2 则通过spawn_agent(agent_type="worker", ...)同一条响应中一次性派发全部子代理并行执行,要求~/.codex/config.toml[features]下开启multi_agent = true。子代理返回的 JSON 在内存中累积,合并写入graphify-out/.graphify_semantic_new.json,其中无效 JSON 的 chunk 即视为失败信号。

二、提示词全文规范:角色约束与抽取规则

规范的核心是一段以代码块给出的、可逐字转发的子代理提示词。它首先锁定输出纪律:

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.

要求只输出符合 schema 的合法 JSON——无解释、无 markdown 围栏、无前言。这是因为上游合并脚本直接对该返回值做json.loads,任何多余文本都会使整个 chunk 报废。随后是八条抽取规则,逐条对应图谱构建中的一个具体工程决策:

  1. 三级置信度语义EXTRACTED表示源文件中显式存在的关系(import、调用、引用);INFERRED表示合理推断(共享结构、隐含依赖);AMBIGUOUS表示不确定——标记出来,而不是省略
  2. 代码文件的边界:只补 AST 找不到的语义边,绝不重复抽取 import(import 边已由 Part A 的 AST 抽取确定性产出)。calls边有方向纪律:source 是调用方、target 是被调用方,永不反转;并且calls保持单一语言内,避免跨语言的虚假调用边。
  3. 文档/论文文件:抽取命名概念、实体与引用。决策理由(rationale,即"为什么做这个决定")不作为独立节点,而是作为rationale属性挂在相关节点上;概念类节点(思想、原理、机制)用file_type:"rationale",命名概念用file_type:"concept"file_type必须且只能是六个值之一codedocumentpaperimagerationaleconcept——任何其他值都会被拒绝。
  4. 图片文件:使用视觉能力理解图片"是什么",而非仅做 OCR 文字识别。
  5. 深度模式DEEP_MODE(即--mode deep)下对 INFERRED 边更激进——间接依赖、共享假设、潜在耦合都值得抽取,拿不准的标 AMBIGUOUS 而不是丢掉。
  6. 语义相似边:两个概念解决同一问题或表达同一思想、但没有结构性连接(无 import、call 或 citation)时,添加semantically_similar_to边,置信度标 INFERRED、confidence_score取 0.6–0.95;仅限非显而易见的跨文件连接。
  7. 超边:当 3 个以上节点共享一个未被成对边捕获的概念、流程或模式时,加入顶层hyperedges数组;慎用,每个 chunk 最多 3 条
  8. YAML frontmatter 透传:若文件含 frontmatter(--- ... ---),把其中的source_urlcaptured_atauthorcontributor复制到该文件的每一个节点上——这些是网页快照类语料的可溯源字段。

2.1 置信度量规(confidence rubric)

规范对confidence_score的要求是全部规则中最严格的一条:

confidence_score is REQUIRED on every edge — never omit it, never use 0.5 as a default.

  • EXTRACTED恒为1.0——源文件中显式存在的关系不打折扣;
  • INFERRED必须从离散刻度中恰好选一档: | 分值 | 含义 | | --- | --- | | 0.95 | 有直接结构性证据(direct structural evidence) | | 0.85 | 强推断(strong inference) | | 0.75 | 合理推断(reasonable inference) | | 0.65 | 弱推断(weak inference) | | 0.55 | 推测但可信(speculative but plausible) |
  • 永不允许 0.5:0.5 被明确保留为"未认真评估"的占位值,若五档均不符合,就把边标为 AMBIGUOUS 而非硬给一个分数;
  • AMBIGUOUS区间为0.1–0.3——存在但存疑的关系保留在图中(供查询端按分数过滤),而不是被静默丢弃。

这套离散刻度的意义在于:图查询与排序可以对confidence_score做阈值过滤时,每个分数都有可解释的语义档位,而不是 LLM 随手生成的连续噪声。

2.2 节点 ID 格式

规范给出了一条确定性命名规则:

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_... Use every directory level, not just the immediate parent.src/auth/session.py+ValidateTokensrc_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.

拆解为四条要点:

  • 字符集仅限小写[a-z0-9_],不含点与斜杠,每个路径段的非字母数字字符替换为_并小写化;
  • stem 是完整仓库相对路径去扩展名、各段以_拼接——必须用每一级目录,而非仅直接父目录(这是避免同名文件碰撞的关键,例如a/utils.pyb/utils.py中的同名函数);
  • 顶层文件直接用文件名 stem;
  • ID 必须与 AST 抽取器产出的 ID 完全一致,且不得附加 chunk 或序号后缀——同一实体无论从 AST 还是语义通道进入图中,ID 都只能由其标签唯一确定,否则同一实体会在图中裂成两个互不相连的"幽灵节点"。

这条"必须与 AST 抽取器一致"是整份规范里承重的约束,下一节说明源码如何兑现它。

三、源码印证:ID 规范为何要"确定性到逐字符"

graphify/ids.py 的模块 docstring 开宗明义:节点 ID 有三个独立的生产方,三方必须完全一致,否则图会把单一实体拆成断连的幽灵节点——

  1. AST 抽取器(extract._make_id):确定性、按语言;
  2. 语义子代理(LLM):遵循的正是本文解析的这份规范;
  3. 图构建器(build._normalize_id):在 LLM 输出的 ID 与 AST 的标点/大小写略有出入时对边端点做归一化调和。

历史上归一化配方曾复制粘贴在extract._make_idbuild._normalize_id两处、仅靠镜像 docstring 维持同步,docstring 列举了因此反复出现的 ID 漂移缺陷类(#811 Unicode 折叠、#550 同名文件碰撞、#1033 AST 与 LLM 文件节点不匹配等)。该模块的normalize_id实现揭示了规范化配方为何如此讲究(graphify/ids.py):

def normalize_id(s: str) -> str: cur = s for _ in range(6): nxt = unicodedata.normalize("NFKC", cur.casefold()) if nxt == cur: break cur = nxt cur = re.sub(r"[^\w]+", "_", cur, flags=re.UNICODE) cur = re.sub(r"_+", "_", cur) return cur.strip("_")
  • casefold再 NFKC,且循环迭代到不动点(上限 6 轮):casefold 可能把字符展开为基字母加组合记号(如İi+ U+0307),单次NFKC(casefold(...))对某些组合记号序列无法达到无大小写稳定态(#2614 的土耳其语标识符缺陷即源于此);
  • 到不动点后才执行[^\w]+_过滤、折叠连续下划线、去首尾下划线,保证幂等(normalize_id(normalize_id(s)) == normalize_id(s))、结果只含\w_、且对输入预折叠与否都收敛到同一结果。

make_id(*parts)(graphify/ids.py)把各部分以_连接后过normalize_id,产出与构建器从拼接串算出的 ID 完全相同。从源码结构看,这份规范的 ID 格式要求("每级目录都要用、ID 必须确定性")与ids.py的配方是同一约定的两端表述:规范约束 LLM 侧的"生产",ids.py保证机器侧的"验证与调和"。

四、source_file逐字规则与 build_merge 的替换匹配

规范末尾的source_file RULE解释了逐字(VERBATIM)要求的工程动机:

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的"替换"逻辑能精确命中而非产生重复。

这一机制在 graphify/build.py 的build_merge中得到印证:被重新抽取的文件会按层(tier)替换其既有贡献——每个source_file出现在新 chunk 中时,图中该文件在对应层(AST 层与语义层由_is_ast_tier区分)的旧节点/旧边先被丢弃,再并入新结果。分层替换的动机是:同一文件有两个生产方(确定性 AST 通道与 LLM 语义通道),二者的节点集在图中并存,只重抽一层时绝不能误删另一层的成果(docstring 注明曾有语义 chunk 误删该文件 AST 标题的缺陷,#2333/#2336)。匹配时同时比较原始形式与_norm_source_file归一化形式,以覆盖"新 chunk 携带 Windows 绝对路径、而存量图保存 POSIX 相对路径"的情形(#1007)。由此形成闭环:LLM 端逐字照抄 FILE_LIST 路径是机器端精确替换的前提,任何 basename 化或重相对化都会让替换失配、旧节点永久残留或重复累积。

五、JSON 输出契约:schema 逐字段解读

规范最后给出完整的输出 schema,要求"恰好输出这段 JSON,不得有任何其他文本":

{ "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 }

字段契约的要点:

  • nodesid遵循前述确定性命名;label是人类可读名;file_type限定为六值枚举;source_url/captured_at/author/contributor四个可空字段承接 frontmatter 透传规则;source_location用于标注源文件内的具体位置。
  • edgesrelation为八值枚举——callsimplementsreferencescitesconceptually_related_toshares_data_withsemantically_similar_torationale_for;每条边必须带confidence(三值)与confidence_score(按第二节的量规取离散档);weight默认 1.0。
  • hyperedgesnodes至少三个成员(与"3+ 节点共享概念"规则呼应);relationparticipate_in/implement/formid用 snake_case;注意超边的 confidence 只有 EXTRACTED 与 INFERRED 两档——规范未给 AMBIGUOUS 超边留位置。
  • input_tokens / output_tokens:占位为 0。按 skill-codex.md 的 Step B3,真实 token 数由调度方从 Agent 工具结果的usage字段读回、写回 chunk JSON 后再合并,最终随各 chunk 求和进入.graphify_semantic_new.json

六、缓存归属:为什么 SPEC_PATH 要传给缓存

规范本身不直接谈缓存,但它的分发路径与语义缓存深度绑定。skill-codex.md 在 Step B0/B3 中要求把references/extraction-spec.md的绝对路径作为SPEC_PATH传给缓存读写(save_semantic_cache(..., prompt_file='SPEC_PATH')),动机在原文注释中写得很清楚:缓存条目"归属于"产出它的那份提示词——当 graphify 升级改动了这份提示词,由旧提示词产出的缓存条目会被重新抽取而非直接回放;提示词未变则缓存继续有效(#1939)。换句话说,这份规范文件的内容哈希事实上参与了缓存失效判定:修改规范中的任何一条规则(哪怕只是措辞),都会触发受影响文档的语义重抽取。这也解释了规范为何如此紧凑且自包含——它是被逐字转发、被整体寻址的"提示词契约"。

七、小结:规范、AST 与构建器之间的三方契约

把本文各节收拢,extraction-spec 实际上是三条契约的交汇点:

  1. ID 契约:规范中的{stem}_{entity}格式 ↔ graphify/ids.py 的make_id/normalize_id(casefold+NFKC 不动点归一),保证 AST 节点与 LLM 节点对同一实体给出同一个 ID;
  2. 路径契约source_file逐字规则 ↔ graphify/build.py 中build_merge的分层替换与剪枝匹配,保证--update增量更新时替换精确命中、旧层不残留、另一层不误删;
  3. 输出契约:严格 JSON schema + 离散置信度刻度 +file_type六值枚举,保证下游合并、校验与阈值过滤无需为 LLM 的自由发挥做防御性解析。

对维护者而言,这份规范与 skill-codex.md 的 Part B 流程、graphify/cache.py 的缓存归属、graphify/build.py 的合并替换构成一条完整链路:改规范前先理解这三方契约中任何一侧的不变量,是安全修改该提示词的前提。

【免费下载链接】graphifyTurn any codebase, with its docs, SQL schemas, configs, and PDFs, into a queryable knowledge graph. A /graphify skill for Claude Code, Cursor, Codex, and Gemini CLI: local deterministic AST parsing, every edge explained, no vector store.项目地址: https://gitcode.com/GitHub_Trending/graph/graphify

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询