文章

为什么 Claude Code 不用 RAG 检索代码,而是用 grep?

  1. 核心问题
  2. RAG 在代码场景的五大痛点
    1. 痛点 1:代码不像散文,切不动
    2. 痛点 2:精确匹配的活,向量干不了
    3. 痛点 3:代码每天在变,索引咋办
    4. 痛点 4:冷启动慢得要命
    5. 痛点 5:黑盒,不可解释
  3. Claude Code 的反向思路:把检索还给模型自己
  4. 检索三件套详解
    1. Grep:基于 ripgrep,绝不是简单封装
    2. Glob:按文件名找,按修改时间排序
    3. Read:按需读取,绝不贪心
    4. 三件套的组合用法
  5. 当三件套不够用:派子 agent 去探索
    1. 检索系统层次
  6. LLM-driven 的多轮迭代循环
  7. 回到原题:为什么 Claude Code 不用 RAG?
  8. RAG 还有用吗?
  9. 开放问题
  10. 总结

原文:字节面试官:”为什么Claude Code不用RAG检索代码,而是用grep?”我:”因为…省钱?” 他沉默了三秒

核心问题

字节面试官问:“为什么 Claude Code 不用 RAG 检索代码,而是用 grep?”

RAG 在代码场景的五大痛点

痛点 1:代码不像散文,切不动

文章是流式的,拦腰切一刀损失不大。但代码有严格结构:

  • 一个函数 200 行,按 100 行切段,上半段是 if 开头,下半段是 else 结尾
  • 函数 A 调用函数 B,但 A 和 B 在不同片段里,模型只看到 A 不知道 B 是干嘛的

痛点 2:精确匹配的活,向量干不了

向量召回的本质是「找相似的」,不是「找对的」。

你跟 Claude Code 说:「帮我看下 getUserById 这个函数的实现」。

向量召回会找一堆「跟用户相关的查询函数」:getUserByNamegetUserByEmailfetchUserInfoqueryUser… 但你要的就 getUserById 这一个。

向量擅长「模糊」,但代码很多时候要的就是「精确」。

痛点 3:代码每天在变,索引咋办

建好索引后,开发同学一个 commit 改了 20 个文件:

  • 要重建?整个项目重新切片、重新 embedding,成本爆炸
  • 不重建?模型查到的全是旧版本,用过期信息写 bug
  • 做增量更新?一堆边界情况:哪些 chunk 要删、哪些要重新算、跨文件引用怎么同步

痛点 4:冷启动慢得要命

百万行代码库跑 RAG,光建索引就要十几分钟。用户打开工具看着进度条转几分钟?体验直接劝退。

痛点 5:黑盒,不可解释

向量召回回来 5 个片段,你问为什么是这 5 个?没人答得上。

出了 bug 不知道从哪儿查起:是召回错了?还是召回对了但模型理解错了?追溯链路非常痛苦。

Claude Code 的反向思路:把检索还给模型自己

程序员的检索工作流:

  1. 看目录结构:ls 一下心里有数
  2. 全局搜关键字:grep -r
  3. 看具体文件:cat 或编辑器打开

Claude Code 的设计哲学:让模型像程序员一样,自己去找。

工具就给三个:

工具作用对应程序员的操作
Glob按文件名 pattern 找文件find
Grep按内容关键字找代码grep -r
Read按需读文件内容cat / 编辑器打开

检索三件套详解

Grep:基于 ripgrep,绝不是简单封装

三层考虑:

  1. 权限统一管控 - Bash 太万能,grep 单独包成工具是一道权限闸门
  2. 输出格式可控 - 结构化输出:行号、上下文行、按文件分组,三种粒度
  3. 性能 - ripgrep 是 Rust 写的,多线程并行,自动尊重 .gitignore

Glob:按文件名找,按修改时间排序

两个小巧思:

  1. 结果按修改时间倒序排列(最近改过的最相关)
  2. 结果有 100 文件硬上限(避免输出爆炸)

Read:按需读取,绝不贪心

  • 默认只读 2000 行,超出截断
  • 可指定 offsetlimit 分段读取
  • 每次直接 stat 磁盘文件,不缓存、不索引、不预处理

这意味着:只要你刚改了文件,下一次 Read 立刻能看到新内容。没有索引层,就没有索引滞后。

三件套的组合用法

场景:「这个项目登录功能在哪实现的?」

  1. Glob 找候选文件:**/*login*.{ts,tsx,js} → 拉回 5 个候选
  2. Grep 在这些文件里搜关键字:passport|auth|login → 定位到具体命中行
  3. Read 读命中文件的相关行段 → 看具体实现

关键:每一步都基于上一步结果调整方向,没有「一次性召回所有代码」。

当三件套不够用:派子 agent 去探索

问题:「调研一下整个项目的认证模块流程」这种任务,需要十几个工具调用,主 agent 上下文会被中间结果塞满。

解决方案:派一个子 agent 出去探索。

机制:

  1. 主 agent 通过 Agent 工具派子 agent,子 agent 有独立的上下文
  2. 子 agent 拿只读工具池:Grep、Glob、Read、Bash(只读命令)
  3. 子 agent 在自己的上下文里多轮迭代,grep 几十次、read 几十次
  4. 子 agent 完成后,只把最终结论返回给主 agent

临界点:少于 3 次查询直接用 Grep/Glob/Read,多于 3 次就派 Explore 子 agent。

加分项:子 agent 可以并行派多个,同时调研多个模块。

检索系统层次

  • 底层:Grep / Glob / Read 三件套,处理简单定向检索
  • 中层:派 Explore 子 agent,处理开放式探索和上下文隔离
  • 上层:主 agent 编排整体任务

LLM-driven 的多轮迭代循环

RAG 的范式:「考试发卷子」→ 用户提问 → 一次性召回 Top-K → 一次性生成答案。没有循环,没有反悔。

Claude Code 的范式:「现场探案」→ 用户提问 → Grep → 看结果 → 「嗯 Read 一下」→ 看内容 → 「找到了」。

每一步:模型说话 → 可能带 tool_use → 执行工具 → 把结果回填到对话历史 → 模型继续说话。直到模型自己说「我搞定了」。

关键能力:每个回合都能根据上一步结果调整方向:

  • Grep 结果是空的?改关键字再搜
  • Read 出来的代码不像你想的?再 Grep 相关函数
  • 文件引用了另一个文件?跟过去看下那个文件

RAG 召回错了就是错了,模型只能将错就错。Agent 召回错了,下一轮自己就调整了。

回到原题:为什么 Claude Code 不用 RAG?

六个原因:

#原因说明
1冷启动grep 毫秒级响应,RAG 分钟级冷启动
2实时性grep 每次现读磁盘最新版本,RAG 索引会滞后
3精确性grep 是确定性字符匹配,RAG 是向量近似匹配
4Token 经济grep+Read 按需读取,RAG 要给整个代码库做 embedding
5可解释性grep 每步透明可审计,RAG 的 Top-K 召回是黑盒
6决策权grep 让 LLM 自己决定每轮搜什么,RAG 一次性丢材料

两种设计哲学

  • RAG 派:LLM 不够强,用工程手段帮它把材料准备好(chunking、embedding、向量召回,本质都是「替模型做决定」)
  • Claude Code 派:LLM 已经足够强,工程的角色是给它准备好工具,把决策权还给它

Anthropic 押注的是「模型会越来越强」,这是个长期主义的选择。

RAG 还有用吗?

RAG 仍然有适合的场景:

  1. 巨型代码库 + 跨仓库检索 - 千万行级别,grep 性能扛不住
  2. 纯语义查询 - 「找一下处理用户认证相关的代码」这种模糊问题
  3. 多人协作知识库 - 代码 + 文档 + Wiki 混合检索

Claude Code 的 grep 方案 适合:

  • ✅ 单项目
  • ✅ 探索式开发
  • ✅ 需要精确性
  • ✅ 要求实时性

工具是为场景服务的,没有银弹。

开放问题

如果未来 LLM 的上下文窗口能到 1 亿 token,整个代码库都能塞进去,RAG 还有意义吗?grep 还有意义吗?

总结

Agent 不是带工具的聊天机器人,而是会自己做决策的执行体。工程师的职责是给它一套好用的工具,而不是替它做决策。

本文由作者按照 CC BY 4.0 进行授权