Graphify 源码深潜:从 AST 构图到 LLM 任务的上下文装配

从语法树构图、跨文件消歧和图遍历查询,到 Skill、AGENTS.md、CLI 与 MCP 接入,讲清 Graphify 如何在构图期与任务运行期分配 LLM 职责。

Graphify 源码深潜:从 AST 构图到 LLM 任务的上下文装配

一个 coding agent 回答“登录链路怎么走”,最笨的办法是搜索 auth,再把搜到的文件一份份塞进上下文。这个办法在十几个文件时还能工作,到了几万甚至几十万行代码就会同时碰到三个问题:

  1. 搜索能找到同名文本,却不知道谁调用谁。
  2. 上下文能装很多代码,却不等于模型知道哪些代码真正相关。
  3. 每次任务都从头搜索,前一次理解无法摊薄下一次任务的成本。

graphify 的答案不是“给 LLM 更大的上下文”,而是先把代码库变成一张可查询的结构图,任务发生时只取回与问题有关的子图。

我把这种架构称为 context compiler(上下文编译器)

源文件 --预先编译并持续更新--> 知识图 graph.json
用户问题 --词法定位与图遍历--> 相关节点及遍历过程中发现的边
查询结果 --按预算渲染并注入上下文--> LLM 推理与回答

这篇基于 graphify v8 分支的 v0.9.14、提交 94d3099 做源码解读。重点不是安装命令,而是四个问题:

  • 文件怎样变成节点与关系?
  • 自然语言问题怎样变成一个小子图?
  • Skill、指令文件、Hook 和 MCP 怎样把子图接入 LLM 任务?
  • 它与全文搜索、向量 RAG 和通常所说的 GraphRAG 到底有什么不同?

先给出最重要的结论:graphify 的核心代码查询不是向量搜索,核心代码构图也不依赖 LLM。LLM 被放在它擅长的位置——理解模糊意图、补充文档语义、综合最终答案;确定性的 AST、图遍历和来源追踪则交给程序。

这里的 AST(Abstract Syntax Tree,抽象语法树)是解析器从源码语法中得到的结构;MCP(Model Context Protocol,模型上下文协议)则是把外部能力暴露给 Agent 调用的一套工具协议。本文默认读者了解基本的 LLM 工具调用概念,不要求图算法背景。

先把三个容易混在一起的处理阶段拆开:

阶段LLM 做什么是否必需
索引构图可选把文档、论文、图片等非结构化资料转成图碎片纯代码构图不需要
graphify query不调用 LLM;只做词法评分、选种子、图遍历和预算渲染不需要
LLM 任务运行时宿主 Agent(如 Codex、Claude)理解问题、选择 CLI/MCP 工具,并基于返回证据作答Agent 任务本身需要

也就是说,宿主 Agent 是 graphify 的调用者和答案综合者,不是 graphify query 内部的查询引擎。

1. 不要把 Graphify 想成“另一个聊天机器人”

理解 graphify 最容易的方法,是把它拆成三个平面。

平面负责什么核心机制
索引平面把源文件变成节点和边解析语法、消解跨文件引用、构图并按连接关系分组
检索平面从问题定位种子节点并扩展子图关键词加权、近似词过滤、广度/深度优先遍历、输出预算
Agent 平面理解用户意图、调用工具、组织答案操作说明、持久项目指令、命令行与标准化工具接口

其中图可以粗略写成 G = (V, E)V 是文件、类、函数、文档概念等节点,Ecallsimportsinheritsreferences 等关系。这里真正重要的不是数学符号,而是检索单位发生了变化:普通 RAG(Retrieval-Augmented Generation,检索增强生成)找“相似文本块”,graphify 找“相关实体及其邻居”。

完整数据流如下:

flowchart LR
    A[代码 / SQL / 文档 / 图片] --> B[文件检测与分类]
    B --> C[Tree-sitter 结构抽取]
    B --> D[可选的 LLM 语义抽取]
    C --> E[统一节点、边与多节点关系]
    D --> E
    E --> F[规范化、去重、跨文件解析]
    F --> G[NetworkX 图]
    G --> H[Leiden 社区与分析]
    H --> I[graph.json]

    Q[用户问题] --> J[Agent 从图词表做受约束扩展]
    I --> K[IDF 排序 + 种子选择 + BFS/DFS]
    J --> K
    K --> L[相关节点与遍历发现边]
    L --> M[LLM 基于证据回答]

