news 2026/4/22 18:53:24

从Claude Code源码到行业实践,Grep回归背后,RAG真的已死?

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
从Claude Code源码到行业实践,Grep回归背后,RAG真的已死?

这一年,“RAG已死”的说法在技术圈掀起了不小的波澜。从《长上下文窗口、Agent 崛起,RAG 已死?》到《The RAG Obituary: Killed by Agents》,一篇篇文章似乎都在宣告这个曾经风靡AI圈的技术范式走向终结。而最具冲击力的信号,来自Claude Code、Codex这类新一代Agent CLI工具,它们纷纷放弃了RAG核心的embedding技术,官方直言不讳:不建索引、不用向量库,靠LLM驱动Grep就足够应对需求。

RAG真的不适合现在的Agent时代了吗?这个问题萦绕在每一个AI技术从业者的心头。为了找到答案,我们深入调研了行业内的主流解决方案,更对Claude Code的源码进行了细致拆解,试图从技术实现、性能原理、行业对比三个维度,揭开Grep回归的真相,也回答RAG在Agent时代是否还有一席之地这一核心问题。

毕竟,Grep作为一个诞生于1973年的Unix文本搜索工具,看似是被时代淘汰的“老古董”,如今却能在AI Agent领域“重出江湖”,背后必然有其不可替代的优势。而Claude Code作为放弃RAG、拥抱Grep的标杆产品,其源码中藏着的设计逻辑,正是我们读懂这一行业趋势的关键。

一、打破认知:Claude Code为何果断抛弃RAG,选择Grep?

Claude Code的创建者Boris Cherny,在多个公开场合都提到过一个让很多技术人意外的事实:Claude Code不用RAG,不用embedding,不建任何索引。它的核心搜索能力,完全依赖于LLM驱动的Grep,简单来说,就是让大模型自主决定搜索关键词,再用Grep工具在文件中逐行匹配,找到需要的内容。

在X/Twitter上,Boris说得十分直接:“Early versions of Claude Code used RAG + a local vector db, but we found pretty quickly that agentic search generally works better.” 翻译成中文就是,早期版本的Claude Code确实用了RAG加本地向量数据库,但他们很快发现,智能体式的搜索(agentic search)通常效果更好。

而在Pragmatic Engineer的采访中,他更是给出了更绝对的结论:“Plain glob and grep, driven by the model, beat everything.” 意思是,由模型驱动的简单glob和grep,击败了所有其他搜索方式。Anthropic官方的Context Engineering博客也确认了这一架构:Claude Code正是通过Grep和Glob,将代码动态加载到上下文(context)中,完成搜索任务。

这个选择并非拍脑袋决定。Boris在采访中提到,他在Meta工作时,曾观察到一个有趣的现象:Instagram的工程师在IDE的“点击跳转至定义”功能崩溃后,所有人都不约而同地回退到了手动Grep搜索代码。这让他意识到,Grep在代码搜索场景中,本身就具备极强的实用性。不过他也坦诚,放弃RAG的决策,有一部分是基于直觉的判断。

尽管Anthropic反复强调不用RAG、只用agentic search + Grep,但关于Grep具体怎么调用、LLM如何决定搜索关键词、工具调用的循环逻辑是什么,这些关键的实现细节一直没有公开。直到2026年3月,Claude Code CLI的一份源码快照因泄露被公开,我们才得以一窥其背后的技术真相。

我仔细研究了这份泄露的源码,发现确实如Boris所说,源码中没有任何与embedding、vector、similarity search(相似度搜索)相关的实现。但更让人感兴趣的是,这套“零索引”的内容搜索机制,具体是如何运转的,它没有预建的索引,没有复杂的向量计算,却能实现高效的代码搜索,这背后的设计哲学,正是我们需要深入拆解的核心。

为了让大家更直观地理解这套机制,我们不妨设定一个贯穿全文的实战场景:假设你正在阅读Claude Code的源码,突然产生一个疑问:当LLM调用GrepTool进行搜索时,Claude Code的bridge远程控制系统,是如何追踪和记录这次工具调用的?当你把这个问题抛给Claude Code,它会如何通过多轮Grep循环,找到答案?接下来,我们就从这个场景出发,一步步拆解Claude Code的搜索机制。

二、深度拆解:Claude Code的LLM驱动多轮Grep循环机制

Claude Code的代码搜索逻辑,可以用一句话概括:LLM自己决定搜什么、用什么工具搜、搜到后要不要继续搜,直到获取的信息足够生成准确回答为止。整个过程没有预设的搜索流程,没有固定的工具调用顺序,一切都由LLM在运行时根据上下文自主判断。

这一章,我们先拆解这个多轮搜索循环的核心逻辑,看看循环中用到的关键工具,再深入分析最核心的GrepTool如何控制返回信息量,最后结合开篇的实战场景,完整走一遍多轮搜索的全过程,让大家直观感受这套机制的运作方式。

2.1 多轮搜索循环:LLM主导的“自主探索”

