本文介绍我开发的 igraph ------ 一个面向 AI 编程助手的代码知识图谱工具。和市面上已有的 CodeGraph 不同,igraph 在代码结构图的基础上加了语义摘要和向量检索,让 Agent 能用自然语言找代码。
体验地址
从一个真实痛点说起
我日常用 Claude Code 写业务代码,有个反复出现的场景:
我说:"帮我看看用户下单这块的逻辑"
然后 Claude Code 开始疯狂 grep、find、一个文件一个文件地 cat......翻了十几个文件之后终于找到了 createOrder 函数,再顺着调用链往下看。整个过程花了 30 多次工具调用,消耗了大量 token。
问题很明显:Agent 不认识你的代码仓库。每次都是盲搜,效率极低。
后来我用了 CodeGraph,情况有改善------Agent 可以通过图谱直接查符号、展开调用链,不用一个个文件翻了。但用了一段时间,发现它有个根本性的限制:
CodeGraph 是个结构查询工具,不是语义检索工具。
它要求你先知道函数名叫什么,然后帮你展开结构。但很多时候你只知道业务含义("处理退款的逻辑"),不知道代码里的命名(processRefund、handleRefundRequest、还是 refund?)。
这就是我决定自己做 igraph 的原因。
igraph 的核心思路
一句话概括:在代码结构图的基础上加语义层,让 Agent 能用自然语言搜代码。
整个系统分两条线:
markdown
离线建库(一次性):
源码 → Tree-sitter 解析 → 提取符号和调用关系 → 存入 SQLite
→ LLM 为每个函数生成中文摘要 → Embedding 向量化
在线查询(毫秒级):
自然语言问题 → 向量化 → 文件粗筛 → 双通道精排 → RRF 融合 → 图谱展开
建库阶段把脏活累活都干了(解析、调 LLM、调 Embedding),查询阶段纯本地 SQLite 操作,零网络延迟。
和 CodeGraph 的区别在哪
先说清楚,两个项目底层技术有不少重合------都用 Tree-sitter 解析 AST、SQLite 存储、MCP 协议对接 AI 助手。但设计思路差别很大。
CodeGraph :建一张精确的代码结构图 → Agent 按图索骥 igraph:建图 + 语义摘要 + 向量化 → 支持自然语言检索
用一个例子说明:
arduino
场景:我想找"处理用户退款"的代码
CodeGraph 的用法:
→ 你得先猜函数名可能叫 refund / processRefund / handleRefund
→ 调用 codegraph_node 查找
→ 找到后展开调用链
igraph 的用法:
→ 直接输入"处理用户退款的逻辑"
→ igraph_explore 返回最相关的函数(带摘要、源码、调用链)
→ 同时关联相关的 PRD 需求和数据库表
更详细的对比:
| 维度 | CodeGraph | igraph |
|---|---|---|
| 检索方式 | 按符号名精确查找 | 向量语义 + 全文关键词双通道,RRF 融合排序 |
| 语义理解 | 无,纯 AST 结构 | LLM 为每个符号生成摘要,向量化后支持语义搜索 |
| 多模态 | 不支持 | 可挂载 PRD 文档和 DB Schema,需求和代码自动关联 |
| 降级策略 | 依赖运行环境 | 无 LLM → 启发式摘要;无 Embedding → 纯全文检索 |
| 增量更新 | 文件监听实时同步 | sha256 diff + 50% 阈值摘要复用 |
| 支持语言 | 7 种(含 C/C++/Rust/C#) | 5 种(TS/JS/Python/Java/Go) |
各有优劣,CodeGraph 胜在实时同步和语言覆盖面,igraph 胜在语义检索和多模态。
几个关键技术决策的思考
1. 为什么要双通道检索
最开始我只做了向量检索,很快发现一个问题:用户搜 fetchUserList 这种精确标识符时,向量检索的效果反而不如全文搜索。
原因也简单------向量模型是按语义编码的,fetchUserList 和 getUserData 在向量空间里可能很近,但用户要的就是那个精确的名字。
所以最终做了两个通道并行:
- Dense 通道 (向量 KNN):擅长语义匹配,"获取用户列表" 能匹配到
fetchUserList - FTS5 通道 (trigram 全文):擅长关键词匹配,搜
fetchUser直接精确命中
两个通道的结果用 RRF(Reciprocal Rank Fusion) 融合:
scss
rrfScore(d) = Σ w_c / (k + rank_c(d))
核心思想是只看排名不看分数。因为向量距离和 BM25 分数量纲完全不同,直接加没有意义。RRF 把它们统一到排名维度上做融合,简单有效。
2. FTS5 为什么选 trigram 分词器
这个选型踩了坑。一开始用的 SQLite FTS5 默认的 unicode61 分词器,发现中文搜索基本废了------它把连续汉字当成一个巨大的 token,搜"用户"匹配不到"获取用户列表"。
换成 trigram(长度为 3 的滑动窗口)之后,任意 ≥3 字符的子串都能匹配。不管是中文"用户列表"还是英文标识符里的 Trade,都能搜到。
代价是 <3 字符的查询词 trigram 搞不定(没法生成 token),我们用 LIKE 子串扫描兜底。
3. LLM 摘要的批处理 + 断点续跑
一个大仓库可能有几千个文件,逐个调 LLM 太慢了。我们的做法是一次 API 调用处理一个文件的所有符号------一个有 20 个函数的文件只需要 1 次调用而不是 20 次。
更重要的是断点续跑。每个文件在数据库里有状态字段(pending / done / error),每次启动只处理 pending 的。中途 Ctrl+C 了、网络断了、API 限流了,下次跑直接从断点继续,已完成的不会重跑。
SIGINT 信号还做了优雅处理------收到 Ctrl+C 后不立即退出,等当前在途的请求完成再退出,避免写了一半的脏数据。
4. 50% 阈值的增量摘要复用
增量更新时,一个文件改了 1 个函数,需要把整个文件的所有摘要都重新生成吗?
我们的策略是:对比每个节点的源码是否变化,如果 ≥50% 的节点没变,就只对变化的节点重跑 LLM,未变化的直接复用旧摘要。
效果很直观:一个 20 函数的文件改了 1 个函数,只需要 1 次 LLM 调用(而不是整个文件重来的 1 次批量调用),节省 95% 的 API 成本。
5. 多模态资源挂载
这是我觉得和 CodeGraph 差异化最大的一个功能。
实际开发中,理解代码不光要看代码本身,还要知道"这段代码对应哪个产品需求"、"操作的是哪张数据库表"。igraph 可以把 PRD 文档(Markdown / PDF / DOCX)和 DB Schema(SQL / JSON / XLSX)挂载进来。
挂载过程:
- 解析文档 → 按章节/表维度切片
- 每个切片向量化
- 用切片向量和已有的文件向量做 KNN 匹配
- 相似度 ≥ 0.85 建 strong 关联,0.70~0.85 建 weak 关联
这样 Agent 查到一个函数时,能同时看到"这个函数实现的是 PRD 第 3.2 节的需求"、"它操作的是 orders 表"。
6. 优雅降级
不是所有人都有 LLM API 和 Embedding 服务的。igraph 的原则是:不管什么环境都能用,只是效果有差异。
完整模式:LLM 摘要 + 向量检索 + 全文检索 + 多模态关联
↓ 没有 LLM
降级 1:启发式摘要(函数签名拼接)+ 向量检索 + 全文检索
↓ 没有 Embedding
降级 2:启发式摘要 + 纯全文检索
↓ 什么都没有
最低保障:纯 AST 结构图 + FTS5 关键词搜索
igraph build --no-llm 在完全离线环境下也能跑通,只是摘要质量差一些。
存储方案:为什么全塞进 SQLite
这个问题估计很多人会好奇。图数据为什么不用 Neo4j?向量为什么不用 Milvus?全文搜索为什么不用 Elasticsearch?
答案是场景决定选型:
igraph 是一个本地开发者工具,核心诉求是零运维、开箱即用。一个 .igraph/igraph.db 文件搞定所有事情------关系数据、全文索引、向量索引全在里面。
- 图遍历 :用递归 CTE 做 N 跳展开,
GROUP BY id + MIN(depth)天然去环。几万个节点、几跳展开,SQLite 毫秒级完成。 - 全文搜索:FTS5 是 SQLite 内置模块,trigram 分词器覆盖中英文。
- 向量检索:sqlite-vec 是个 SQLite 扩展,加载进来就有 vec0 虚拟表,支持 KNN。
如果哪天要做成在线服务,这些组件可以逐个替换。但对于当前的本地工具场景,引入任何一个外部服务都是过度设计。
目前的不足
实事求是地说几个短板:
- 首次建库慢:要跑 LLM + Embedding,比 CodeGraph 纯 AST 解析慢很多。大仓库可能要几十分钟甚至更长。
- 语言覆盖少:目前只支持 TS/JS/Python/Java/Go 五种,CodeGraph 支持更多(包括 C/C++/Rust/C#)。
- 没有实时 watch :改了代码要手动跑
igraph build触发增量更新,不如 CodeGraph 的文件监听自动同步方便。 - Embedding 模型耦合:换了 Embedding 模型需要重新向量化所有数据,因为不同模型的向量空间不兼容。
后续计划做的事情:
- 实时 watch 模式(文件监听 + 防抖 + 增量)
- PRD 图片识别(目前只提取文本内容)
- 更多语言适配器
技术栈概览
| 层次 | 选型 | 理由 |
|---|---|---|
| 语言 | TypeScript (ESM) | 前端/全栈生态 |
| 解析 | Tree-sitter (N-API) | 多语言统一 API |
| 存储 | better-sqlite3 + WAL | 同步 API、单文件 |
| 全文 | FTS5 (trigram) | 内置、中英文子串匹配 |
| 向量 | sqlite-vec (vec0) | 零额外进程 |
| LLM | OpenAI 兼容 API(裸 fetch) | 不依赖 SDK |
| Embedding | BGE-M3 via TEI,1024 维 | 开源可自建、多语言 |
| MCP | 官方 SDK,stdio 传输 | Claude Code / Cursor 直接对接 |
| CLI | Commander.js | 轻量声明式 |
写在最后
igraph 和 CodeGraph 不是替代关系,更像是不同侧重点的两个方案。如果你的主要需求是结构导航(知道函数名、看调用链),CodeGraph 的实时同步和语言覆盖更好。如果你更需要语义搜索、想让 Agent 通过自然语言理解代码、还想关联 PRD 和数据库,igraph 可能更合适。
欢迎交流技术细节。