过去很多人习惯用 RAG 理解 AI 检索:先把文档切块,做 embedding,存进向量库;用户提问时,把问题也转成向量,再找最相近的片段塞回上下文。

这套方法放在知识库问答里很自然。用户问的是一个概念,文档里可能是另一种说法,问题和答案不一定共享同一批词。语义相似度可以补上这种词汇不匹配。

代码库则不同,Claude Code、Codex 这类 coding agent 做代码搜索时,主力工具经常不是传统 RAG,而是更朴素的 Grep,准确说是 ripgrep。这不是倒退,代码本身的形态决定了很多检索问题更适合精确匹配。

RAG 解决什么问题

RAG 的目标是把模型没有记住、或者不该靠记忆回答的外部信息取回来。

典型链路是:

  1. 先把文档切成 chunk。
  2. 用 embedding 模型把 chunk 转成向量。
  3. 把向量、元数据和原文位置存进向量数据库。
  4. 用户提问时,把 query 也转成向量。
  5. 做 top-K 相似度搜索,必要时 rerank。
  6. 把命中的片段组装进模型上下文。

这套链路的长处是语义召回。比如用户问“怎么处理用户退出登录”,文档里可能写的是 session invalidationrevoke refresh token。关键词不一致,但语义接近,向量检索有机会把它找出来。

所以 RAG 很适合自然语言知识库、产品文档、客服问答、论文资料库。它处理的是“我不知道答案在哪里,甚至不知道该搜什么词”的场景。

代码检索也会遇到这种问题,但这不是 coding agent 最常遇到的那类问题。

Coding Agent 为什么更常用 Grep

coding agent 面对代码库时,大量任务不是开放式问答,而是定位、追踪和验证:

  • login 在哪里实现?
  • createUser 谁在调用?
  • 这个配置项有没有被读取?
  • 某个错误信息是哪里抛出来的?
  • 这条 API 请求最后落到哪个 handler?

这些问题往往能落到代码里的精确字符串、符号、路径、类型、函数名、错误文本、配置键。

代码里的标识符本身就带着语义。getUserById 不只是一个字符串,它已经把动作、对象和约束写进名字里。错误文案、路由路径、环境变量、class 名、interface 名也一样。对这类信息,精确匹配通常比语义相似度更可靠。

Claude Code 的搜索过程可以概括成一个循环:LLM 自己决定搜什么,调用 Grep / Glob / Read,读到结果后再决定下一轮搜什么,直到信息足够。

它不是一次检索给出答案,而是多轮探索:

  1. 模型看到用户问题和可用工具。
  2. 模型把问题转成搜索词,比如函数名、类名、路径片段、报错文本。
  3. 调用 Grep 找候选文件。
  4. 读取少量相关文件片段。
  5. 根据新线索调整关键词继续搜。
  6. 最后把多轮发现综合成答案或修改方案。

Grep 在这里不是单纯的文本搜索,而是模型推理过程里的探针。模型提出假设,Grep 用很低的成本验证假设。

Grep 绕开了很多工程问题

coding agent 偏爱 Grep,不是因为 Grep 更聪明,而是因为它把很多工程问题都绕开了。

RAG 的前提是预处理。要切 chunk,要做 embedding,要建索引,还要保证索引和真实代码同步。这个链路一旦放进开发环境,就会遇到很具体的问题:第一次打开仓库要不要等索引完成?文件刚改完但索引还没更新怎么办?重命名、删除、生成文件、monorepo 里的排除规则怎么处理?

Grep 的做法很直接:直接查当前磁盘。用户刚打开仓库,agent 就能搜;代码刚保存,下一次搜索看到的就是新内容。它没有“索引是否过期”的状态,也不需要解释为什么检索结果和编辑器里看到的不一致。

权限和隐私也是类似的逻辑。很多 RAG 架构需要把代码块上传到服务端生成 embedding 或做向量检索,即使中间做了加密、去标识化、生成后丢弃原文,对开发者来说依然多了一层信任成本。Grep 在本地文件系统上工作,安全边界很清楚。