这张图也说明了一个常见误解:graphify 并没有把模型“训练得更懂代码”,更没有修改模型参数。它改变的是 模型在本次任务开始推理前能看到什么证据

先看一个量级感:我对仓库自带的 6 个 httpx 样例文件执行 --code-only,得到 193 个节点、423 条边和 6 个社区,全程 0 次 LLM 调用;其中 DigestAuth --uses [INFERRED]--> Response 是一跳连接。这里的 CLI 查询本身同样没有调用模型;只有宿主 Agent 在查询前做意图到图词汇的映射,并在查询后组织答案。第 8 节会给出完整复现实验。

2. 索引第一步:Tree-sitter 把语法树翻译成图

graphify 的整体流水线在仓库的架构文档里概括为:

detect -> extract -> build -> cluster -> analyze -> report -> export

2.1 单文件抽取先保留“确定事实”

大多数受支持的编程语言首先由 Tree-sitter 解析;XAML、项目 XML、Svelte 模板回退路径、manifest 和数据库/包管理器 introspection 等容器或配置格式则使用 XML、正则或专用解析器。通用抽取引擎在 _extract_generic 中完成这几件事:

  • 为文件、类、接口、函数、方法等定义创建稳定节点。
  • 保存 source_filesource_location,让结果可以回到原文件和行号。
  • 抽取 containsimportsinheritsimplements 等直接关系。
  • 暂存还不能在单文件内确定目标的 raw_calls
  • 保存参数、局部变量和接收者类型等解析线索,供第二遍跨文件解析使用。

可以把一个极简文件想成下面这张图:

auth.py
  └─contains─> DigestAuth
                 └─contains─> auth_flow()

auth_flow() ──uses / calls──> Request / Response

与只按文本切 chunk 相比,AST 抽取保留了两件对 coding agent 非常关键的东西:符号边界关系方向。同样出现 request 这个词,在注释、变量名、函数参数和真正的调用表达式中,含义并不一样;语法树能先把它们区分开。

2.2 为什么还需要第二遍跨文件解析

单个文件通常只知道“这里调用了 save()”,却不知道它最终对应哪个文件里的哪个 save()。graphify 的 extract() 因此是一个两阶段过程:

  1. 并行完成每个文件的结构抽取。
  2. 汇总所有定义、导入和类型信息,再解析跨文件边。

当前源码并不是看到同名函数就直接连线。它加入了多层保守约束:

  • 有导入证据时优先沿导入路径解析。
  • 同名候选不唯一时宁愿不连,也不随便选一个。
  • JS/TS 的跨模块调用需要导入证据。
  • 不允许一个 Python 调用因为同名而连到 Kotlin 或 TypeScript 定义。
  • this.field.method()、带类型参数、局部 new 绑定等模式,先恢复接收者类型再找方法。

这反映了 graphify 的一个重要取舍:对代码关系,错误的边往往比缺失的边更危险。 一条假的 calls 边会污染影响分析、最短路径、社区划分和高连接核心节点排名;缺一条边至少还能在读源码时被发现。

2.3 EXTRACTEDINFERREDAMBIGUOUS 不是装饰

每条边都带来源置信标签:

  • EXTRACTED:源码里直接出现的结构事实,例如明确导入或直接调用。
  • INFERRED:程序或语义抽取器根据导入、命名、类型和上下文推断出的关系。
  • AMBIGUOUS:证据不足但值得保留给人检查的关系。

这不是严格校准过的统计概率,而是一套证据等级协议。它的价值在于让下游 Agent 可以说:“这条关系是源码直接给出的”,或者“这是解析器推断的,需要回到源文件确认”,而不是把所有边伪装成同样可靠。

3. LLM 参与构图,但只负责 AST 不擅长的部分

“graphify 是否使用 LLM”不能简单回答是或否,必须区分语料类型。

3.1 纯代码:正常路径不把代码交给语义模型

仓库的工作原理说明明确区分了结构抽取与语义抽取:大多数代码走本地结构解析;文档、论文、图片和转录文本才进入可选的语义阶段。当前 CLI 还提供 --code-only,用于跳过文档、论文和图片的语义 pass,因此不调用 LLM;如果另外显式启用 Cargo/Postgres 等 introspection,图中仍可能合入非 AST 的本地结构数据。

