Graphify 源码深潜:从 AST 构图到 LLM 任务的上下文装配
一个 coding agent 回答“登录链路怎么走”,最笨的办法是搜索 auth,再把搜到的文件一份份塞进上下文。这个办法在十几个文件时还能工作,到了几万甚至几十万行代码就会同时碰到三个问题:
- 搜索能找到同名文本,却不知道谁调用谁。
- 上下文能装很多代码,却不等于模型知道哪些代码真正相关。
- 每次任务都从头搜索,前一次理解无法摊薄下一次任务的成本。
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 是文件、类、函数、文档概念等节点,E 是 calls、imports、inherits、references 等关系。这里真正重要的不是数学符号,而是检索单位发生了变化:普通 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_file和source_location,让结果可以回到原文件和行号。 - 抽取
contains、imports、inherits、implements等直接关系。 - 暂存还不能在单文件内确定目标的
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() 因此是一个两阶段过程:
- 并行完成每个文件的结构抽取。
- 汇总所有定义、导入和类型信息,再解析跨文件边。
当前源码并不是看到同名函数就直接连线。它加入了多层保守约束:
- 有导入证据时优先沿导入路径解析。
- 同名候选不唯一时宁愿不连,也不随便选一个。
- JS/TS 的跨模块调用需要导入证据。
- 不允许一个 Python 调用因为同名而连到 Kotlin 或 TypeScript 定义。
- 对
this.field.method()、带类型参数、局部new绑定等模式,先恢复接收者类型再找方法。
这反映了 graphify 的一个重要取舍:对代码关系,错误的边往往比缺失的边更危险。 一条假的 calls 边会污染影响分析、最短路径、社区划分和高连接核心节点排名;缺一条边至少还能在读源码时被发现。
2.3 EXTRACTED、INFERRED、AMBIGUOUS 不是装饰
每条边都带来源置信标签:
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 才是图质量的第二道防线
抽取器返回 nodes 和 edges 并不等于可以直接写进图。LLM 可能产生轻微漂移的 ID,不同目录可能存在同名文件,旧缓存可能还使用旧版 ID 规则,外部依赖又可能只有边没有本地图节点。
build_from_json 做了大量看似琐碎、实际上决定图是否可信的清理:
- 统一旧版
links/edges、source/source_file等 schema。 - 根据
source_file重新生成语义节点 ID,减少 LLM 命名漂移。 - 合并同一 Markdown 文件的快速扫描节点与语义节点。
- 当不同 ID 的非 AST 占位节点能唯一匹配某个 AST 定义时,把关系重连到 AST 规范节点。
- 如果 AST 与语义节点原本就是同一 ID,后写入的语义属性仍可能覆盖 AST 属性。
- 对同名候选保持歧义,不按哈希迭代顺序随机选赢家。
- 丢弃端点不存在的悬空边,包括未解析的标准库、外部依赖和普通本地引用。
- 阻止
calls、imports、references因同名跨语言误连。 - 以稳定顺序写边,减少相同输入在不同进程里的无意义 diff。
默认情况下它创建的是无向 NetworkX Graph,以兼容旧行为;原始方向在内存图里暂存为边的 _src、_tgt 属性,导出时会恢复成 JSON link 的 source、target。要创建 DiGraph,库调用需使用 build_from_json(..., directed=True);Agent 的 Skill 流程也定义了 /graphify <path> --directed,但当前提交的 headless graphify extract CLI 本身没有 --directed 选项。这带来一个必须知道的边界:
- 邻域检索很适合默认无向图,因为“与 X 相关的东西”通常不关心进入还是离开。
- 严格的调用方向、影响方向和同一节点对上的多条平行关系,不能只靠默认邻域遍历理解。
graphify path与 MCPshortest_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 更稳:
- 分词:去掉英语疑问词和填充词;中文可做分词;标点不参与匹配。
- 候选预过滤:trigram(三字符片段)倒排表先排除不可能命中的节点,但不改变最终评分。
- 节点评分:完整短语、精确、前缀、子串、源文件命中分层计分,并用 IDF(逆文档频率)降低
error、handler这类高频词的权重。 - 种子选择:优先高分节点、限制同名标签占满槽位,同时保证多词问题的每个有效词至少有一个种子。
- 图遍历:BFS(广度优先)用于附近的广泛上下文,DFS(深度优先)用于纵向展开;真正查询两点连接要用
path,且默认无向图仍需核对边方向。非种子的超高连接枢纽节点不继续向外扩展,避免一个公共工具节点把整张图拖进来。
遍历结果严格说并不是完整“诱导子图”:_bfs/_dfs 返回访问到的节点,以及遍历过程中记录的发现边;节点之间其他替代边或回边可能不在结果里。最后 _subgraph_to_text 才把节点、文件、行号、社区、边关系和置信标签渲染成文本。
这里还有一个容易被“token budget”名称掩盖的实现事实:预算不限制图遍历成本,只在渲染阶段用 token_budget * 3 的字符数近似截断文本;而且输出顺序是先全部节点、再输出边。因此预算较小时,可能已经遍历了很多节点,最终文本却一条边也容不下。它是上下文输出阀门,不是查询计算量上限,也不是真实 tokenizer 的精确计数。
这就是 graphify 最核心的检索思想:
词法匹配只负责找到少量可信入口;真正的召回由显式关系向外扩展完成。
向量 RAG 可能因为语义相似找到另一段鉴权文档,graphify 则会从 DigestAuth 沿 uses、calls、imports 找到它真实连接的 Request、Response 和调用者。两者解决的是不同问题。
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.14 的 hook-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_server 把 graph.json 的查询能力暴露成 MCP 工具,包括:
query_graphget_nodeget_neighborsget_communitygod_nodesgraph_statsshortest_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.json 的 directed 标志加载,默认构图通常是无向的,且 graphify query 使用固定深度 2;MCP 加载器则无条件按有向图创建,query_graph 默认深度 3,neighbors() 只沿出边扩展。同一句问题经两个入口可能返回不同邻域。团队如果把 CLI 与 MCP 混用,应把方向和深度显式纳入验证,而不能假设二者只是传输协议不同。
7.5 可选反馈环:让成功与失败成为下次检索信号
save-result 可以把一次问答标记为 useful、dead_end 或 corrected,reflect 再把这些记录汇总成 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
查询先把 DigestAuth 与 Response 选成种子,再扩展到 Request、Client、Auth、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 不完全一样:
- 代码主干边来自 AST 和解析规则,不是先让 LLM 猜关系。
- 核心
query是 IDF/字符串评分加 BFS/DFS,不是 vector top-k。 - 社区来自已有边的拓扑密度,不要求 embedding。
- 文档语义边和 LLM 社区命名是可选增强,不是代码图成立的前提。
仓库的 BENCHMARKS.md 提到本地向量编码器、稠密向量 RAG 和混合排序等配置,那是统一基准框架中的比较系统或内存检索适配器,不应反推为核心 graphify query 内部使用向量数据库。
我更愿意把它定义为:
面向代码与混合工程资料的结构化检索层,可以作为 RAG 的一种替代,也可以与向量检索组成混合检索。
10. 基准数字该怎样读
项目自己的基准报告称,在约 100 万行的 ERPNext 上,固定 coding agent 加一个 graphify 工具后,六道问题的关键事实覆盖率从 70.8% 提升到 82.0%;代码图构建可以是零 LLM 调用额度。这个结果支持“结构工具可能改善大型仓库问答”的方向,但不能直接推导成“所有项目都提升 11.2 个百分点”。
需要同时看到四个限定:
- 代码智能问题只有
n=6,样本量很小。 - 基准框架与被测系统来自同一项目,虽然公开可复现,仍应等待独立复验。
- 结果依赖问题类型;字面量定位可能仍是 grep 更快。
- “零 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
如果要在团队里落地,我会采用下面的顺序:
- 先只建 code-only 图。 验证语言覆盖、节点数量、调用边和查询质量,不急着把所有文档送去语义抽取。
- 项目级安装 Skill 与指令。 让规则随仓库版本化,而不是只存在某台机器的全局配置里。
- 坚持 graph-first,source-confirmed。 先用图定位社区、符号和路径,再读少量源码确认细节;不要把图当作最终真相。
- 修改后做增量更新。 把图的新鲜度视为测试和类型检查之外的派生资产维护。
- 重复查询多时再上 MCP。 单人偶尔查询用 CLI 已足够;IDE、多 Agent 或团队共享时,MCP/HTTP 才能体现复用价值。
- 文档语义抽取按风险开启。 ADR、RFC、论文和架构图价值高时再启用,并明确 backend、预算、缓存与隐私边界。
- 保留 grep、测试与运行时工具。 图适合回答“关系是什么”,grep 适合回答“这个字面量在哪”,测试和 trace 适合回答“运行时实际发生了什么”。
结论:Graphify 真正改变的是上下文装配方式
看完源码后,我认为 graphify 最值得借鉴的不是“知识图谱”这个名词,而是四类职责分离:
- 让解析器负责事实。 AST、导入、类型和跨文件解析提供可追溯的结构骨架。
- 让可选语义抽取器负责非结构化资料。 只有文档、图片等需要时,才让构图期 LLM 补充语义图碎片。
- 让图查询负责检索。 从少量词法种子沿真实关系扩展,再按预算渲染上下文。
- 让宿主 Agent 负责歧义与表达。 它把自然语言映射到项目词汇,调用 CLI 或 MCP,并根据查询证据组织最终答案。
这比“把更多文件塞给更大的模型”更像一个工程系统:模型能力仍然重要,但它不再承担索引、查找、来源追踪和结构约束的全部责任。
如果读完后只记住一句话,可以记这句:
Graphify 不是替 LLM 理解整个仓库,而是把仓库预编译成一份可查询、可裁剪、可追溯的上下文,让 LLM 每次只理解当前任务真正需要的部分。