整套搜索机制的核心,是一个不断迭代的循环流程,具体逻辑如下:首先,将用户的提问和所有可用的工具列表,一起传给LLM;LLM根据用户需求和工具特性,返回要么是直接的文本回答,要么是工具调用请求。如果是工具调用请求,系统会执行该工具,将工具的返回结果追加到对话历史中,然后带着更新后的完整对话历史,再次调用LLM。

LLM会基于不断增长的上下文,判断下一步该做什么,是继续调用工具补充信息,还是直接生成回答。这个循环会一直持续,直到LLM认为信息已经足够,不再调用工具、直接生成文本回答,循环才会自然结束。同时,循环也有强制退出机制:当达到最大搜索轮次上限、超出预算限制、用户主动中断,或者工具调用被权限拒绝时,循环会立即终止。

值得注意的是,这个循环对所有工具都是平等的。LLM可以在任何时候调用任何工具,甚至在一次响应中同时调用多个工具,没有硬编码的“必须先搜索再读取”的固定流程。这一点,与传统RAG的“一次性检索、固定流程”形成了鲜明对比。

在代码搜索场景中,与搜索相关的核心工具有四个,每个工具都有明确的底层实现和作用,具体如下:

工具底层实现作用
GrepToolripgrep (rg)通过正则表达式,搜索文件内容
GlobToolglob模式匹配按文件名、路径模式,查找目标文件
FileReadToolNode.js fs模块读取指定文件的指定行范围内容
AgentTool独立LLM对话启动子agent,完成多步探索任务

除此之外,还有LSP(Language Server Protocol)工具,通过“跳转到定义”“查找引用”等语义精确的操作,补充Grep在语义理解上的不足。但整个搜索架构的核心,依然是上面提到的四个工具。

其中,AgentTool比较特殊。它不是直接用于搜索文件,而是启动一个独立的子agent,让子agent在自己的上下文窗口里,完成一整套搜索任务,最后只把总结性的结论返回给主对话。子agent有多种类型,其中与搜索最相关的是Explore类型,它只配备了搜索和读取工具(Grep、Glob、Read),不能编辑文件、不能执行命令、不能嵌套启动新的Agent,相当于一个“纯只读的搜索专家”。

子agent的核心价值,在于上下文隔离。它会从零开始构建自己的对话历史,不继承主对话的任何消息,这意味着它在搜索过程中产生的大量Grep结果、代码片段,都会留在自己的上下文里,主对话只需要接收一段简洁的结论。这就解决了一个关键问题:如果在主对话里直接进行大范围搜索,几轮Grep和Read下来,上下文很快就会被大量中间结果塞满,导致LLM无法聚焦核心信息。而交给子agent处理后,主对话的上下文只会增加一条结论消息,极大地降低了上下文负担。

2.2 GrepTool的核心:灵活控制信息量,避免上下文臃肿

在实际使用中,LLM最常用的搜索模式是“先定位,再深入”:先用Grep或Glob找到相关的文件,再用FileReadTool读取文件的具体内容。但很多人会有疑问:Grep搜到文件后,是不是每次都要搭配Read工具,才能看到具体代码?其实不然,关键在于GrepTool有三种输出模式,每种模式返回的信息量完全不同,可以根据实际需求灵活选择。

第一种是files_with_matches模式,也是默认模式。这种模式下,Grep只返回匹配的文件路径列表,不返回任何代码内容。比如我们执行搜索命令Grep({pattern: "class.*Transport", output_mode: "files_with_matches"}),它会返回cli/transports/WebSocketTransport.ts、cli/transports/SSETransport.ts这样的文件路径。LLM拿到的只有文件名,所以这种模式下,通常需要后续调用Read工具,才能看到文件的具体代码。

这种默认设计,其实是有意控制信息量,避免一次Grep就把大量代码涌入上下文窗口,让LLM自己判断哪些文件值得深入读取。除此之外,还有一个保护机制:head_limit默认值为250,即使Grep搜到10000条匹配结果,也只会返回前250条,防止大量搜索结果淹没上下文,影响LLM的判断。

第二种是content模式。这种模式下,Grep会返回匹配行及其上下文代码。比如执行命令Grep({pattern: "TOOL_VERBS", output_mode: "content", "-C": 5}),它会直接返回匹配行前后各5行的代码片段。对于很多场景来说,这些代码片段已经足够用了,比如确认一个常量的值、查看一个函数的签名、检查某个import是否存在,不需要再调用Read工具读取整个文件,既节省了时间,也减少了上下文负担。

第三种是count模式。这种模式下,Grep只返回每个文件的匹配数量,用于快速评估搜索词在项目中的分布密度,不返回任何具体内容。比如我们想知道“SessionActivity”这个关键词在项目中出现了多少次、分布在哪些文件里,就可以用count模式,快速获取统计结果,再决定是否进一步深入搜索。

由此可见,GrepTool的工具组合方式是非常灵活的:Grep(默认模式)→ Read是最常见的路径,但Grep(content模式)可以独立使用;LLM也可以直接调用Read工具(如果已经知道具体的文件路径),甚至可以一次同时发起多个Grep请求,并行搜索。