这意味着对一个只有代码的仓库,索引阶段可以做到:

代码不离开本机
不消耗 LLM API token
在相同依赖与配置下力求可复现

3.2 文档与图片:让 LLM 产出受约束的图碎片

AST 能看懂 import,却看不懂 ADR(Architecture Decision Record,架构决策记录)为什么放弃方案 A;也无法从架构图里直接得到“支付服务依赖风控规则”这种语义关系。因此文档、论文、图片和音视频转录可以进入 extract_files_direct 或 Agent 子任务流程。

提示词要求 LLM 不要自由写总结,而要返回固定 JSON;执行器随后宽容解析并做尽力校验,并非任一字段错误就让整次任务失败。这里的 hyperedges 指一次连接两个以上节点的“多节点关系”;普通二元关系仍放在 edges 中:

{
  "nodes": [{"id": "...", "label": "...", "source_file": "..."}],
  "edges": [{
    "source": "...",
    "target": "...",
    "relation": "references",
    "confidence": "INFERRED",
    "confidence_score": 0.75
  }],
  "hyperedges": []
}

extract_corpus_parallel 会按 token 预算和目录相近性切块,默认最多并行四个请求;输出截断时把块一分为二重试;合并时按提交顺序恢复确定性顺序;默认以 best-effort 方式在块完成后写语义缓存,避免长任务中断后全部重来,不过缓存可被环境变量关闭,写入失败也只会告警。

输入内容还会被包装成带文件路径与 SHA-256 的 <untrusted_source> 数据块。源码中的 _wrap_untrusted 明确要求模型把文件内容当作不可信数据而不是指令。这不能从理论上消灭 prompt injection,但至少把“仓库文档也是指令”变成了一个被显式处理的安全边界。

所以更准确的说法是:

graphify 用确定性解析器建立代码骨架,再让可选 LLM 为非结构化资料补语义边;LLM 输出会经过 best-effort(尽力而为)的规范化与校验。schema 问题多以告警呈现,非法节点或边会被跳过,找不到端点的悬空边会被丢弃,而不是发现任何问题就终止整次构图。

4. build_from_json 才是图质量的第二道防线

抽取器返回 nodesedges 并不等于可以直接写进图。LLM 可能产生轻微漂移的 ID,不同目录可能存在同名文件,旧缓存可能还使用旧版 ID 规则,外部依赖又可能只有边没有本地图节点。

build_from_json 做了大量看似琐碎、实际上决定图是否可信的清理:

  1. 统一旧版 links/edgessource/source_file 等 schema。
  2. 根据 source_file 重新生成语义节点 ID,减少 LLM 命名漂移。
  3. 合并同一 Markdown 文件的快速扫描节点与语义节点。
  4. 当不同 ID 的非 AST 占位节点能唯一匹配某个 AST 定义时,把关系重连到 AST 规范节点。
  5. 如果 AST 与语义节点原本就是同一 ID,后写入的语义属性仍可能覆盖 AST 属性。
  6. 对同名候选保持歧义,不按哈希迭代顺序随机选赢家。
  7. 丢弃端点不存在的悬空边,包括未解析的标准库、外部依赖和普通本地引用。
  8. 阻止 callsimportsreferences 因同名跨语言误连。
  9. 以稳定顺序写边,减少相同输入在不同进程里的无意义 diff。

默认情况下它创建的是无向 NetworkX Graph,以兼容旧行为;原始方向在内存图里暂存为边的 _src_tgt 属性,导出时会恢复成 JSON link 的 sourcetarget。要创建 DiGraph,库调用需使用 build_from_json(..., directed=True);Agent 的 Skill 流程也定义了 /graphify <path> --directed,但当前提交的 headless graphify extract CLI 本身没有 --directed 选项。这带来一个必须知道的边界:

  • 邻域检索很适合默认无向图,因为“与 X 相关的东西”通常不关心进入还是离开。
  • 严格的调用方向、影响方向和同一节点对上的多条平行关系,不能只靠默认邻域遍历理解。
  • graphify path 与 MCP shortest_path 在默认无向视图上找到的是“连接路径”,不保证符合 caller→callee 的可执行方向;解释路径时仍要检查 _src_tgt
  • nx.Graph 对同一节点对只能保留一条边,项目因此专门提供 diagnose multigraph 检查潜在的关系折叠。