Grep 的结果也适合 agent 做下一步决策。它命中了哪个文件、哪一行、哪个字符串,一眼就能判断要不要继续读。向量检索返回的是相似片段,有时语义上接近,工程上却不相关;一旦把 agent 带进错误模块,后面的推理就会越走越偏。

ripgrep 本身也够快。它会遵守 .gitignore,跳过二进制文件,多线程搜索,并用高效的正则实现和 SIMD 优化。在 MB 到几百 MB 级别的本地项目里,暴力扫一遍通常不是瓶颈。真正花时间的,往往是模型如何从搜索结果里组织下一步探索。

Grep 的价值在于把代码检索压回一个很稳定的工程接口:当前文件系统里有没有这个字符串。这个接口不优雅,但可靠、实时、可解释,适合让 LLM 在多轮循环里反复试探。

Grep 也有代价

Grep 路线的问题也很明显。

它会产生多轮工具调用。一次搜索拿到文件列表后,agent 还要读文件;读完发现新符号,再继续搜;复杂问题可能要反复十几次。这会消耗时间、token 和上下文空间。

它也依赖模型提出好关键词。如果模型不知道应该搜 refreshTokensession 还是 auth middleware,就可能绕远路。对于自然语言描述很强、代码命名又不直观的问题,纯 Grep 会吃亏。

它还缺少结构关系。Grep 能告诉你字符串出现在哪里,但不知道哪个函数调用了哪个函数,哪个 route 绑定哪个 controller,哪个 class 继承了哪个 base class。模型可以通过多轮搜索拼出这些关系,但成本不低。

Claude Code 后来引入 LSP,也是这个原因。go to definitionfind references 这类语义精确操作,可以替代一部分 Grep + Read 的路径。它不是向量 RAG,而是代码语言服务提供的确定性语义检索。

从 GraphRAG 到 CodeGraph

Grep 不是终点。它解决了快速找到文本线索的问题,但它不理解代码结构。谁调用了谁,哪个路由绑定哪个 handler,某个符号的影响范围有多大,这些关系需要 agent 通过多轮 Grep 和 Read 慢慢拼出来。

这里可以提 GraphRAG,但要避免把它和 CodeGraph 混成一类。GraphRAG 仍然属于 RAG 家族,只是在文本检索之外引入实体、关系和社区结构,用图改善自然语言资料的召回与综合。CodeGraph 关心的不是如何让文档问答更准,而是如何把代码里的符号和调用关系直接暴露给 agent。两者都重视结构,面对的对象和工程目标不同。

CodeGraph 是一个比较新的代码场景案例。它的 GitHub 描述是 Pre-indexed code knowledge graph for Claude Code,也就是给 Claude Code 准备的预索引代码知识图谱。它用 tree-sitter 解析源码,把函数、类、方法、调用关系、import、继承、框架路由等信息抽取成图结构,存进本地 SQLite 数据库,再通过 MCP 工具暴露给 agent 查询。它给 agent 的不是相似文本片段,而是更接近 IDE / LSP / 静态分析工具的结构化上下文。

这和传统 RAG 的差异很关键。RAG 通常围绕 chunk 和 embedding 工作,目标是从语义相似度里找可能相关的文本;CodeGraph 索引的是代码结构,节点可以是 symbol、route、文件、函数、类,边可以是 calls、imports、references、extends。检索问题也跟着变了:不是哪些片段和我的问题语义相似,而是这个入口点在哪里、调用链怎么走、改这个符号会影响哪些地方。

README 里的 benchmark 也在讲这件事。没有 CodeGraph 时,Explore agent 会大量使用 grep、find、ls、Read,先花时间发现文件,再拼调用关系;有 CodeGraph 时,agent 用少数几次图查询就能拿到相关上下文,甚至不再读取文件。它减少的不是模型思考,而是反复发现文件和拼接关系的机械成本。

这不意味着 CodeGraph 就是比 Grep 或 RAG 更高级的下一代答案。它只是换了一组取舍:为了让 agent 更快拿到结构关系,它重新引入了初始化、索引、语言适配、增量同步和图构建成本。小项目里,直接 Grep 可能更轻;大型仓库、跨模块理解、调用链追踪、影响分析这类任务里,图结构才更容易显示优势。

CodeGraph 更适合作为一个观察窗口:代码检索的新探索不是简单回到传统向量 RAG,也不是说 Grep 已经足够解决一切,而是把全文搜索、符号检索、调用图、路由识别、影响分析这些能力组合起来,让 agent 用更少的探索成本理解代码库。

这不是线性的进化路线。不是 RAG 落后,Grep 更先进,Graph 又比 Grep 更先进。不同检索对象需要不同结构。自然语言文档需要语义召回,代码编辑现场需要实时精确搜索,复杂代码理解需要显式关系图。

不同对象需要不同检索结构

把 RAG、Grep 和 Graph 放在一起看,RAG 面对的是自然语言材料。 自然语言的麻烦在于词汇不稳定,同一个意思可以有很多表达方式。用户问“怎么退出登录”,文档可能写 invalidate session;用户问“权限怎么校验”,文档可能写 policy enforcement。在这种场景里,embedding 的语义召回很有价值。Grep 面对的是正在变化的本地代码。 代码里的很多关键信息不是模糊语义,而是稳定标识符:函数名、类名、路径、错误文本、配置键、路由字符串。这里更看重实时、确定、可解释。agent 可以用 LLM 把自然语言任务翻译成搜索词,再通过多轮 Grep / Read 主动探索。Graph 面对的是代码结构。 它关心的不只是某个词在哪里出现,而是这些符号之间有什么关系。当问题从定位一个函数,变成理解一条调用链、一个模块边界、一组路由入口或一次修改的影响范围时,图结构就比纯文本结果更有优势。

真正的分歧不是谁取代谁,而是你把代码库看成什么。把它看成文档集合,就会自然走向 RAG;把它看成当前磁盘上的文本,就会走向 Grep;把它看成符号和关系组成的系统,就会走向 Graph。

变化发生在检索方式上

过去讨论 RAG,默认检索发生在回答之前:系统先检索,再把结果交给模型回答。

coding agent 的检索方式不同。模型不是被动接收 top-K 片段,而是在任务过程中主动探索。它会先搜宽泛关键词,再根据命中结果缩小范围;看到一个函数名后继续找调用方;发现一个类型后再查定义;读到一个测试文件后反推业务入口。

这种主动探索让 Grep 重新变得有用。单看 Grep,它只是字符串匹配;放到 LLM 的循环里,它变成了可迭代、可验证、可调整的搜索动作。

CodeGraph 也是沿着这个方向发展。它没有把代码库当成一堆自然语言片段,而是把代码当成一个可查询的结构系统。agent 不再只问哪些 chunk 和我的问题语义相似,而是问:

  • 这个 symbol 在哪里定义?
  • 谁调用了它?
  • 它调用了谁?
  • 改它会影响哪些测试?
  • 这个 route 最终进入哪个 handler?
  • 这条跨语言调用链怎么走?

代码检索和普通文档检索最大的区别也在这里。代码不是文章,代码有名字、有路径、有类型、有调用关系、有执行入口。好的检索系统应该利用这些结构,而不是把它们全部压平成 embedding chunk。

结论

Grep 回归,是因为代码检索的高频需求本来就偏精确匹配;LLM 又能把自然语言任务动态翻译成一连串搜索词,让 Grep 从静态命令变成主动探索工具。

CodeGraph 的出现,则说明除了向量 RAG 和纯 Grep 之外,还有一类更重视代码结构的本地索引方案:全文搜索负责找名字和文本,图结构负责找关系,LSP / tree-sitter / 静态分析负责提供确定性语义。

今天的代码检索不是 RAG 和 Grep 二选一。Grep 负责快速、实时、精确地触达代码;CodeGraph 这类工具负责把重复探索和结构推理前置;RAG 仍然适合真正需要语义召回的自然语言资料和模糊概念搜索。

coding agent 用更多 Grep,不是因为 RAG 不高级,而是因为在代码世界里,最有价值的线索往往藏在名字、路径和调用关系里。谁能更低成本地把这些线索拿出来,谁就更适合 agent 的工作流。

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