这种灵活性是有意为之的,其核心思想是“用软引导代替硬约束”:系统提示(system prompt)会建议LLM先通过Grep定位文件,再用Read深入读取,GrepTool的默认输出模式也自然引导这个流程,但不会在代码里堵死其他路径,让LLM可以根据具体的搜索场景,做出最合理的判断。

2.3 实战演练:追踪GrepTool的执行记录,看多轮搜索如何落地

回到开篇的实战场景:当我们问Claude Code“LLM调用GrepTool做搜索时,bridge是怎么追踪和记录这次工具调用的?”,它会通过多轮搜索,一步步拼凑出完整的答案。这个过程恰好能完美展示多轮Grep循环的运作方式,下面我们就一步步拆解这个真实的搜索过程。

第一轮:广撒网,初步定位相关文件。LLM会先将问题中的核心关键词“GrepTool”“追踪”“记录”,翻译成Grep能识别的搜索关键词,然后用默认的files_with_matches模式,对整个项目进行一次全面扫描。执行的命令如下:

Grep({pattern: "GrepTool|tool.*track|tool.*activity", glob: "*.ts"})

这次搜索返回了4个文件:structuredIO.ts、sessionRunner.ts、bridgeUI.ts、bridgeStatusUtil.ts。其中3个文件在bridge/目录下,1个在cli/目录下。由于我们的问题聚焦于bridge系统,所以LLM会重点关注bridge/目录下的文件,其中sessionRunner.ts(从文件名就能看出,它是会话执行器),最有可能包含工具执行追踪的相关逻辑。

第二轮:看上下文,获取关键代码片段。为了进一步确认sessionRunner.ts中的相关逻辑,LLM会将Grep切换到content模式,查看GrepTool在该文件中的上下文内容。执行的命令如下:

Grep({pattern: "GrepTool|tool.*activity", path: "bridge/sessionRunner.ts", output_mode: "content", "-C": 5})

这次返回的代码片段中,能看到一张映射表的尾部,显示GrepTool: 'Searching'BashTool: 'Running',但映射表的上文被截断了,无法看到完整的逻辑。LLM判断,仅凭这部分片段无法获取完整信息,需要调用Read工具,读取该文件的完整上下文。

第三轮:调用Read工具,读取完整代码。LLM调用FileReadTool,打开sessionRunner.ts的完整上下文,通过这次读取,找到了三个关键结构,正是追踪工具调用的核心逻辑:

第一个是工具名→动词映射表(TOOL_VERBS),这个映射表共有18个条目,其中与搜索相关的部分如下:

Grep: 'Searching', GrepTool: 'Searching',
Glob: 'Searching', GlobTool: 'Searching',
Read: 'Reading', FileReadTool: 'Reading',
Edit: 'Editing', FileEditTool: 'Editing',
Bash: 'Running', BashTool: 'Running',

从这个映射表可以看出,每个搜索工具(Grep、Glob)都被映射成了“Searching”,而且有两套命名方式,比如内部名Grep和外部SDK名GrepTool,这说明工具名是硬编码在映射表里的,不是动态注册的。

第二个是摘要生成函数。这个函数的作用,是把映射表中的动词和搜索目标拼接在一起,生成工具调用的摘要。其中,动词来自上面的映射表,目标则从工具调用的输入中提取(优先取file_path,其次是pattern、command、url等)。比如一次GrepTool({pattern: "reconnect|backoff"})调用,生成的摘要就是“Searching reconnect|backoff”。

第三个是活动解析器。它会从session的标准输出(stdout)中,逐行解析JSON数据,当发现工具调用事件时,就会调用上面的摘要生成函数,生成摘要,并打包成一条活动事件。

到这里,我们已经知道了bridge系统是如何追踪和记录工具调用的,但还有一个疑问:活动事件生成之后,会被发送到哪里,用于什么用途?这就需要进行第四轮搜索。

第四轮:追踪活动事件的使用方,拼凑完整链路。LLM通过Grep搜索“SessionActivity”(活动事件的核心关键词)被哪些文件引用,一次追出了整条链路。执行的命令如下:

Grep({pattern: "SessionActivity|currentActivity", path: "bridge/", output_mode: "content", "-C": 2})

这次搜索,三个关键文件浮出水面,分别承担了不同的作用:

第一个是bridge/types.ts,这里定义了活动事件的类型,只有3个字段:类型、摘要、时间戳。每个session会维护一个环形缓冲区和一个当前活动指针,用于存储和管理活动事件。

第二个是bridge/bridgeMain.ts,这里有一个定时器,会周期性地轮询每个session的当前活动,并维护最近5次工具调用的轨迹,比如“Searching → Reading → Searching → Editing”这样的历史记录。

第三个是bridge/bridgeUI.ts,它会在收到工具启动事件后,缓存摘要文字,并将其渲染到bridge的状态面板上,让用户能够直观看到当前的工具调用状态。

到这里,完整的追踪链路就拼凑完成了:session进程输出工具调用的JSON数据 → 活动解析器提取数据并生成摘要 → bridge主进程定时轮询获取最新活动 → UI模块将摘要渲染到状态面板。这就是Claude Code通过多轮Grep循环,解决具体问题的完整过程,也让我们看到了LLM驱动Grep的实际价值。

