
给 Agent 一个 1200 个文件的仓库,让它修一个「下单时运费被算了两遍」的 bug。它的第一个动作通常是搜索 shipping,然后拿到四十个命中文件,逐个打开,读到第三十个的时候上下文已经满了,真正的调用链还藏在依赖注入的容器配置里。
这不是模型笨。搜索工具只认识字符串,而代码的语义长在调用关系上:谁实例化了谁,哪个事件被谁订阅,改了这行会连带崩掉哪几个下游。这些东西 grep 给不出来,embedding 检索也给不出来,因为向量相似度和依赖关系是两套东西。
最近一堆工具在抢这个位置。GitHub 趋势榜上,Graphify 一天涨 1173 星,把代码库连同文档、SQL schema、配置文件、PDF 一起建成可查询的图;colbymchenry/codegraph 涨 347 星,主打预索引加自动增量同步,Rust 内核,支持一大串 Agent 客户端;codebase-memory-mcp 走的是把代码、文档和 Agent 记忆接在一起的路子。再往前看,aider 早在 2023 年就把这套东西叫 repo map,Serena 用 LSP 做符号级的导航和重构,伊利诺伊大学与腾讯 AI 实验室合作的 RepoGraph 则把它做成了可复现的学术原型。
grep 的天花板在哪
grep 的能力边界很清楚:你给一个字符串,它给一批行号和上下文。它对「文本」很诚实,对「结构」一无所知。
写死名字的调用它能找到,比如 calculate_shipping(order)。但现实代码里大量的关系是间接的:
- 依赖注入容器把实现类注册成接口,运行时才决定注入哪一份
- 事件总线里
order.created被三个服务订阅,订阅关系写在配置或装饰器里 - 动态语言的方法派发,Rails 的
method_missing、Python 的元类在运行时生成方法 - 跨语言的生成代码,proto 生成的服务桩、ORM 生成的数据模型
这几类里,grep 命中的往往只是定义处,看不到调用者。Agent 于是很自信地改了一个「只有一处引用」的函数,上线后崩在三个服务上。
现在主流 Agent 用的那套「按需搜索」,也就是 glob 列目录加 grep 加按行读文件,并没有那么不堪。它最大的优势是永远读的是硬盘上最新的代码,不存在索引过期的问题。在一天几百次提交的大仓里,这个优势足以抵消结构信息的损失。图谱派和搜索派的分歧,本质上是「结构准」和「数据新」的取舍。
图谱是怎么造出来的
把代码变成图,无非是把「符号」当节点,把「关系」当边。区别在于用什么手段抽取关系,这决定了图的精度上限。
树级解析(tree-sitter):把源码解析成 AST,再用查询语句抽出定义和引用。aider 的做法是给每种语言配一份 .scm 查询文件,抓 @name.definition 和 @name.reference 两类捕获,然后以文件为节点建图,边表示「A 文件引用了 B 文件定义的符号」。这套做法的好处是不用装编译环境,pip 装个 wheel 就能支持几十种语言;坏处是它只懂语法,不懂类型,同名符号在多个文件里定义时它分不清该连哪一条边。
语言服务器(LSP):借 IDE 那套基础设施,符号解析走编译器的语义分析。Serena 走的就是这条路,它把自己定位成「给 Agent 用的 IDE」,工具接口按符号组织,而不是按行号组织。查引用、跨文件重命名、找实现类,这类操作在 LSP 下是精确的。代价是要为每种语言起一个 language server,配置麻烦,启动慢,在一些语言上还很不稳。
预索引加增量同步:先把整个仓库建成一张持久化的图,再挂上文件监听或 git hook,改动发生时只重算受影响的子图。codegraph 的卖点是这个,它宣称能跟踪 grep 跟不到的动态派发跳转,并且直接回答「改这里的影响半径有多大」。工程上这是最像产品的一条路,因为 Agent 每次查询都是毫秒级,不用现场解析。
学术侧的代表是 RepoGraph,它把粒度压到「代码行」:每个节点是一行代码,边表示定义与引用之间的依赖。查询时从目标行出发取一个 ego-graph(邻域子图)塞进 prompt,而不是把整个仓库结构倒进去。作者把这套东西插到四种已有的 SWE 方法上,报告说在 SWE-bench 上全线提升,成为当时开源框架的最好成绩,在 CrossCodeEval 上也能用。
一张图能省下多少 token
代码图谱常被理解成让 Agent 看到更多代码。实际算下来,它省的是阅读量。
算笔账。假设仓库 1200 个文件,每个文件 220 行,平均每行 38 个字符,总计约一千万字符,按代码大致 3.2 字符一个 token 估算,全量读入超过三百万 token,任何模型的窗口都装不下。一次 grep 返回 30 行上下文,大约 350 token,搜八次接近 3k;读六个文件大约 16k,读三十个文件接近 80k。而 aider 的 repo map 默认预算只有 1000 token,占全仓库的万分之三。
用万分之三的预算拿到全仓库的骨架,这是图谱真正的账。
aider 的具体做法值得抄。它先按引用关系给文件建有向图,用 PageRank 式的传播算重要性:被很多重要文件引用的符号排名高,正在被编辑的文件作为个性化起点,把相关性往它附近拉。排名完成后,用二分搜索找「能塞进 token 预算的最大符号集合」,渲染时只保留定义行和父级作用域签名,中间全部用省略号压掉。文件少的时候地图自动变宽,文件多的时候自动收窄。
这套机制解决的是一个很具体的工程矛盾:上下文窗口是硬的,仓库是软的。你没法把仓库塞进窗口,只能决定塞哪一段。图谱的作用就是替 Agent 做这个决定,让它把预算花在真正要改的那几个函数上,而不是花在搜索结果的噪音里。
图谱负责指路,源码负责提供事实。Agent 先拿图确定「改这里,要连带看这五个文件的两个函数」,再去读那两段源码。图不能替代读代码,它替代的是「猜该读哪里」这个过程。
什么时候这张图会坑你
第一,索引过期比没有索引更危险。
没有索引的时候,Agent 会去搜,搜到的是当下的代码,最多慢一点。索引过期的时候,它拿到的是一个确定但错误的答案,而且看起来特别可信。想象一张三天前建的图告诉它「这个接口只有一个实现类」,昨天新增的那个实现类就永远进不了它的视野。
判断一个代码图谱工具好坏,先看它的新鲜度机制:有没有内容哈希或 mtime 失效,有没有 git hook,查询结果里带不带 commit sha。不带 sha 的图,等于没有版本概念。
第二,元编程是 AST 的死角。
Rails 的 method_missing、Python 的元类、装饰器注册的路由、Spring 的注解扫描,这些结构在源码里根本不存在对应节点。AST 只能看到 def 和 class,看不到运行时被合成出来的方法。同理,字符串拼出来的动态导入、反射调用的类名,静态分析一律抓不到。
这不是工具做得不好,是静态分析的原理限制。在重度元编程的代码库里,图谱的覆盖率会掉得很难看,这时候不如老实退回搜索。
第三,小仓库不该上。
二十个文件的仓库,全读也就几万 token,读到的是完整实现细节,比只给签名和结构有用得多。给这种仓库建索引,纯属给自己增加一个需要维护、需要担心过期的中间层。
第四,多语言边界的断点。
前端调后端靠 HTTP,后端调服务靠 gRPC,类型定义在 proto 或 OpenAPI 里。这些跨语言的边在单语言解析器眼里是断的。有些工具会做框架感知的路由映射(比如把 Next.js 的 route 文件映射成 API 端点),但覆盖率参差不齐,别默认它一定有。
第五,构建成本被低估。
全仓首次索引在大仓库上动辄几十分钟,CI 里怎么缓存、缓存怎么失效、多人协作时索引放本地还是共享,这些运维问题没人愿意谈,但它们决定了这东西能不能长期活下去。
三条路怎么选
| 方式 | 机制 | 什么时候合适 |
|---|---|---|
| 结构索引(图谱、repo map) | 预计算符号与依赖,按相关性裁剪 | 仓库大、结构稳定、改动集中在少数模块 |
| 按需搜索(glob + grep + read) | 直接查活文件系统 | 提交频繁、代码刚改完、需要绝对新鲜 |
| 向量检索 | 语义相似度召回代码块 | 自然语言提问,比如「哪里处理退款」 |
现实里这三条不该是单选题。我的建议是先量一下自己的仓库:随便挑十个真实的历史任务,统计 Agent 平均读了多少文件、调了多少次搜索、定位准确率多少。如果平均要读十五个以上文件才能找到入口,索引的收益就很大;如果三五个文件就搞定,把时间花在别的地方。
如果要上,从小处开始。挑改动最密集的两三个模块建符号图,跑二十个已知答案的历史 bug 做评测,图上定位的准确率和 token 消耗都记下来。跑通了再扩到全仓。一上来全仓建图,索引新鲜度维护不住,最后会变成一个没人敢删的僵尸服务。
这波工具潮说明了什么
这四五个仓库同时冒头,技术上并没有什么新突破。repo map、LSP、代码图这些手段都用了好几年,aider 2023 年就在跑。真正变的是供给方式:MCP 把「给 Agent 提供上下文」变成了一层可以插拔的服务,工具不用再绑定某个 IDE 或某个 Agent,装一个 MCP server 谁都能用。
这带来一个不太乐观的推论:这一层最终会被吞噬。上下文供给是 Agent 的基础能力,跟文件读写、终端执行同级,主流的编码 Agent 迟早会内置一个够用的版本。今天这些独立工具的窗口期,取决于它们能不能在「内置版够用」之前,把价值挪到更靠近业务的地方。
往哪挪?codegraph 的路线图给了个方向:从「查代码结构」走到「这个 PR 该测哪些用例、可能破坏哪些业务流程、业务逻辑有没有被改坏」。这类问题需要的不只是语法图,还要路由表、接口契约、测试覆盖、埋点这些业务语义。谁把这些接进去,谁就不容易被内置功能替代。
对独立开发者来说,同样的逻辑成立:一份 SKILL.md 加一个 tree-sitter 解析脚本就能做出个能用的代码图工具,门槛低得可怕。真正难的是把它接到具体业务上,比如只针对某个行业的 ERP 或者某个框架的插件生态做深度索引。通用能力会被平台拿走,垂直的语义不会。
最后
代码图谱是一张地图,源码是地面。地图的价值在于让你少走错路,不在于替代你走路。地图最关键的性质是新鲜度,一张三个月没更新的地图,比没有地图更容易让人迷路。
下次让 Agent 动一个陌生仓库之前,先问一句:它是靠搜,还是靠图?如果是靠搜,它大概会打开比你预想多三倍的文件;如果是靠图,问它一句「这张图是哪个 commit 的」。
参考来源: