Claude Code 为什么放弃 RAG:用 grep 干代码搜索的真实架构

0 阅读16分钟

Claude Code 为什么放弃 RAG:用 grep 干代码搜索的真实架构

Claude Code 早期版本是用 RAG 的。Anthropic 团队后来在内部 benchmark 里发现:换成 agentic search——也就是让模型自己去 grepglobread——在代码场景下"性能大幅超越其他方案"。这句话来自 Boris Cherny 的播客原话。我读到时还挺意外,毕竟这两年大家默认"做代码 agent = 上 RAG"。后来翻完它的实现细节,才明白这不是简单的"grep 比向量搜索好",是一次更深的设计判断。

一、它到底解决什么问题

先把问题说清楚:让一个 agent 改一个 bug,它得先找到相关代码。

具体场景大概是这样的:用户说"修一下 payment API 的 auth bug"。agent 要先回答几个问题——

  1. 这个项目长什么样(目录结构 / 模块划分)
  2. payment API 在哪个文件(精确定位)
  3. auth 相关的代码散在哪些地方(跨文件依赖)
  4. 跑测试要用什么命令(项目惯例)

这些问题的答案不在模型的训练集里——它们在用户当前的代码库里。所以这是一个"动态上下文获取"问题。

行业里主流有三条路:

方案代表思路
向量 RAGCursor把整个代码库 embedding 化,存向量库(Cursor 用 Merkle 树 + Turbopuffer),查询时召回 top-K chunk 拼进 context
传统索引引擎JetBrainsPSI 树 + stub 索引(20 年打磨),强符号导航,但索引构建慢
Agentic searchClaude Code不建索引,给 agent 三个工具(grep / glob / read),让它自己多轮探索

Anthropic 的早期版本走的是第一条。Boris Cherny 在 Claude Code: Anthropic's Agent in Your Terminal 那篇 2025 年 5 月的博客访谈里说,他们试过 RAG + 本地向量库,但 benchmark 跑下来发现"agentic search 在性能上大幅超越所有其他方案"。后来他给 Claude Code 重新定位:

Claude Code 不是一个产品,是一个 Unix 工具。

行业里常见的解释是"grep 比向量搜索更准"。这话不算错,但它把所有难点都装进了"准"这个字里——读完源码会发现,真正的判断比这复杂。

二、核心架构是怎么分层的

Claude Code 的代码探索能力不是"grep 工具"这一件事,是一套四层架构。这个架构本身就回答了"agent 怎么获取上下文"这个问题:

       Claude Code 代码探索:四层检索架构
   ┌────────────────────────────────────────────────┐
                                                   
      Layer 3Agent 委托检索                  
      ─────────────────────────────                
      Explore Agent(只读 / Haiku 模型 /          
      独立上下文 / 防火墙作用:原始结果留在        
      子上下文,只回精炼摘要给主 Agent)           
                                                  
                              委托                
      Layer 2   模型驱动检索                      
      ─────────────────────────────                
      Glob ──▶ Grep ──▶ Read(多轮循环)           
      由主 Agentwhile(true) 里决定             
      下一步搜什么读什么                         
                                                  
                              调用                
      Layer 1   智能预注入                         
      ─────────────────────────────                
      Memory Prefetch(用 Sonnet 跑 sideQuery     
   5 条相关记忆)+ @file 扫描 +             
      Skill 自动发现                               
                                                  
                              启动时              
      Layer 0   静态上下文                         
      ─────────────────────────────                
      System Prompt + CLAUDE.md + Git Status      
      + MEMORY.md 索引                             
                                                   
   └────────────────────────────────────────────────┘
                              
              用户输入 ──────┘
                "修一下 payment API 的 auth bug"

四层都有自己的角色:

  • Layer 0 是会话开始就喂进去的——CLAUDE.md(项目惯例)、git status(当前修改)、MEMORY.md 的索引。这些是确定要的信息,不进 agent 决策。
  • Layer 1 是会话启动时并行预取——比如用一个轻量 Sonnet 调用做"side query",从历史记忆里挑出 ≤5 条可能相关的;扫一下用户消息里 @file 引用;把可能用到的 skill 描述加载进来。这是有预测的预注入,但不是 RAG(不查向量)。
  • Layer 2 才是真正的 agentic search——Glob / Grep / Read 三件套,在 while(true) 循环里被主 Agent 反复调用。搜什么、搜几次、什么时候停,全是 LLM 自己决定的。
  • Layer 3 是子 Agent 委托——遇到大量探索(比如"梳理整个项目的 auth 流程"),主 Agent 可以 spawn 一个 Explore Agent,这个子 agent 用更便宜的 Haiku 跑、独立上下文、只读权限,最后只把摘要回传给主 Agent。这一层我后面会单独讲——它是 Claude Code 一个特别巧的设计。