三、性能解密:暴力搜索为何能“击败一切”?ripgrep的底层优势

看完上面的实战过程,很多人都会有一个疑问:每轮Grep都是在项目文件中进行暴力扫描,一个大一点的项目可能有几万个文件,这种暴力扫描难道不会很慢吗?为什么Boris会说“Grep击败了一切”?

其实,我们平时提到的Grep,和Claude Code使用的Grep,并不是同一个东西。诞生于1973年的GNU grep是最经典的版本,它采用逐文件递归的方式,不认识.gitignore文件,默认是单线程运行,速度相对较慢。而Claude Code的GrepTool,底层调用的并不是GNU grep,而是ripgrep(简称rg),一个2016年由Andrew Gallant用Rust重写的现代实现,专门为大型代码库的快速搜索场景设计。

这一点,我们可以从Claude Code的源码中得到佐证:在tools/GrepTool/GrepTool.ts的第21行,有这样一句代码:import { ripGrep } from '../../utils/ripgrep.js',这说明真正负责搜索工作的,是ripgrep,而不是系统自带的GNU grep。

这一章,我们就来解密ripgrep的底层优势:它的五层过滤机制如何把搜索范围从几万文件缩小到几十个,SIMD和Boyer-Moore算法如何加速文件内匹配,以及代码搜索和向量检索在数据规模上的本质差异。最后,我们还会用Claude Code自己的源码,做一组ripgrep和GNU grep的实测对比,看看两者的速度差距到底有多大。

3.1 ripgrep的五层过滤:从几万文件到几个文件的快速筛选

ripgrep的核心优势之一,就是它并非对每个文件都进行正则匹配,而是在真正开始搜索内容之前,通过五层过滤,逐步缩小搜索范围,从而大幅提升搜索速度。这五层过滤是层层递进、乘法叠加的,每一层都能过滤掉大量无关文件,具体如下:

第一层:目录级剪枝(.gitignore)。ripgrep会默认遵守.gitignore文件中的规则,跳过整棵目录子树,连目录的内容都不会读取。比如一个Node.js项目中,node_modules/目录可能包含数万个文件,而通过.gitignore中的一条规则,就能直接跳过这个目录,大幅减少需要搜索的文件数量。

第二层:路径范围限制(path参数)。通过path参数,限定目录遍历的起点,只在指定的目录及其子目录中搜索,避免对整个项目进行全量扫描。比如我们只想搜索bridge/目录下的文件,就可以通过path: "bridge/"参数,直接将搜索范围限定在该目录内。

第三层:文件类型过滤(glob参数)。通过glob模式,过滤掉不匹配的文件类型,比如只搜索.ts文件、.js文件,这样即使在指定路径下,也只会遍历并搜索符合条件的文件,进一步缩小范围。

第四层:二进制文件检测。ripgrep会读取文件开头的几个字节,判断该文件是否为二进制文件,如果是,就直接跳过,不进行后续的正则匹配。这能避免对图片、视频、可执行文件等无关文件的无效搜索。

第五层:内容搜索(正则匹配)。这是最后一步,只有通过了前面四层过滤的文件,才会进行正则匹配,查找符合条件的内容。

我们用贯穿全文的实战例子,来看看这五层过滤的实际效果。在泄露的Claude Code源码中,共有4471个文件,我们执行第四轮的搜索命令:Grep({pattern: "SessionActivity|currentActivity", path: "bridge/", glob: "*.ts"}),其过滤过程如下:

原始文件数:4471个;

第一层.gitignore剪枝:4471个(由于这份源码快照中没有node_modules目录,所以这一层没有起到过滤作用);

第二层path限制bridge/:32个(只遍历bridge/目录,直接从4471个文件缩减到32个);

第三层glob *.ts过滤:32个(bridge/目录下全是.ts文件,这一层没有额外过滤);

第四层二进制检测:32个(全是文本文件,无二进制文件);

第五层正则匹配:3个文件命中(bridgeStatusUtil.ts、sessionRunner.ts、bridgeUI.ts)。

从这个例子可以看出,path限制是最大的过滤器,一步就将搜索范围从4471个文件砍到32个,再加上其他过滤层,最终只需要对3个文件进行正则匹配,搜索速度自然会大幅提升。而在包含node_modules/的完整项目中,.gitignore剪枝的效果会更加明显,能一次性砍掉数万个无关文件。

3.2 文件内搜索加速:SIMD、Boyer-Moore与零拷贝技术

通过五层过滤后,需要实际进行内容搜索的文件已经很少了,但ripgrep在文件内的正则匹配环节,还有多项优化手段,进一步提升搜索速度,这些优化也是它能击败GNU grep的关键。

第一个优化是SIMD向量化匹配。ripgrep底层使用的是Rust的regex crate,它能利用CPU的SIMD(单指令多数据)指令,并行比较多个字节。普通的逐字节比较,一次只能处理1个字节,而AVX2指令集一次能处理32个字节,搜索效率提升非常明显。在实际搜索时,ripgrep会先用SIMD快速扫描搜索词首字符的出现位置,只在命中首字符时,才进行完整的正则匹配,进一步减少无效计算。对于多模式搜索,ripgrep还会使用Teddy算法,实现SIMD级别的多模式并行匹配。