换句话说,graph.json 是面向 Agent 检索优化的知识表示,不是编译器级别的完美调用图。

5. Leiden 社区发现:从目录结构转向真实连接结构

代码目录是人为组织方式,真实架构边界却经常跨目录:一个鉴权子系统可能同时涉及 middleware、数据库模型、配置和前端路由。graphify 使用 Leiden/Louvain 类图划分算法,按节点之间的边密度发现社区。

当前 cluster() 不只是调用一次算法:

  • 有向图会先转成无向图再做社区发现。
  • 孤立节点单独成组。
  • 可以先排除连接数异常高的枢纽节点(hub),再按邻居多数票把它挂回最合适的社区。
  • 超过全图 25% 的大社区会继续拆分。
  • 规模足够大但内部内聚度(社区内部连接相对紧密的程度)太低的社区会做第二次拆分。
  • 最终按规模和成员名称稳定排序,让社区 ID 在相同依赖与配置下尽量保持可复现;是否安装 graspologic 会影响实际选择 Leiden 还是 Louvain,跨依赖版本不保证同样的分组。

社区划分本身不需要 embedding(向量表示)。代码边、文档引用边以及可选语义相似边已经提供了拓扑信号;命名社区才可以选择按中心节点生成标签,或使用 LLM 标签。

这一层的作用不是给目录换一个更漂亮的名字,而是给 Agent 一个中层索引:

整个仓库
  -> 支付处理社区
      -> PaymentService
          -> charge()

如果节点是“字”,社区就是“段落”。它让宏观架构问题不必一开始就在几万个符号上搜索。

6. 查询核心:不是 embedding,而是“词法定位 + 图扩展”

构图通常预先完成,并随代码变更增量更新;真正决定每次 LLM 任务上下文质量的是查询链路。

6.1 Agent 先做受约束的词汇翻译

graphify 的核心 CLI 不做同义词和跨语言语义匹配。项目给 Codex 等 Agent 的查询参考流程要求先从真实节点标签生成词表,再让 Agent 从词表中选择最多 12 个与用户意图相关的 token。注意:这是 Skill 约定的 Agent 操作协议,并不是 CLI 强制执行的输入校验;直接调用 graphify query 仍可传入任意文本。

例如用户问:

认证失败后是怎么刷新的?

图里可能只有:

auth, credential, token, refresh, guardian

按照这份 Skill 协议,LLM 被要求理解中文意图后只从真实词表选择已有词,不凭训练记忆发明图里不存在的搜索词。这个设计把能力分工得很干净:

  • LLM 负责模糊语义到项目词汇的映射。
  • 图查询负责确定性检索。
  • 选择过的词显式输出,扩展过程可以审计。

6.2 确定性查询分五步

_query_graph_text 的主链路非常短:

terms = query_terms(question)
scored = score_nodes(graph, terms)
seeds = pick_seeds(scored)
nodes, edges = bfs_or_dfs(graph, seeds, depth)
return render_subgraph(nodes, edges, token_budget)

细节决定它为什么比普通 substring search 更稳:

  1. 分词:去掉英语疑问词和填充词;中文可做分词;标点不参与匹配。
  2. 候选预过滤:trigram(三字符片段)倒排表先排除不可能命中的节点,但不改变最终评分。
  3. 节点评分:完整短语、精确、前缀、子串、源文件命中分层计分,并用 IDF(逆文档频率)降低 errorhandler 这类高频词的权重。
  4. 种子选择:优先高分节点、限制同名标签占满槽位,同时保证多词问题的每个有效词至少有一个种子。
  5. 图遍历:BFS(广度优先)用于附近的广泛上下文,DFS(深度优先)用于纵向展开;真正查询两点连接要用 path,且默认无向图仍需核对边方向。非种子的超高连接枢纽节点不继续向外扩展,避免一个公共工具节点把整张图拖进来。

遍历结果严格说并不是完整“诱导子图”:_bfs/_dfs 返回访问到的节点,以及遍历过程中记录的发现边;节点之间其他替代边或回边可能不在结果里。最后 _subgraph_to_text 才把节点、文件、行号、社区、边关系和置信标签渲染成文本。

这里还有一个容易被“token budget”名称掩盖的实现事实:预算不限制图遍历成本,只在渲染阶段用 token_budget * 3 的字符数近似截断文本;而且输出顺序是先全部节点、再输出边。因此预算较小时,可能已经遍历了很多节点,最终文本却一条边也容不下。它是上下文输出阀门,不是查询计算量上限,也不是真实 tokenizer 的精确计数。