这个分层里藏着一个关键的设计判断:把"决策权"放在 LLM 那一层,而不是产品那一层。 Cursor 的 RAG 在产品层就决定了"召回 top 30 chunk"——LLM 拿到的是已经被裁剪过的上下文。Claude Code 不裁剪,把工具给 LLM,让它自己决定怎么用。这两种哲学都对,但产生的产品形态完全不同。

三、一次代码搜索是怎么走通的

抽象讲容易飘。让我用"修 payment API auth bug"这个具体例子,把 Layer 2 的多轮调用拆开看。下面是一次真实的调用序列(基于 Claude Code 行为逆向 + 官方 docs 描述):

   一次"修 auth bug"任务的检索流程(简化版)
   
   T0  会话启动
       │ 加载 Layer 0:System Prompt + CLAUDE.md + git status
       │ 并行 Layer 1:sideQuery 拉 3 条相关 memory + 扫 skill
       ▼
   T1  Glob "**/payment*"          ← 找入口文件
       └─ 返回 8 个候选文件路径(按 mtime 排序)
       │
   T2  Read src/api/payment.ts     ← 读最可能的入口
       └─ 返回 全文(< 25K tokens)
       │
   T3  Grep "import.*auth"          ← 找 auth 相关引用
            in src/api/             
       └─ 返回 5 个文件 + 上下文行
       │
   T4  Read src/auth/middleware.ts  ← 读关键依赖
       └─ 已读过?检查 mtime
          → 未变化 → 返回 "File unchanged"(省 ~25K tokens)
          → 变化 → 重新读
       │
   T5  ─── LLM 判断:上下文够了吗 ───
       │ 够 → 进入修复阶段(Edit + Bash test)
       │ 不够 → 继续 Grep / Read
       ▼
   T6  Edit src/api/payment.ts      ← 修复
       │
   T7  Bash "npm test -- payment"   ← 验证
       └─ 失败 → Read 错误日志 → 回到 T6
       └─ 成功 → 完成

几个工程细节值得展开。

漏斗式调用。 顺序不是随机的——Glob → Grep → Read 是一个收敛漏斗。Glob 像目录浏览(按文件名找候选),Grep 像内容过滤(在候选里搜模式),Read 是最贵的(读全文)。每一跳都把范围收窄。LLM 内化了这个 pattern,几乎不会一上来就 Read 100 个文件。

Token 预算硬限制。

工具限制
Grep默认 head_limit=250 行,结果 ≤ 20KB
Glob最多返回 100 个文件,按 mtime 倒序(最近改的优先)
Read单次最多 25,000 tokens / 256KB / 2000 行

这些数字不是拍脑袋的——它们让单轮调用的 token 消耗封顶可预测,模型不会被一次 Read 整个 50000 行的文件直接撑爆。

Read 去重。 这是我看到的时候笑出声的优化——Read 工具在调用前检查目标文件的 mtime,如果跟上次读时一样,直接返回字符串 "File unchanged",省掉整个文件内容的传输。命中率官方测算是 18%,每次省约 25K tokens

这个优化能成立有一个前提:mtime 是文件系统层面真理,不是 agent 维护的状态。Claude Code 不需要"记得读过哪些文件"——文件系统已经记了。

Prompt Cache 分区。 System Prompt 用 __SYSTEM_PROMPT_DYNAMIC_BOUNDARY__ 分隔静态和动态部分,静态区用 scope: 'global'所有用户共享同一份缓存——这意味着 Anthropic 服务器端只为 system prompt 算一次 KV cache,所有 Claude Code 用户的请求都命中这个缓存。这是 Anthropic 自家产品才能做的优化。

把这些细节加起来,你会发现一件事:Claude Code 的"agentic search"不是简单的"模型自己 grep"——它是一整套配合 LLM 工作模式的工程系统。 grep 是看得见的部分,水面下还有 token 预算、去重、缓存、子 agent 隔离一系列东西。

四、为什么 grep 打败了向量索引

讲完 how,回头讲 why。这是这篇文章我最想说清楚的部分。

行业默认 RAG 是好东西。它确实是好东西——对很多场景。但 Anthropic 的判断是:代码这个特定场景,RAG 的优势失效了。

我把他们的理由整理成四条。

第一,代码场景下"精确匹配"比"语义相似"重要。

向量搜索的强项是语义召回——你搜"用户认证",能找到 login / authenticate / verifyUser 这种语义相近但用词不同的代码。这在产品文档、客服问答里非常有价值。

但代码不是这样工作的。代码的核心特征是符号即真理。当你说"修 processPayment 函数的 bug"时,你要的就是那个名字精确叫 processPayment 的东西。不是"语义相似的",是"那个"。grep "processPayment" 一次拿到所有引用——精确、完整、零幻觉。embedding.similarity("processPayment") 给你一堆"看起来相关"的——其中一些可能是 processOrderhandlePayment 这种结构相近但实际无关的函数。