第二个优化是Boyer-Moore跳跃算法。对于固定字符串的搜索,ripgrep会从搜索模式(pattern)的末尾开始比对,遇到不匹配的字符时,会根据“坏字符表”,直接跳过多个字符,而不是逐字节回溯。这种方式在搜索长pattern时,效率提升尤为明显,通常只需要扫描约n/m个字符(n为文件大小,m为pattern长度),就能完成搜索。

第三个优化是操作系统Page Cache。读过的文件内容,会被操作系统缓存在内存中,对于开发者日常使用的代码项目,大部分文件都会被频繁访问,几乎永远在缓存中。这意味着,首次搜索可能会触发磁盘I/O,而第二次搜索时,文件内容直接从内存中读取,速度会快很多。

第四个优化是mmap零拷贝技术。对于大文件,ripgrep会使用mmap(内存映射)代替普通的read()系统调用。普通的read()调用,需要将数据从内核空间复制到用户空间,而mmap能让进程直接访问内核的Page Cache,省去了一次数据复制的过程,大幅提升读取速度。不过,对于小文件,mmap的系统调用开销反而不划算,所以ripgrep会根据文件大小,动态选择使用mmap还是普通read()。

第五个优化是多线程并行。ripgrep会使用线程池,并行处理多个文件:一个线程负责遍历目录树,产出文件路径;多个worker线程并行搜索不同的文件;搜索结果通过无锁队列(lock-free queue)汇总,避免了线程间的锁竞争,进一步提升搜索效率。

3.3 实测对比:ripgrep vs GNU grep,差距到底有多大?

为了更直观地展示ripgrep的速度优势,我们用Claude Code自己的源码(约4500个文件、95万行代码),做一组实测对比。在同一台机器上,分别用ripgrep和GNU grep(加-r参数,递归搜索),搜索同一个关键词,取3次运行的稳定值,结果如下:

搜索模式ripgrepGNU grep -r速度倍数
TOOL_VERBS(低频词)0.09s2.55s28倍
async.*generator(正则)0.10s3.30s33倍
import.*from(高频词)0.10s2.45s25倍

从实测数据可以看出,两者的搜索速度差距非常明显,ripgrep的速度是GNU grep的25-33倍。值得注意的是,两者搜索的文件范围几乎一样(ripgrep搜索4494个文件,GNU grep搜索4522个文件),差距主要来自ripgrep的多线程并行和SIMD向量化加速,而非文件过滤。

更重要的是,ripgrep的搜索延迟非常低,0.1秒左右的延迟,对于交互式使用来说,基本可以忽略不计。这也是Claude Code能够采用多轮Grep循环的核心前提:如果每次搜索都需要几秒甚至十几秒,多轮循环的体验会非常差,而ripgrep的高速搜索,让多轮迭代变得可行。

3.4 关键前提:数据规模决定暴力搜索的可行性

ripgrep的速度虽然快,但它的暴力搜索模式,并非适用于所有场景。Claude Code之所以能放弃索引、只用Grep,一个很重要的原因是,它面对的数据规模,恰好落在了暴力搜索可行的范围内。

我们可以对比一下向量检索和Grep的适用场景数据量,就能明白其中的差异:

对比维度向量检索Grep(ripgrep)
常见场景数据量GB级别及以上MB级别到几百MB
单次比较成本768次浮点乘法(余弦相似度)1次字节相等判断
SIMD加速后约24次乘法/指令约32次比较/指令
暴力扫描总耗时秒级到分钟级数十毫秒

一个250MB的代码库,在Page Cache命中(开发者的日常项目几乎总是命中)的情况下,连磁盘I/O都可以省去,整份数据都躺在内存里。按现代开发机约30GB/s的内存带宽计算,把这250MB数据从Page Cache中读取一遍的理论时间约为250MB / 30GB/s ≈ 8毫秒。再加上ripgrep做SIMD模式匹配的CPU开销,实测总耗时通常在几十到一百多毫秒,完全不需要构建索引来提升速度。

而如果数据量达到GB级别甚至更大,比如大型企业的代码仓库、海量的文档库,ripgrep的暴力搜索就会变得很慢,这时向量检索的优势就会显现出来。这也说明,技术的选择没有绝对的优劣,关键在于场景的适配。

四、行业对比:零索引vs双索引,Grep回归的行业共识与分歧

Claude Code选择零索引、拥抱Grep,但这并不意味着整个行业都在放弃RAG和索引。事实上,行业内的主流解决方案,呈现出了两种不同的路线:一种是以Claude Code、Codex为代表的零索引路线,另一种是以Cursor为代表的双索引路线。

这一章,我们就将这三种方案放在一起对比,看看它们的设计思路有何不同,规模如何决定架构选择,以及Grep方案最受争议的token成本问题,Claude Code是如何应对的。通过这些对比,我们能更清晰地看到,Grep的回归不是偶然,而是行业发展到一定阶段的必然选择,同时也能明白,RAG并没有真正“死亡”,只是其实现形式在发生变化。