这就是 graphify 最核心的检索思想:

词法匹配只负责找到少量可信入口;真正的召回由显式关系向外扩展完成。

向量 RAG 可能因为语义相似找到另一段鉴权文档,graphify 则会从 DigestAuth 沿 usescallsimports 找到它真实连接的 RequestResponse 和调用者。两者解决的是不同问题。

7. 它究竟怎样集成进 LLM 任务

把图建出来还不够。模型必须知道何时查、怎么查、查完怎样约束答案。graphify 提供四种接入机制,外加一个可选反馈环。

7.1 Skill:把一套流程变成 Agent 可调用能力

安装后,$graphify/graphify Skill 同时承担两类职责:一类是构图编排,包括检测文件、运行 AST 抽取、对文档分块、合并结果、聚类和保存清单;另一类是查询纪律,包括先读真实图词表、受约束地映射意图、先查图再回源码确认。

Skill 不是一个 Python API wrapper,而是一份面向 Agent 的操作协议。它把第 3 节的可选语义构图与第 6.1 节的查询前词汇映射连成完整工作流。尤其在 Codex 路径里,语义文件可以被切成多个 chunk,由多个子 Agent 并行返回图碎片;纯代码语料则跳过这一步。

7.2 持久指令与 Hook:改变 Agent 的工具选择顺序

_agents_install 会向 AGENTS.md 写入规则:代码库问题优先执行 graphify query/path/explain,得到方向后再读取具体源码;修改代码后运行增量更新。

Claude Code、CodeBuddy、Gemini 等支持的 Hook 还能在 Agent 即将 grep 或逐文件读取前注入提醒,但采用 fail-open(提醒失败也放行)策略,不阻止原工具执行。

这里有一个当前版本的源码细节值得单独指出:Codex 安装器确实会写 .codex/hooks.json,但 v0.9.14hook-check 因 Codex Desktop 不接受 PreToolUse.additionalContext 而直接安全退出。因此 Codex 当前真正持续生效的机制是 AGENTS.md 与 Skill,Hook 条目本身是 no-op(执行了但不产生效果)。这比 README 中“Hook 在 Bash 前提醒”的概括更精确。

7.3 CLI:最简单的上下文注入通道

Agent 可以直接运行:

graphify query "auth refresh token" --budget 2000
graphify path "DigestAuth" "Response"
graphify explain "PaymentService"

标准输出就是下一步推理的上下文。其优势是简单、可脚本化;缺点是 Agent 需要会选择命令并解析文本。

7.4 MCP:把图能力变成正式 Tool Calling

_build_servergraph.json 的查询能力暴露成 MCP 工具,包括:

  • query_graph
  • get_node
  • get_neighbors
  • get_community
  • god_nodes
  • graph_stats
  • shortest_path
  • PR 影响与分诊工具

这时数据流变成:

用户问题
  -> LLM 判断需要代码图工具
  -> MCP query_graph(question, depth, token_budget)
  -> Graphify 返回结构化子图文本
  -> 工具结果进入本轮模型上下文
  -> LLM 根据节点、边、文件和行号组织答案

MCP 并没有让图“进入模型内部”。它只是把检索能力放进模型的工具集合,并用 schema 约束调用参数。stdio 适合本机一项目一进程,Streamable HTTP 则可以让团队共享一个服务;服务会按 graph.json 的修改时间和大小热加载新图。

不过,v0.9.14 的 CLI 与 MCP 查询并非完全同义:CLI 按 graph.jsondirected 标志加载,默认构图通常是无向的,且 graphify query 使用固定深度 2;MCP 加载器则无条件按有向图创建,query_graph 默认深度 3,neighbors() 只沿出边扩展。同一句问题经两个入口可能返回不同邻域。团队如果把 CLI 与 MCP 混用,应把方向和深度显式纳入验证,而不能假设二者只是传输协议不同。

7.5 可选反馈环:让成功与失败成为下次检索信号

save-result 可以把一次问答标记为 usefuldead_endcorrectedreflect 再把这些记录汇总成 lessons 与 learning overlay。后续子图输出可以带 learning=preferred/tentative/contested 一类状态。