代码搜索失败时,向量搜索的失败是模糊的:是嵌入模型不够好?是 chunk 切错了?还是 embedding 已经过期?而 grep 的失败是清晰的:关键词没拼对。可调试性差出一个数量级。

第二,状态是 bug 的温床。

向量索引必须维护。代码每改一行,理论上对应的 chunk 都要重新 embedding。Cursor 用 Merkle 树做增量更新,已经是工程上很优雅的方案——但即使如此,"索引滞后于代码"的问题还是会反复出现。

grep 没这个问题。它每次搜的都是当前文件系统真实状态。你 git checkout 一个分支,下一次 grep 立刻反映新分支的代码——不需要"重建索引"。

这背后是一个更深的判断:有状态系统的复杂度是叠加的。 索引启动 / 更新 / 失效 / 崩溃恢复 / 缓存一致性,每一项都是 bug 来源。grep 是 stateless 的——崩了就重跑,没人会觉得"丢了什么"。

我读到这里想起了 Roy Fielding 那篇 REST 论文里的那句话:服务端的 statelessness 不是性能优化,是错误模式的简化。 同一个判断在 2025 年的 AI agent 上又一次成立。

第三,隐私是架构问题,不是政策问题。

Cursor 用 RAG 必须把代码 embedding 后传到向量库(即使是托管的也要传到他们的 Turbopuffer)。学术研究已经证明 embedding 可以反推回原始内容(不是 100% 还原,但足以泄露语义)——这对企业用户是个真问题。

Claude Code 用 grep 是完全本地执行的(agent 给出 grep 命令,本地工具运行),原文上下文只在每次回传给 Anthropic 时进上下文,不存在"持久化的代码副本在他们手里"。这对合规敏感的客户是个根本性差异——不是"我们不上传"的承诺,是"架构上没有上传这一步"的事实。

第四,agent 时代的稀缺资源不是召回,是可预测性。

这一条是我自己的判断,但读完源码后觉得它呼之欲出。RAG 在 2020-2023 年大流行,是因为那时候模型上下文小、推理慢、调用贵——一次 LLM call 是稀缺资源,必须前置把最相关的内容塞进去。

到了 2025 年,Claude Sonnet 4 的上下文 200K-1M、Haiku 便宜到可以并行十几个、tool use 已经是模型一等公民。稀缺的东西换了——不再是"一次 LLM call",而是"输出的一致性和可调试性"。在这个新约束下,让 LLM 自己多调几次 grep(每次便宜可预测),比让 RAG 一次性塞 30 个 chunk 进上下文(高方差,调试困难)更划算。

实测数据印证了这个判断。第三方在 2026 年初做的 5 个重构任务对比:

工具平均 Token平均时长一次成功率
Cursor (RAG)11,00090 秒60%
Claude Code (agentic)2,000180 秒75%
Cline (agentic + auto sub-task)8,500240 秒80%

Claude Code 比 Cursor 少用 5.5 倍 token,但慢一倍,成功率高 15 个百分点。这个组合在生产环境的 agent 工作流(人挂着等结果)里是赚的——慢 90 秒不痛,省 80% token 和提升成功率却是直接赚钱。

五、设计判断里的取舍

讲到这里给人感觉好像 agentic search 完胜。它没有。它有非常明确的代价。

速度慢。 多轮调用本质上是串行的——Glob 完才能 Grep,Grep 完才能 Read。即使工具内部并发,整体时长还是被"轮次"拉长。Cursor 一次 RAG 召回完事,时长更短。在"实时交互"场景(边写代码边问 Claude),这个延迟是劣势。这也是为什么 Cursor 在 IDE 内嵌的体验里更顺手——它的 90 秒级响应配合人坐在屏幕前等,不让人难受;而 Claude Code 的 180 秒级响应更适合"挂在 terminal 后台跑"。

大型 monorepo 第一次冷启动慢。 假如你让 Claude Code 第一次进入一个 50000 文件的 monorepo 做事,它的多轮 Glob/Grep 探索会花相当多时间在"搞清楚这是什么项目"上。Cursor 因为有预先索引,进来直接能干活。这个差距在小项目里看不到,大项目里很明显。

对模型能力的强依赖。 agentic search 的有效性建立在"LLM 知道什么时候搜什么"这个假设上。Sonnet / Opus 级别的模型这件事做得很好;换到弱模型,多轮调用很容易陷入"反复搜同一个东西""搜了不该搜的"。这意味着 agentic search 不是普世解——给你一个 7B 开源模型让你做 coding agent,老老实实建索引可能反而靠谱。