4.1 Cursor的双索引架构:预处理优先,兼顾精确与语义搜索

Cursor作为AI编程领域的另一款主流工具,采用的是经典的RAG架构,并在此基础上叠加了trigram索引,形成了“语义索引+精确搜索索引”的双索引架构。需要说明的是,Cursor同样有Grep搜索工具可用,只是它没有像Claude Code那样放弃索引,而是选择了“索引+Grep”的混合模式。

首先是语义索引。Cursor在本地会用tree-sitter,将代码按语法边界切成小块,然后通过Merkle Tree做增量同步,也就是说,只有代码发生变化的部分,才会被同步,避免全量同步带来的开销。这些代码块会被加密上传到Cursor的服务端,服务端用embedding模型生成向量后,会立即丢弃原始代码,只将向量和元数据存入Turbopuffer(一个高性能的向量搜索引擎)。

其搜索流程如下:用户提问 → 生成embedding → 向量最近邻搜索 → 取top-K结果 → reranking(重新排序) → 将结果组装到上下文 → LLM生成回答。这种方式的优势是,能够实现语义层面的搜索,即使用户输入的关键词与代码中的表述不完全一致,也能找到相关内容。

其次是精确搜索索引。Cursor在2025-2026年开发了Instant Grep功能,使用trigram(三字符组合)倒排索引,加速Grep搜索。在预处理阶段,Cursor会把文件内容切成3字符的滑动窗口,比如“OAuth”会被切成“OAu”“Aut”“uth”,然后为每个trigram维护一个包含该trigram的文件列表。当用户进行搜索时,Cursor会取所有trigram对应文件列表的交集,得到候选文件,再对候选文件进行正则匹配,大幅提升搜索速度。

总的来说,Cursor走的是“预处理路线”:代码仓库在后台会被分块、embedding,向量写入Turbopuffer,同时通过Merkle Tree维护增量同步,离线建好的索引是整个搜索链路的前提。而Claude Code走的是“按需路线”:没有索引、没有预处理,LLM在对话中实时决定用什么关键词Grep、读哪些文件,所有语义理解都由模型自己在循环中完成。

这两种架构,对应着两套不同的取舍:索引换来的是更高的命中率和跨仓库扩展性,能够应对更大规模的代码库;而零索引换来的是零启动延迟、零维护成本,以及与开发者工作流的零摩擦,开发者不需要等待索引构建完成,打开工具就能直接搜索,非常适合本地小型项目。

4.2 核心共识:规模决定架构,Grep是代码搜索的“首选工具”

Cursor的索引规模,本身就说明了一个问题。Turbopuffer的官方客户案例,披露了Cursor的向量基础设施数据:100亿+向量、1000万+命名空间(每个命名空间对应一个用户的一个代码库)、写入吞吐量约10GB/s。Cursor的CTO Sualeh Asif曾表示,Turbopuffer是“扩展过程中少数不需要操心的基础设施之一”。

这个规模意味着,Cursor面对的不仅仅是小型个人项目,还有大量的大型代码库。当代码库足够大时,ripgrep的暴力扫描延迟会变得不可接受,而索引能够大幅降低搜索延迟。对于Agent场景来说,搜索延迟直接决定了在有限时间内,Agent能完成多少轮搜索,也就决定了Agent对代码的理解深度。

所以,零索引和双索引,并不是技术优劣之分,而是场景选择之分。Claude Code面向的是开发者本地项目(MB级别到几百MB),ripgrep的暴力扫描只需几十毫秒,加上LLM的推理能力,零索引意味着零启动延迟、零维护成本、零配置,性价比极高;而Cursor面向的是更广泛的场景,包括大型代码库,这时索引就是必需的。

但有一个细节非常值得玩味:在Cursor 2025年3月泄露的Agent system prompt中,grep_search被明确标注为“主要探索工具”(MAIN exploration tool),LLM被要求先用一组宽泛的关键词进行Grep,而codebase_search(语义搜索),只在“概念性查询”时作为补充。

一家把语义搜索当核心卖点、为此自建整套向量基础设施的公司,内部却把Grep放在第一个被调用的位置,这足以说明一个行业共识:在代码搜索这个任务上,“精确匹配找到已知符号”,远比“语义理解找到相似概念”来得高频和确定。向量检索解决的是Grep覆盖不到的长尾场景,而不是反过来。

行业趋势也在印证这一点:有分析指出,Cursor正在弱化纯向量搜索,转向“向量+Grep”的混合搜索;而Claude Code则把Grep路线推到了极端,完全不用语义检索,靠LLM把语义需求翻译成精确关键词,再交给Grep处理。

值得注意的是,Claude Code自己也在不断演进。在v2.0.74版本中,它引入了LSP(Language Server Protocol)支持,用“跳转到定义”这类语义精确的操作,替代了部分Grep + 多文件Read的流程,实测降低了约40%的token消耗。而社区也在做补充:有人开发了Beacon插件,利用Claude Code自带的PreToolUse hooks,拦截Grep调用,替换为“向量+BM25+rank fusion”的混合搜索,兼顾Grep的精确性和向量检索的语义能力。