这仍然不是模型权重学习,而是外部工作记忆:系统保存“哪些节点上次有用、哪些路径是死路、哪个答案后来被纠正”,下一次 Agent 先读这些信号。

8. 一个最小实验:6 个文件怎样变成 193 个节点

我用仓库自带的 worked/httpx/raw 样例做了纯代码构图。使用已安装 CLI 时可以运行:

graphify extract worked/httpx/raw \
  --out ./demo-output \
  --code-only

本次运行得到:

6 code files
193 nodes
423 edges
6 communities
0 LLM calls

然后执行:

graphify path "DigestAuth" "Response" \
  --graph demo-output/graphify-out/graph.json

结果是下面这条一跳连接;在其他例子里,默认无向图的 path 仍需核对 link 的实际方向:

Shortest path (1 hops):
  DigestAuth --uses [INFERRED]--> Response

再用自然语言查询:

graphify query "what connects DigestAuth to Response?" \
  --graph demo-output/graphify-out/graph.json \
  --budget 600

查询先把 DigestAuthResponse 选成种子,再扩展到 RequestClientAuth、transport 方法等邻居。本次遍历得到 124 个节点;随后约 600 token 的渲染预算在列完节点前就耗尽,因此输出被截断且没有一条边进入文本。这恰好印证了前面的实现边界:预算控制最终上下文长度,不限制遍历规模,也不保证低预算结果能保留关系。

为了确认本文涉及的三条关键路径不是只看代码猜测,我还运行了:

pytest tests/test_serve.py tests/test_install.py tests/test_extract_cli.py -q

结果为 174 passed。这不能证明项目在所有语言和仓库上都正确,但至少覆盖了本文重点讨论的查询、安装集成和 headless 抽取行为。

9. Graphify、grep、向量 RAG 与 GraphRAG 的区别

方案基本检索单位最擅长主要盲区
grep / ripgrep字符串出现位置精确字面量、错误消息、配置值不理解跨文件关系
向量 RAG语义相似文本块同义表达、自然语言文档、模糊主题调用方向和影响链不天然可靠
graphify符号节点与显式关系调用链、依赖、架构、影响范围、来源追踪依赖解析器覆盖和图的新鲜度
常见 GraphRAG实体图 + 社区摘要 + 图检索大型文本语料的实体关系与全局主题构图成本、LLM 推断边的可靠性

graphify 可以被宽泛地归入 GraphRAG,但它和“先 embedding、再实体抽取、再社区摘要”的文本 GraphRAG 不完全一样:

  1. 代码主干边来自 AST 和解析规则,不是先让 LLM 猜关系。
  2. 核心 query 是 IDF/字符串评分加 BFS/DFS,不是 vector top-k。
  3. 社区来自已有边的拓扑密度,不要求 embedding。
  4. 文档语义边和 LLM 社区命名是可选增强,不是代码图成立的前提。

仓库的 BENCHMARKS.md 提到本地向量编码器、稠密向量 RAG 和混合排序等配置,那是统一基准框架中的比较系统或内存检索适配器,不应反推为核心 graphify query 内部使用向量数据库。

我更愿意把它定义为:

面向代码与混合工程资料的结构化检索层,可以作为 RAG 的一种替代,也可以与向量检索组成混合检索。

10. 基准数字该怎样读

项目自己的基准报告称,在约 100 万行的 ERPNext 上,固定 coding agent 加一个 graphify 工具后,六道问题的关键事实覆盖率从 70.8% 提升到 82.0%;代码图构建可以是零 LLM 调用额度。这个结果支持“结构工具可能改善大型仓库问答”的方向,但不能直接推导成“所有项目都提升 11.2 个百分点”。

需要同时看到四个限定:

  1. 代码智能问题只有 n=6,样本量很小。
  2. 基准框架与被测系统来自同一项目,虽然公开可复现,仍应等待独立复验。
  3. 结果依赖问题类型;字面量定位可能仍是 grep 更快。
  4. “零 LLM 调用额度”指 AST-only 构图;开启文档、图片语义抽取或 LLM 社区命名会产生模型调用。

项目早期的 token benchmark 还报告过:在 52 个混合文件和 5 个样例问题上,子图查询相对重读全部文件的启发式平均 token estimate 减少 71.5x。这里语料 token 用 words × 100 / 75 估算,子图 token 用 characters / 4 估算,并不是真实 tokenizer 计数或 API 账单。这个数字能说明“索引成本可以被重复查询摊薄”,但高度依赖语料规模和查询方式:只有 6 个文件时,项目自己也报告 token reduction 约等于 1x,价值更多来自结构清晰度。

