前言

这篇文章讲清楚 Grep 和 RAG 在 AI Agent 里到底是怎么工作的——从原理到参数传递、返回结果,再到两者怎么配合使用。

如果你在做 Agent 开发,或者好奇 Claude Code / Cursor 是怎么"读懂"你的代码库的,这篇文章给你一个完整的认知框架。


一、先搞清楚 Agent 为什么需要检索

LLM 的上下文窗口是有限的。哪怕 Claude 有 100K token,一个中型 Java 项目轻松几十万行,全塞进去根本不现实

所以 Agent 在回答你之前,需要先"找到相关代码",再把这部分内容送进 LLM。这个"找"的过程,就是检索。

用户提问
   ↓
检索层(Grep / RAG)
   ↓
取出相关代码片段
   ↓
拼成 Prompt 送给 LLM
   ↓
LLM 回答

检索的质量直接决定 LLM 回答的质量。找错了,LLM 再强也没用。


二、Grep:最古老也最直接的检索

Grep 是什么

Grep 是 Unix 上的文本搜索工具,1974 年诞生,比 LLM 早了半个世纪。

它的工作原理极其简单:逐行扫描文件,找出匹配正则表达式的行

grep -r "getUserById" ./src
# 返回结果
src/service/UserService.java:23:    public User getUserById(Long id) {
src/controller/UserController.java:45:        User user = userService.getUserById(userId);
src/test/UserServiceTest.java:12:        User result = service.getUserById(1L);

就这么简单。没有任何"智能",纯粹的字符串匹配。

Grep 在 Agent 里怎么工作

以 Claude Code 为例,当你问"帮我看看 getUserById 这个方法有没有问题",Agent 内部大概是这样的:

第一步:Agent 生成 tool call

LLM 决定调用 grep 工具,生成如下参数:

{
  "tool": "grep",
  "params": {
    "pattern": "getUserById",
    "path": "./src",
    "flags": ["-r", "-n", "--include=*.java"]
  }
}

参数说明:

  • pattern:要搜索的正则表达式或字符串
  • path:搜索范围,缩小范围能节省 token
  • flags
    • -r 递归搜索子目录
    • -n 显示行号
    • -l 只返回文件名(不返回内容,省 token)
    • --include=*.java 只搜 Java 文件

第二步:工具执行,返回结果

{
  "matches": [
    {
      "file": "src/service/UserService.java",
      "line": 23,
      "content": "    public User getUserById(Long id) {"
    },
    {
      "file": "src/controller/UserController.java", 
      "line": 45,
      "content": "        User user = userService.getUserById(userId);"
    }
  ],
  "total_matches": 3
}

第三步:Agent 拿到结果,再读具体文件

光有行号不够,Agent 还会调 read_file 工具,读取定义所在函数的上下文(比如前后 30 行),然后一起塞进 Prompt。

第四步:最终 Prompt 大概长这样

用户问题:帮我看看 getUserById 这个方法有没有问题

相关代码(来自 UserService.java:20-35):
```java
@Service
public class UserService {
    
    public User getUserById(Long id) {
        return userRepository.findById(id)
            .orElseThrow(() -> new RuntimeException("User not found"));
    }
}

请分析这段代码是否有问题。


### Grep 的优势和局限

**优势:**

- **精确**:找到的就是你要的,零噪音
- **快**:毫秒级,不需要任何预处理
- **透明**:你能完全看懂它在搜什么
- **无依赖**:不需要向量数据库、不需要 embedding 模型

**局限:**

你问:“处理支付失败的重试逻辑在哪里?”

Grep 能搜什么?
→ “支付失败” ✓(找中文注释还行)
→ “retry” ✓(英文关键词)
→ “处理支付失败的逻辑” ✗(语义描述,没有对应的字符串)


Grep 是**字面量匹配**,不理解语义。你得猜出代码里用了什么词,才能搜到。

---

## 三、RAG:让检索理解语义

### RAG 是什么

RAG = **Retrieval-Augmented Generation**,检索增强生成。

核心思路是:把文档/代码转成向量(embedding),存进向量数据库,查询时把问题也转成向量,用**向量相似度**找最相关的内容。

              预处理阶段(提前做)

代码文件 → 切块 → Embedding 模型 → 向量 → 向量数据库

查询阶段(实时做) |
用户问题 → Embedding 模型 → 查询向量 → 相似度搜索

Top-K 相关片段

拼 Prompt → LLM


关键在 **Embedding**:它把文字变成一串数字(比如 1536 维的向量),语义相近的文字在向量空间里距离也近。

“处理支付失败” 的向量 → [0.23, -0.15, 0.87, …]
“payment retry logic” → [0.25, -0.13, 0.84, …] ← 向量很接近!


所以即使你用中文问,也能找到英文代码里的相关逻辑。

### RAG 在 Agent 里怎么工作

**预处理阶段**(项目打开时/代码变更时触发):

  1. 遍历代码文件
  2. 按一定策略切块(chunk)
  3. 每块调 embedding API,得到向量
  4. 存入向量数据库(Faiss / Pinecone / Chroma 等)

切块策略很关键,后面细讲。

**查询阶段**(用户提问时):

**第一步:问题向量化**

```json
{
  "input": "处理支付失败的重试逻辑",
  "model": "text-embedding-3-small"
}

返回:

{
  "embedding": [0.023, -0.156, 0.872, ...(1536)]
}

第二步:向量数据库检索

results = vector_db.search(
    query_vector=query_embedding,
    top_k=5,           # 返回最相似的 5 个片段
    threshold=0.75     # 相似度阈值,低于这个就不要
)

返回结果:

[
  {
    "score": 0.92,
    "file": "src/payment/PaymentRetryService.java",
    "chunk": "public void retryFailedPayment(Payment payment) {\n    int maxRetries = 3;\n    for (int i = 0; i < maxRetries; i++) { ...",
    "start_line": 45,
    "end_line": 78
  },
  {
    "score": 0.87,
    "file": "src/payment/PaymentConfig.java", 
    "chunk": "// 支付失败重试间隔配置\npublic static final int RETRY_INTERVAL_SECONDS = 30;",
    "start_line": 12,
    "end_line": 15
  }
]

第三步:拼 Prompt

用户问题:处理支付失败的重试逻辑在哪里?

相关代码片段(相似度 0.92):
[PaymentRetryService.java:45-78]
public void retryFailedPayment(Payment payment) {
    ...
}

相关代码片段(相似度 0.87):
[PaymentConfig.java:12-15]
// 支付失败重试间隔配置
...

切块策略:RAG 质量的关键

切块切得好不好,直接影响 RAG 的噪音多少。

按固定行数切(差):

第 1 块:第 1-50 行
第 2 块:第 51-100 行  ← 可能把一个函数从中间切断

函数被切断,embedding 的语义就乱了。

按 AST 节点切(好):

第 1 块:UserService 类的 getUserById 方法(完整)
第 2 块:UserService 类的 saveUser 方法(完整)
第 3 块:UserService 类的类注释 + 字段声明

按函数/类边界切,每块都是完整的语义单元,embedding 质量高得多。

Cursor 的 RAG 噪音相对少,主要原因就是用了 AST-aware chunking。


四、两者的本质区别

          Grep                    RAG
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
匹配方式  字面量 / 正则           语义相似度
适合场景  "找 getUserById"        "找处理支付失败的逻辑"
精确度    高(找到就是)           中(可能有噪音)
速度      极快(毫秒)             较慢(需 embedding)
预处理    无                       需要建索引
token消耗 高(返回原始文本)       中(返回精选片段)
依赖      无                       向量数据库 + embedding 模型

Grep 是关键词搜索引擎,RAG 是语义搜索引擎。两者解决的是不同类型的问题。


五、实际 Agent 里是怎么配合用的

主流 Agent 的策略不是"选一个",而是分层检索

用户问题
   ↓
问题分类
   ├── 精确查询(找某个符号/文件)
   │       ↓
   │      Grep
   │       ↓
   │    精确结果
   │
   └── 语义查询(找某个功能/逻辑)
           ↓
          RAG
           ↓
       候选片段(Top-K)
           ↓
     Grep 二次验证(确认准确位置)
           ↓
       精确结果

Claude Code 的实际做法

Claude Code 主要用 grep,但它很聪明地控制了 token 消耗:

  1. 先用 -l flag 只搜文件名,不返回内容
  2. 确定文件后,只读相关区域(不是整文件)
  3. 多次 grep 缩小范围,再精读
# 第一步:找有哪些文件
grep -rl "getUserById" ./src
# 返回:src/service/UserService.java

# 第二步:在该文件里找精确位置  
grep -n "getUserById" src/service/UserService.java
# 返回:23:    public User getUserById(Long id) {

# 第三步:读取第 20-40 行的上下文
# 只读这 20 行,而不是整个文件

Cursor 的实际做法

Cursor 以 RAG 为主,grep 为辅:

  1. 用户提问 → 先走 RAG,拿到语义相关片段
  2. 对 RAG 结果里的符号,用 grep/AST 找精确定义
  3. 两路结果合并送给 LLM

六、为什么主流还是 Grep

看完 RAG 这么强大,你可能会问:为啥 Claude Code 不用 RAG?

原因很现实:

1. 冷启动问题

RAG 需要先对整个项目建索引。一个中型项目,embedding 几千个文件,可能要几分钟。用户打开项目就要等,体验很差。

2. 索引维护成本

代码随时在改。改了一个文件,索引就过期了。增量更新索引比听起来复杂。

3. 部署复杂

需要运行向量数据库,占内存,还要维护 embedding 模型版本。grep 只需要一行 shell 命令。

4. 噪音问题依然存在

RAG 的 Top-K 结果里,相似度排第 3 的片段可能完全不相关,但还是被塞进了 Prompt,浪费 token 还可能干扰 LLM。

5. Grep 对大多数任务够用

80% 的 Agent 任务是精确符号查询,grep 完全胜任。


七、实战建议:自己做 Agent 怎么选

小项目(< 5万行)

直接用 grep,够快够准,不引入额外复杂度。

中大型项目(> 10万行)

用混合策略:

精确查询 → grep
         
语义查询 → RAG(AST-aware chunking)
              ↓
         命中结果 → grep 精确定位

提升 grep 效率的几个技巧:

# 只返回文件名,减少 token
grep -rl "pattern" ./src

# 限制文件类型
grep -r "pattern" --include="*.java" ./src

# 排除干扰目录
grep -r "pattern" --exclude-dir={.git,target,node_modules} ./src

# 显示上下文(前后 3 行)
grep -n -C 3 "pattern" ./src/UserService.java

提升 RAG 质量的几个技巧:

  1. 用 tree-sitter 做 AST-aware chunking,按函数/类切,不按行数切
  2. 给每个 chunk 加元数据(文件名、类名、方法名),参与 embedding
  3. 设合理的相似度阈值,低于 0.75 的结果直接丢弃
  4. hybrid search:向量相似度 + BM25 关键词搜索取并集,再重排

总结

  • Grep = 精确、快、无依赖,适合符号级精确查询
  • RAG = 语义理解强,适合功能描述式查询,但有噪音和索引成本
  • 实际 Agent 两者配合用,Grep 做精确查,RAG 做语义理解
  • 主流工具现状:Claude Code 以 Grep 为主,Cursor 以 RAG 为主
  • 自己做 Agent:小项目用 Grep,大项目上混合策略

检索层做好了,LLM 才能发挥真正的价值。垃圾进,垃圾出——这条规律在 Agent 里同样成立。


Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