不是非此即彼。 我必须强调这点:放弃 RAG 不等于"以后所有 agent 都不该用 RAG"。Claude Code 自己的 Layer 1 里就有一个"用 Sonnet 做 sideQuery 拉历史记忆"的步骤——这本质就是一个轻量 RAG。它的判断是:主代码搜索用 agentic search,但跨会话记忆这种"内容稳定、量大、需要语义召回"的场景,仍然用 RAG 风格的预取

把这几条 trade-off 合起来看,agentic search 的适用场景大致是:

     是否选 agentic search 而非 RAG?
     
     ┌───────────────────────────────────┐
     │                                   │
     │  ① 检索对象的"真理来源"在文件      │
     │     系统/数据库里吗?              │
     │     ├─ 是(代码、文件、数据)→ →  │
     │     └─ 否(文档、知识、对话历史)  │
     │        → 优先 RAG                 │
     │                                   │
     │  ② 模型 ≥ Sonnet 4 / GPT-4o 级别?│
     │     ├─ 是 → →                     │
     │     └─ 否 → 优先 RAG              │
     │                                   │
     │  ③ 需要的是精确匹配,还是语义召回?│
     │     ├─ 精确(符号、文件、API)→ → │
     │     └─ 语义(概念、意图)         │
     │        → 优先 RAG                 │
     │                                   │
     │  ④ 用户能接受 2-3x 延迟换 token  │
     │     省、可调试性、隐私保护吗?     │
     │     ├─ 能 → 选 agentic search    │
     │     └─ 不能 → 选 RAG             │
     │                                   │
     └───────────────────────────────────┘
     
     四个全是"是/能" → agentic search
     有一个"否" → 考虑 RAG / 混合方案

六、自己做 agent 工具该带走什么

如果你在做一个 agent 类工具,对照 Claude Code 的判断,有几条具体的可借鉴:

1. 检索对象的"真理来源"在哪里,决定要不要建索引。 文件系统是真理来源 → 不要建索引(grep 就行)。一份每月更新一次的产品手册是真理来源 → 建索引很合理。混合场景(代码 + 文档)→ 分别处理,不要一刀切。

2. 工具集要让 LLM 形成"漏斗"。 Claude Code 的 Glob/Grep/Read 不是随便选的——它们是天然的收敛序列(粗 → 细)。你给 agent 工具时,要想清楚它们之间是不是这种关系。给十个并列的搜索工具,agent 会迷茫。

3. Token 预算要硬封顶。 每个工具的单次返回必须有上限(行数 / 字节 / token),否则一次 grep 返回 100MB 直接把上下文炸了。Claude Code 的 250 行 / 20KB / 25K tokens 是磨出来的数字,借鉴这个思路。

4. 重视"无操作"的优化。 Read 的 mtime 去重看起来不起眼,18% 命中率却很可观。任何"可以不做"的工作都值得一个 fast path。这种优化只能在 stateless 系统里做——一旦你维护了"我读过什么"的状态,反而做不了。

5. 给探索任务专门的子 Agent。 主 Agent 的上下文是宝贵的(要保留判断逻辑、用户对话历史)。把"读 100 个文件然后总结"这种任务委托给一个独立上下文的子 Agent,结果回来时只是一段摘要——这个隔离层很有用。Claude Code 的 Explore Agent 用 Haiku 跑(便宜)+ 只读权限(安全)+ 独立上下文(不污染主 agent),这三件配合起来值得抄。

6. 别把 RAG 当默认选项。 这条不是"别用 RAG"——是别在没想清楚之前默认上 RAG。问自己:我真的需要语义召回吗?我的"知识"会变化吗?变化频率?如果答案是"代码每天改几十次",agentic search 大概率比 RAG 香。

读完 Claude Code 这套设计,我想起 Boris Cherny 那句"它不是产品,是 Unix 工具"。这句话在 2025 年的 AI 时代听起来有点违和——大家都在做"产品"。但他们的判断是:在 agent 这个层级,最值钱的不是"自动决策""智能召回"这些黑盒能力,而是 可预测、可调试、可组合 这些 Unix 哲学早在 1973 年就讲清楚的东西。

模型在变强,工具会变多,但"知道何时遗忘"这件事——把状态扔回文件系统、把决策权交还给 LLM、把架构剥到只剩三个 Unix 工具——这套判断会比任何具体的模型 / 框架活得更久。


参考资料

  • Anthropic 博客《Claude Code: Anthropic's Agent in Your Terminal》;
  • Boris Cherny 公开访谈与播客原话;
  • Finisky Garden《拆解 Claude Code 的 RAG 机制》逆向分析;
  • TIMEWELL Inc. 2026-01 三工具 benchmark;
  • Claude Code 工具系统源码(Tool.ts / GrepTool.ts / GlobTool.ts / FileReadTool.ts)。*

关注公众号「coft」,获取更多 AI 时代的深度思考。