真正稳健的结论不是某个倍数,而是:

一次构图成本
  可以被后续多次小子图查询摊薄;
仓库越大、重复问题越多,摊薄价值越明显。

11. 它会在哪些地方失败

一张图永远是源码的有损模型。使用 graphify 时至少要知道这些边界。

11.1 动态行为不会凭空变成静态事实

反射、依赖注入容器、运行时 monkey patch、动态 import、宏展开、代码生成和配置驱动路由,都可能让静态图缺边。解决这类问题最终仍要结合测试、日志和 runtime trace。

11.2 解析器优先准确率,而不是完整召回

同名候选歧义时不连边可以减少幻觉,却会漏掉真实调用。INFERRED 也只是证据等级,不是经过数据集校准的 85% 成功概率。

11.3 自然语言与项目词汇不一致时会漏检

核心查询没有 stemming(把单词还原或归并到词干)、同义词或跨语言 embedding。Agent 的受约束词表扩展能缓解问题,但前提是:

  • 图中确实存在相近词。
  • Agent 遵守 Skill 流程。
  • 扩展词没有把意图映射到错误社区。

11.4 高连接节点既是信息,也是噪声

公共 utils、全局 Config 或框架基类可能连接全仓库。查询会把连接数位于最高约 1% 的节点视作 p99 枢纽,并配合上下文过滤和输出预算控制扩展爆炸;但控制爆炸也可能切断一条真实的长路径。

11.5 图会过期

graphify update .、watch 与 git hook 能降低陈旧概率,不能保证每个环境都及时更新。任何影响范围结论都应带着当前 commit 或图生成时间理解。

11.6 可选语义抽取继承 LLM 的成本与不确定性

文档送到远程 backend 会产生隐私与费用问题;模型输出可能截断、漂移或误连。缓存、schema、置信度和清理器是在降低风险,不是正确性的数学证明。

12. 我会怎样把它放进真实 coding workflow

如果要在团队里落地,我会采用下面的顺序:

  1. 先只建 code-only 图。 验证语言覆盖、节点数量、调用边和查询质量,不急着把所有文档送去语义抽取。
  2. 项目级安装 Skill 与指令。 让规则随仓库版本化,而不是只存在某台机器的全局配置里。
  3. 坚持 graph-first,source-confirmed。 先用图定位社区、符号和路径,再读少量源码确认细节;不要把图当作最终真相。
  4. 修改后做增量更新。 把图的新鲜度视为测试和类型检查之外的派生资产维护。
  5. 重复查询多时再上 MCP。 单人偶尔查询用 CLI 已足够;IDE、多 Agent 或团队共享时,MCP/HTTP 才能体现复用价值。
  6. 文档语义抽取按风险开启。 ADR、RFC、论文和架构图价值高时再启用,并明确 backend、预算、缓存与隐私边界。
  7. 保留 grep、测试与运行时工具。 图适合回答“关系是什么”,grep 适合回答“这个字面量在哪”,测试和 trace 适合回答“运行时实际发生了什么”。

结论:Graphify 真正改变的是上下文装配方式

看完源码后,我认为 graphify 最值得借鉴的不是“知识图谱”这个名词,而是四类职责分离:

  1. 让解析器负责事实。 AST、导入、类型和跨文件解析提供可追溯的结构骨架。
  2. 让可选语义抽取器负责非结构化资料。 只有文档、图片等需要时,才让构图期 LLM 补充语义图碎片。
  3. 让图查询负责检索。 从少量词法种子沿真实关系扩展,再按预算渲染上下文。
  4. 让宿主 Agent 负责歧义与表达。 它把自然语言映射到项目词汇,调用 CLI 或 MCP,并根据查询证据组织最终答案。

这比“把更多文件塞给更大的模型”更像一个工程系统:模型能力仍然重要,但它不再承担索引、查找、来源追踪和结构约束的全部责任。

如果读完后只记住一句话,可以记这句:

Graphify 不是替 LLM 理解整个仓库,而是把仓库预编译成一份可查询、可裁剪、可追溯的上下文,让 LLM 每次只理解当前任务真正需要的部分。

源码索引