4.3 Codex的验证:殊途同归的零索引选择

除了Claude Code,OpenAI的Codex CLI,也做出了和Claude Code几乎相同的架构决策,不建索引、不用embedding、不用向量数据库。社区曾向OpenAI提交过“添加向量索引功能”的请求,但被OpenAI团队关闭,并明确表示“not currently on our roadmap”(目前不在我们的路线图上)。

不过,Codex和Claude Code的实现路径,有一个关键分歧:搜索工具的设计不同。Claude Code为搜索操作封装了专用工具,GrepTool有三种输出模式和head_limit等参数,GlobTool专门做文件名匹配,FileReadTool按行范围读取,每个工具有明确的参数schema,LLM通过结构化的工具调用来使用它们,不需要解析原始的shell输出,减少了出错概率,也更容易控制信息量。

而Codex没有专门的搜索工具,它的核心工具是shell(可以执行任意shell命令)和apply_patch(专用diff格式编辑文件),此外还有update_plan、view_image、web_search、spawn_agent(多agent协作)等工具。所有的代码搜索操作,都通过shell工具完成,LLM可以直接组合rg、find、cat、git等Unix命令,进行搜索。

在Codex的多个system prompt文件中,都有同一条指令:“When searching for text or files, prefer using rg or rg --files respectively because rg is much faster than alternatives like grep.” 翻译成中文就是,当搜索文本或文件时,优先使用rg或rg --files,因为rg比grep等替代工具快得多。它的搜索模式,同样是多轮迭代:Grep → 读文件片段 → 调整关键词 → 再搜索,和Claude Code的逻辑基本一致。

我们可以用一个表格,清晰对比Claude Code和Codex CLI的核心差异:

对比方面Claude CodeCodex CLI
搜索工具专用工具(GrepTool、Glob、Read),有结构化参数通过shell工具执行rg、find、cat,无专用搜索工具
索引
子agent内置(Explore、Plan等类型,上下文隔离)内置(spawn_agent/send_message/wait_agent等)
编辑方式Edit(字符串替换)apply_patch(diff格式)

这两种路径的核心分歧,在于搜索工具的封装程度。Claude Code把Grep封装成带结构化参数的专用工具,降低了LLM的使用难度;而Codex让模型直接写shell命令调用rg,给予了模型最大的灵活性,可以自由组合管道、正则、路径过滤等操作,但需要模型自己处理非结构化的文本输出,出错概率相对更高。

但真正值得注意的是两者的共识:两个互为竞争对手的AI编程产品,独立做出了几乎相同的架构决策,用LLM驱动ripgrep,放弃向量检索。这绝对不是巧合,它说明在当前LLM的能力水平,以及开发者本地项目的规模范围内,零索引+Grep,已经是一个被反复验证的有效方案。

4.4 争议焦点:Grep方案的token成本,Claude Code如何应对?

Grep方案的优势很明显:零索引、零维护、零启动延迟,精确匹配能力强。但它也有一个非常明显的争议点:多轮调用Grep和Read,会不会消耗大量的token?毕竟,搜索循环的每一轮,都要把完整的对话历史发给Claude API,上下文会越来越长,token消耗也会随之增加。

向量数据库厂商Milvus(Zilliz)的工程师,曾发表文章《Why I’m Against Claude Code’s Grep-Only Retrieval? It Just Burns Too Many Tokens》,直接质疑这一点。文章展示了一个实测案例:用Claude Code调试一个VSCode扩展的bug,Grep在仓库中反复搜索,倾倒了大量无关文本,最终花了14次工具调用、32.2k tokens、59.3秒才找到答案,而实际上,正确的10行代码,被埋在500行噪声里。

文章将问题归纳为三点:一是token膨胀,每次Grep都会把大量无关代码塞进上下文,成本会随着仓库规模的扩大而恶化;二是时间税,AI需要向代码库“问二十个问题”,开发者只能干等,体验不佳;三是零语义,Grep只做字面匹配,不理解代码的含义和关联,容易搜到无关内容。作为替代方案,他们开源了基于向量检索的MCP插件Claude Context,声称在相同任务上,token消耗降低约40%、工具调用次数减少约36%。

那么,Claude Code自己是怎么应对context膨胀和token成本问题的?从源码中,我们发现了至少三层机制,需要说明的是,这三层机制都是通用的工程手段,embedding方案同样可以使用,并非Grep方案独有的优势。

第一层:prompt cache(提示缓存),降低重复计费。Claude API会识别出,本次请求的输入前缀和上次完全相同,只是在末尾追加了最新一轮的工具结果。这时,API会直接复用已有的计算缓存,只为新增的增量部分付全价,而前面累积的大部分内容,会按约1/10的缓存价计费,大幅降低token成本。

