前几年做知识库、做 AI 问答,RAG 几乎是标配。甚至 1 年前的王者 Cursor,当时选型也是如此。但从去年 Claude Code 开始,在代码搜索这件事上,几乎没人把 RAG 当主力了。 RAG 死了么?
这篇文章会从技术角度拆解这个问题。内容来自我在做的 Zero2Agent 系列(一个从 0 到 1 带你深入 Coding Agent 的教学项目),这是其中关于代码搜索模块的部分。
搜索能力肯定是必要的
给 Agent 一个读文件查目录的能力,它并不会高效理解代码。
在真实仓库里,问题不是"能不能打开文件",而是"怎么先缩小搜索空间,再精读"。
否则在小仓库里勉强能跑,但项目一大就崩。Agent 要么把整个文件从头读到尾吞噬 Token,要么靠瞎猜路径疯狂试错。
真实的工程师从不会通读整个项目。他们的习惯是:先搜索关键词,再打开相关文件精读。
所以给 Agent 做代码理解,除了"读",另一个非常重要的是"搜"。
Coding Agent 的主流搜索方案
如果按照前几年的经验,很多人直觉上会觉得,Agent 应该优先用语义搜索(RAG)。毕竟听起来更高级。但在本地代码开发场景,并非如此。
目前大家熟悉的一些 Coding Agent 产品:
没有人在核心链路上用纯 RAG。即使是 Cursor 和 Windsurf 这两个同时支持语义搜索的产品,Grep 仍然是主链路,RAG 只是辅助。
为什么不是纯 RAG:三层原因
第一层:代码搜索是封闭域问题
代码和自然语言有区别。
在自然语言的知识库里,同一个概念表达方式各异——"用户"可以是 user、customer、member、account。语义召回在这种场景下有价值,因为你需要把不同表达映射到同一个概念。
但代码是高度结构化的规范体系。函数名、变量名、路径、调用链本身就有极高的信息密度。一个项目里,fetchUserData 就是 fetchUserData,不会有人写成 grabCustomerInfo 然后期望被语义召回。
加上大模型对主流代码写法已经有很强的先验认知,它见过足够多的代码,知道常见的命名模式和文件组织方式。这意味着文本硬匹配在代码领域的天花板出奇的高。
代码仓库大多不超过几万文件,百万行代码。按我在百万行代码的仓库里工作的经验,通过一些简单的技巧,搜索仍然有不错的召回率与准确性。
同时,相比搜索域更广的网络搜索,本地代码库的搜索,是一个收敛的封闭域,不是开放的网络搜索。
第二层:RAG 的系统账太重
RAG 不只是多算几个 Token。它是一条包含分片、向量化、数据库存储、多路召回重排的重资产链路,这还只是最基础的链路。至少你需要处理:
- 分片策略:代码怎么切?按函数?按文件?按固定 Token 数?每种切法都有信息损失
- 向量化模型:通用 embedding 模型对代码的理解质量参差不齐
- 向量数据库:存储、索引、查询,这是一套独立的基础设施
- 召回重排:多路召回后还要做相关性排序
- 实时更新:代码仓库高频变动,向量索引怎么保持同步?
在企业级代码库高频变动,分支众多的场景下,维持向量索引实时更新的成本并不低。Cursor 团队专门写过文章介绍他们在这块的优化,这不是一个简单的问题。
相比之下,传统的 grep 配合本地优化,能给出极具性价比的体验。ripgrep 本身就是优秀的工具,在大多数场景下已经足够快。
第三层:Agent 需要的是可控性
这一点很关键,可控性相当于把搜索白盒化给了 Agent,否则搜索策略将被隐藏在 RAG 链路中,把当前"聪明"的大模型屏蔽在了外面。
Agent 调 grep 搜 fetchUserData,如果没有结果,它立刻知道是关键词问题——可能拼错了、可能应该搜 getUserData、可能应该换个思路从调用方入手。它可以马上换策略重试。
但 RAG 是黑盒。
如果向量匹配偏轨,返回了一堆"语义相关"但实际不相关的代码,Agent 无从排错。更糟糕的是,它很可能会认真阅读这些代码,试图从中找到线索,结果被这些"貌似相关"的噪音污染整个推理上下文。
对 Agent 来说,最怕的不是"搜不到",而是"返回一堆看起来相关、但无法验证的噪音"。
搜不到,Agent 知道要换策略。但拿到一堆似是而非的结果,Agent 也许会困惑、会在错误的方向上越陷越深。
在封闭的代码域内,确定性与可迭代性,超过了模糊的"语义智能"。
行业还在尝试继续优化 grep
grep 虽然胜出了,但不代表问题已经完美解决。
在我开发的真实的 Coding Agent 产品数据中,搜索工具占据了 15-20% 的调用比例,是除读写文件外最核心的基础能力。这意味着搜索的性能和准确性会直接影响 Agent 的整体效率。
ripgrep 在大仓里会遇到性能上限——动辄 15 秒以上的扫描时间。对于一个需要实时交互的 Agent 来说,每次搜索等十几秒是不可接受的。Agent 一个任务可能要搜索几十次,累积下来就是几分钟的纯等待。
Cursor 的索引优化思路
Cursor 最近披露了他们的优化思路:为文本搜索建立倒排索引 (Inverted Index)。
首先,用多长的字符片段(N-grams)作为索引键?
// 二元组 (Bigram) 灾难
[A,B] -> [List: 10万个文件]
键很少,但 posting list 庞大到无法计算
// 四元组 (Quadgram) 灾难
[A,B,C,D] -> [List: 2个文件]
列表虽小,但键的数量爆炸(数十亿),内存撑爆
经典的折中方案是 Trigram(三元组)——提取所有重叠的 3 字符序列建索引。Google 的 CodeSearch 和 Zoekt 都是基于此。
但 Cursor 认为 Trigram 依然不够好。每个 trigram 中的字符在相邻的 trigram 中大量重复。如果在查询时拆少了,候选文件依然很多;拆多了,从索引加载几十上百个列表的开销,甚至会比直接从头扫描文件更慢。
Sparse N-grams:ClickHouse 和 GitHub 的解法
Cursor 参考了 ClickHouse 和 GitHub 新版 Code Search 的方案:稀疏 N-grams (Sparse N-grams)。
它不再机械地每 3 个字符切一刀。算法会为文档中的每一对字符分配一个确定性的"权重"。只有当子串两端的权重大于内部包含的所有权重时,它才会被提取为一个 token。
权重是基于真实开源代码的频率表生成的,极其罕见的字符对权重最高。
查询时,算法根本不需要全量匹配。它只需要抽出搜索词中最冷门、区分度最高的边界 token 去查倒排索引。只需极少的几次索引探测,就能将候选文件范围从几万个精准坍缩到个位数。剩下的,再交给传统的正则引擎在内存中做验证。
为什么必须本地化
这套索引数据为什么不能放云端?语义索引放在服务端还算可以接受。代码稍微改动,在向量空间中的位移并不大,短暂的不一致影响有限。
但正则搜索必须保持良好的新鲜度。Agent 搜索很频繁,它对 Grep 的理解就是"精准"搜索。如果网络往返产生几百毫秒的延迟同步差,就会很容易导致 Agent 搜不到自己刚写的代码,进而累计产生严重的幻觉。
所以 Cursor 选择使用本地内存来存储索引信息。为了不吃爆本地编辑器的内存,他们将索引分成两个文件落盘:一个存储连续的 Posting list,另一个极度紧凑的哈希表记录偏移量。
在编辑器进程中,只对这张寻址表执行 mmap。查询时直接二分查找拿到偏移,再精准从磁盘对应扇区抽回数据。配合监听本地 Git 状态做增量合并,实现了 Chromium 级别大仓中近乎瞬时且无感知占用的体验。
这说明行业没有停在 grep,而是在继续做本地索引优化。 这条路还没走完。
另外三条值得关注的方向
除了底层索引优化,还有三条路线在被应用。
路线 A:用架构隔离搜索污染
与其死磕索引性能,不如从组织结构上动手。
主 Agent 自己多轮调用 grep 试错,极易导致主线推理崩溃——上下文被大量试错过程填满。Agent 读了一堆搜索结果,然后发现都不对,再搜,再读,再发现不对...几轮下来,有效信息被淹没在搜索噪音里。
一种实践是:将探索彻底剥离给 Sub Agent。
子节点在隔离的沙盒上下文中,执行高度并行的探索、尝试、排查、沿调用链顺藤摸瓜。主进程在外面等待,子进程只把筛洗完毕的、纯粹干净的结论(相关路径、片段精要)合回主线。
用「空间」上的隔离,换取主干推理上下文的纯净。
Claude Code 已经在用这种模式。Sub Agent 的上下文隔离,其实不只用在搜索场景,它是一个通用的架构模式。
路线 B:专职搜代码的小模型
Cognition(Devin 团队)分析数据发现,Agent 居然有超过 60% 的时间在浪费在"找代码"上。
RAG 不靠谱,让上千亿参数的大模型去做搜代码这种苦力又太慢太贵。他们的解法是:用 RL 专门训练了一个专职搜代码的小模型——SWE-grep。
一触发,它就会多路并发探测,最多 4 轮锁定。在 Reward 设定上,刻意且严重地偏袒"查准率"——宁可漏掉线索,也不容许带回无关代码造成主脑的幻觉。
这是一个有意思的方向:不是让大模型做所有事,而是让专职小模型做专职的事。
路线 C:基于语法树的结构化搜索
ast-grep(GitHub 13k+ Stars)代表的是一条全新的维度:不搜文本,改搜结构。
传统 grep 的问题是:如果你的 Agent 想找 fetchUserData,它会把注释里、Markdown 文档里、甚至报错字符串常量里的同名字符全部兜回来。Agent 花了海量 Token 读了一堆毫无执行价值的垃圾。
ast-grep 借助 tree-sitter 这个库,在搜索发生前,就已经把源码解析为抽象语法树 (AST)。你用的不再是反人类的 Regex 猜边界,而是直接写一段带占位符的合法代码模式:
# 查找并匹配任意变量的 console 调用
sg -p "console.log($ARG)"
它能凭借 AST 的能力,只精准命中逻辑上的函数调用,而不是所有包含这个字符串的地方。顺带附赠的代码重写(Rewrite)也能依靠同一套节点安全替换。Aider、CodeRabbit 在采用。
但这条路线也有未解决的问题:跨语言一致性(不同语言的 AST 结构差异很大)、并发稳定性等。它目前更像一种在特定问题上有效方案,还不是"成熟的标准答案"。
所以,RAG 死了吗?
回到标题的问题。在本地代码搜索的核心链路上,RAG 确实没有成为主流。行业用脚投票,选择了 grep 系方案。原因前面已经说清楚了:封闭域、系统成本、可控性。
但说"RAG 死了"并不准确。有两个维度值得展开:
规模量级的边界
本文讨论的场景是本地开发,单个代码仓库——几千到几万文件,百万行代码量级。在这个规模下,grep 的确够用。
但问题会随规模突变。如果搜索范围从"一个仓库"扩展到"所有 GitHub 仓库"呢?或者千万、亿级别的文件呢?grep 遇到的问题和现在完全不同,甚至可能根本无法直接使用。
在广域搜索场景下,语义检索、倒排索引、分布式架构等方案会重新变得必要。这不是 RAG 的失败,而是问题本身变了。
结构化程度的影响
grep 能在代码搜索里表现好,背后有一个前提:代码本身就是高度结构化的信息。
这里说的"结构化",不是指 JSON、YAML 这种格式化的数据结构,而是指语义本身的结构化。代码有规范的命名、清晰的模块边界、确定的调用关系。一个精心维护的知识库也是如此——文档之间的行文结构、概念的组织方式、思路的递进关联,虽然不直接体现为某种格式化的数据形态,但内容本身是高度结构化的。
根据我的实践,大模型 + grep 在这类"语义结构化"的信息上效果都不错。真正困难的是那些"语义非结构化"的场景:零散的笔记、格式混乱的文档、口语化的记录、缺乏组织的知识碎片。在这些场景下,文本硬匹配的天花板会急剧下降。
也许需要预处理、后处理,也许需要查询时实时增强。这些路径,和 RAG 的思路并不矛盾。
结论
更准确的说法是:在 Coding Agent 的本地代码搜索场景,RAG 从"默认选项"变成了"可选辅助"。
这是一个特定场景下的结论,不是普适判断。
grep 赢在代码的结构化特性,赢在本地仓库的规模边界,赢在 Agent 对可控性的需求。换一个场景,结论可能完全不同。
搜索决定了 Agent 能看到什么,看到的质量决定了推理的上限。这个模块的演进,还远没有结束。
这篇文章来自我在做的 Zero2Agent 系列。一门带你零基础搞定 AI Agent 技术细节的实践课程。
目前市面上的学习资源大致分两类:
一类是入门课程——讲概念、跑 demo、搭个最简原型。能帮你建立初步认知,但很难回答"真实产品里为什么这样设计"。
另一类是开源代码——你可以从 Claude Code、Codex CLI、Gemini CLI 等项目学习。但直接读源码的问题是:你能看到"怎么做",却很难看到"为什么这样做"。
这些学完很可能连面试都不够。Zero2Agent 想填的是中间这层:从零开始,边构建边拆解一个产品级 Coding Agent 的设计决策。 即使你之前不了解这个领域,跟着这条线也能看到整件事的全貌和演进脉络。
👉 github.com/alienzhou/z… 还在持续更新中,如果感兴趣可以去看看。