假设你是一个代码 Agent,刚被丢进一个陌生的仓库。你接到的任务是:修复“Chrome 上登录成功后偶尔跳回登录页”的 bug。你会怎么做?

我最早以为,先爬一遍文件树,找到所有和认证相关的文件,然后读一遍,逻辑自然就清楚了。可真当我尝试把这种思路教给程序时,发现根本行不通——几十万文件,读不完。一个 5000 文件、平均 300 行的仓库,就有 150 万行代码。按 token 估算,一行代码大约 10~20 token,全部读完接近 2000 万 token。就算给模型塞一个 200K 的上下文,也要来回装 100 次。
所以代码 Agent 在这个问题上的真正关键,不是“读”,而是“导航”。它必须像一个老开发一样,能判断出:哪几个文件最可能藏着这个 bug。这篇文章拆解一下主流的导航策略,以及各自的代价和边界。
文件树索引:最直觉但最低效的策略
最朴素的方案是让 Agent 自己用 ls、cd、cat 去探索。这就像是给一个新同事一台终端,让他自己翻项目。
问题在于,文件树只告诉你文件在哪,不告诉你代码在做什么。Agent 每打开一个文件,就要消耗几千 token,几次下来上下文就满了。而且它会反复犯同一个错误:看到名字相似的文件就打开,结果发现根本不是要找的地方。
当然,文件树并不是一无是处。它能建立一种“目录感”,让 Agent 对项目结构有个粗印象。但对真实大型仓库,纯文件树遍历一定是死路。
符号索引:把仓库变成一张地图
IDE 能瞬间跳到函数定义,是因为代码被解析成了符号表。代码 Agent 也应该这样做。
Aider 的 repo map 就是代表性做法。它用 tree-sitter 把源码解析成一棵语法树,然后提取类名、函数名、变量名和它们之间的引用关系,生成一个“代码地图”。这个地图不是逐字的代码,而是符号级别的摘要。Aider 会按当前任务动态选择地图中相关度最高的部分,再丢给 LLM。
说人话就是:你不必把整本小说给 AI,只需要给它一张带注释的人物关系图。它知道该去哪一章找哪个人物。
但语法树的符号索引对静态语言很友好,对 Python、JavaScript 这些动态语言就有点力不从心。monkey patch、运行时动态注册的函数、通过装饰器注入的依赖,在静态解析中统统看不到。
依赖图:顺着调用链找线索
符号地图告诉你“哪里有类”,但不知道“改了它会波及谁”。要评估影响面,得靠依赖图。
从入口函数开始,沿着 import 和 function call 从上往下追,或者反向从出错的地方往上追,Agent 就能圈出一批候选文件。这种策略特别适合重构和修复级联故障。
不过依赖图也有一个致命缺点:静态分析出来的“依赖”不一定是真实运行时的依赖。比如一个库里 import 了某个模块,但实际调用只发生在很少的情况下。如果 Agent 沿着这些虚假路径走,就会在无关代码里打转。
语义搜索:用语言找代码,而不是用代码找代码
第三种思路是让用户直接用自然语言查询代码。把代码切片变成 embedding 向量,再用向量相似度搜索。这就是语义搜索,Cursor、Sourcegraph 都在做类似的事情。
Sourcegraph 的 Code Search 已经支持很多复杂的搜索语法,但语义搜索仍然是一个很重要的入口。它的好处是显而易见的:你不需要记住准确的函数名,甚至不知道某个功能在哪一层。
但我用了很久之后发现,语义搜索有一个隐藏陷阱:它匹配的是文本语义,不是行为语义。你搜“session expired”,它能返回包含“expired”的代码,但它不会告诉你这行代码是否真的触发 session 过期逻辑。我试过一个真实例子:在一个电商项目里搜“优惠券过期”,返回的结果里有几段关于“session 过期”的代码,因为两者的 embedding 很接近。模型能看出它们“语言上相似”,但看不出它们一个属于营销域,一个属于认证域。
| 策略 | 定位精度 | token 成本 | 最擅长 | 最大盲区 |
|---|---|---|---|---|
| 文件树遍历 | 低 | 高 | 小仓库快速浏览 | 深层依赖和命名混淆 |
| 符号索引 | 中 | 中 | 快速建立全局地图 | 动态语言特性 |
| 依赖图 | 中高 | 中 | 追踪调用链、评估改动影响 | 死代码和运行时绑定 |
| 语义搜索 | 低到中 | 低 | 从语义找相关代码 | 因果推理和精确判断 |
我踩过的坑:把整个仓库塞进上下文
有一段时间我天真地认为,既然上下文窗口有 200K,为什么不做全仓库索引然后把相关文件全喂进去?我做了个实验,把项目里引用最多的 50 个文件全部拼接,加上任务描述,塞给模型。
结果模型表现得很奇怪:它确实看到了所有代码,但好像“哪都看了,哪都没看懂”。它对一个无关紧要的配置文件的关注度,和对核心逻辑的关注度差不多。这让我意识到:上下文越长,注意力越容易被稀释。模型不会自动区分哪个文件重要。
后来我在 Aider 的文档里看到一句话:repo map 的关键不是把所有代码都塞进上下文,而是把代码“压缩”到模型能消费的规模。这个思路和我的直觉完全相反,但它是对的。
现代 Agent 的混合导航策略
真正落地的代码 Agent,几乎不会只用一种策略。它们会在不同阶段切换。
一个典型的导航流程是这样的:
- 先用 tree-sitter 生成符号索引,按任务过滤出候选文件。
- 再用依赖图延展,找到和这些候选文件有调用关系的模块。
- 然后对候选列表做一次语义查询,补充遗漏的代码片段。
- 最后打开真正相关的 3~5 个文件,仔细阅读核心逻辑。
普林斯顿的 SWE-agent 实验也说明了导航设计的重要性。他们发现,Agent 使用的工具界面对表现的影响比底层模型还大。SWE-agent 没有给模型一个普通终端,而是设计了一个受控的 interface,专门用于浏览文件、搜索符号、定位代码。这种克制让 Agent 的行为更高效。
还有一点值得注意:几乎所有导航策略都假设代码库本身是“可读”的。如果命名混乱、模块职责不清、注释错误,那么符号索引会给出虚假的路标,语义搜索会召回无关代码,依赖图会画出错误的路径。工具再好,也救不了烂代码。
所以我的判断是,代码 Agent 的导航能力,短期内的突破口不是更大的上下文窗口,而是更好的“路标”——更细粒度的符号索引、更准确的调用关系、以及能够理解“修改意图”的语义模型。真正让 Agent 变得可靠的,不是它记住了多少代码,而是它知道该忽略哪些代码。
原创文章,作者:guanweilu,如若转载,请注明出处:https://guanweilu.cn/article/752.html