在Claude Code的源码中,我们可以看到它为此做了精细的工程优化:system prompt在发送前,会被拆成多个独立的文本块,每个块可以单独标记缓存策略。这种分块设计,确保了不变的部分能精确命中缓存,不会因为动态内容的变化而失效。有分析显示,在agentic循环中,92%的prompt前缀在相邻轮次间完全相同,实测token成本降低约81%。

第二层:auto-compaction(自动压缩),缩短对话历史。多轮Grep和Read会让对话历史持续增长,当累积的token数接近上下文窗口上限时,Claude Code会自动触发对话压缩:用LLM对旧的对话历史生成摘要,然后用摘要替换原始消息,直接缩短对话历史。这意味着,上下文不会无限增长,早期搜索轮次的Grep结果和Read内容,最终会被压缩成一段摘要,为后续的搜索和推理腾出空间。

第三层:子agent隔离,减少主上下文负担。我们在第二章提到的Explore子agent,本身就是一种上下文管理手段。大量Grep和Read的原始结果,会在子agent的独立上下文里处理和消化,只有精炼后的结论,会返回给主对话,避免主上下文被大量搜索中间结果撑满,从而减少token消耗。

这三层机制,让Grep的多轮搜索在实践中变得可控,但并没有消除Grep方案相对于embedding方案,在单次检索精准度上的差距。Grep方案的核心权衡(tradeoff)是:用更多的搜索轮次和更大的context开销,换取零索引、零维护、零启动延迟的工程简洁性。这个权衡,在开发者本地项目的规模上是合算的,但在更大规模的场景下是否仍然成立,取决于搜索轮次和context成本的增长曲线。

4.5 边界清晰:Grep的有效性,取决于场景是代码还是自然语言

Milvus的批评,针对的是通用场景下的token开销,但在代码搜索这个具体场景下,Grep的表现,可能比我们直觉预期的要好得多。一项系统性研究(GrepRAG: An Empirical Study and Optimization of Grep-Like Retrieval for Code Completion,ISSTA '26),在CrossCodeEval和RepoEval_Updated两个代码基准数据集上,做了严格的对比实验。

实验的核心的是:让LLM自主生成ripgrep命令,检索代码上下文,然后用检索到的内容做代码补全。结果发现,即使是最朴素的单轮Grep检索,代码补全效果也超过了基于embedding的RAG基线:在RepoEval_Updated的Python Line补全任务上,Naive GrepRAG的Exact Match(精确匹配率)达到38.61%,而Vanilla RAG(BM25 + embedding)只有24.99%。

论文分析了Grep在代码场景下成功的原因:代码搜索的关键词,95%是标识符,其中类名占36%、方法名占41%、变量名占18%。这些标识符本身,就是代码的语义,精确匹配恰好是最直接、最有效的检索方式。这和自然语言搜索有本质区别:自然语言中,“词汇不匹配”是常态,比如“获取用户信息”,可能会被改述成“查询用户数据”;但在代码中,getUserById就是getUserById,不会被改述成fetchPersonByIdentifier,Grep的精确匹配恰好能发挥最大作用。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/22 18:51:51

别只把NE555当定时器了!在STC15单片机上做个简易频率计试试

突破传统:用NE555与STC15打造高性价比数字频率计 在电子设计领域,NE555这颗诞生于1971年的经典芯片至今仍散发着独特魅力。大多数教材和项目都将其局限在定时器或振荡器的角色,却忽略了它作为信号源在测量系统中的潜力。本文将带您探索如何用…

作者头像 李华
网站建设 2026/4/22 18:47:45

如何构建智能直播录制系统:开源录播姬的技术演进与实战指南

如何构建智能直播录制系统:开源录播姬的技术演进与实战指南 【免费下载链接】BililiveRecorder 录播姬 | mikufans 生放送录制 项目地址: https://gitcode.com/gh_mirrors/bi/BililiveRecorder 在数字内容创作蓬勃发展的今天,直播录制已成为内容保…

作者头像 李华
网站建设 2026/4/22 18:47:36

JimuReport积木报表:零代码构建企业级专业报表的终极指南

JimuReport积木报表:零代码构建企业级专业报表的终极指南 【免费下载链接】JimuReport 开源的报表工具与BI大屏,完美替代帆软和Tableau,提供强大的报表能力。一款类似Excel的报表设计器和大屏设计!完全在线傻瓜式拖拽设计&#xf…

作者头像 李华
网站建设 2026/4/22 18:47:35

2025最权威的降AI率工具解析与推荐

Ai论文网站排名(开题报告、文献综述、降aigc率、降重综合对比) TOP1. 千笔AI TOP2. aipasspaper TOP3. 清北论文 TOP4. 豆包 TOP5. kimi TOP6. deepseek 维普AIGC检测系统,是当下国内学术领域,用于识别人工智能生成内容的重…

作者头像 李华
网站建设 2026/4/22 18:45:39

终极指南:用Android手机变身USB键盘鼠标的完整教程

终极指南:用Android手机变身USB键盘鼠标的完整教程 【免费下载链接】android-hid-client Android app that allows you to use your phone as a keyboard and mouse WITHOUT any software on the other end (Requires root) 项目地址: https://gitcode.com/gh_mir…

作者头像 李华