乐于分享
好东西不私藏

编码 Agent 检索文档方案:从 RAG 到 Agentic Search 的工程实践

编码 Agent 检索文档方案:从 RAG 到 Agentic Search 的工程实践

在大模型落地到软件工程的实践中,一个核心矛盾逐渐浮现:代码是活的,文档是死的,而模型上下文是有限的。编码 Agent(如 Claude Code、Cursor、Copilot Workspace)要同时面对“改一行代码”和“理解一个系统设计”两种截然不同的任务。检索策略选错,轻则答非所问,重则引入幻觉 Bug。

本文基于游戏开发与中间件框架的实际落地经验,系统梳理一套“代码走工具,文档走向量,Agent 负责编排”的检索方案。


一、问题背景:为什么单纯 RAG 在代码场景不够用

传统 RAG(Retrieval-Augmented Generation)在知识库问答中表现出色,但在编码 Agent 场景中暴露三个致命问题:

1. 语义相似 ≠ 代码正确

在自然语言中,“用户认证”和“用户登录”高度相似;但在代码中,authenticate() 和 login() 可能是两个完全不同的入口,甚至属于不同服务。向量检索容易把“长得像”但“用不对”的代码段召回给 Agent。

2. 结构信息在分块中被抹除

RAG 的标准做法是把文档切成 256~512 token 的 chunk。这对 Markdown 文档尚可,但对代码而言是灾难:

  • 一个函数被腰斩,前半段在 chunk A,后半段在 chunk B

  • 类的继承关系、调用链、import 路径在分块后断裂

    Agent 拿到残缺代码,要么无法理解,要么“脑补”逻辑。

3. 索引滞后与动态性缺失

活跃代码库的日常状态是:

  • 文件每分钟都在增删

  • 函数名在重构

  • 分支在并行演进

维护一个实时更新的向量索引成本极高,且极易出现“召回已删除代码”的问题。

因此,Anthropic 在 Claude Code 中最终选择了放弃传统 RAG,转向 Agentic Search


二、Agentic Search:让 Agent 自己“翻代码”

1. 核心思想

不再预先建索引,而是赋予 Agent 一组“阅读工具”:

  • Glob:按路径模式查找文件(**/*.service.ts

  • Grep:按正则表达式搜索内容(class\s+Scheduler

  • Read:读取指定文件或行范围

  • LS:浏览目录结构

Agent 像一名资深程序员一样,通过多轮交互逐步缩小范围,最终定位到目标代码。

2. 为什么它对代码更有效

  • 精确匹配grep "Scheduler::Tick" 绝不会误伤 Scheduler::Tock

  • 结构感知:Agent 可以先读 main.cpp,发现 #include "scheduler.h",再去读头文件和实现,复现人类阅读路径。

  • 实时性:直接读取磁盘,永远看到最新代码。

  • 零预处理:无需切词、无需 embedding、无需维护向量库。

3. 代价与局限

  • Token 消耗:一次复杂的代码探索可能需要 10~20 次工具调用,消耗数万 token。

  • 探索成本:在超大型单体仓库(Monorepo)中,盲目搜索效率低下。

  • 依赖起点:如果缺乏入口指引(如 README 或 CLAUDE.md),Agent 容易“迷路”。

结论:Agentic Search 是代码真理源的最佳访问方式,但不适合处理海量非结构化文档。


三、文档检索:RAG 的主战场

虽然代码适合“读文件”,但以下场景 RAG 依然不可替代:

1. 游戏需求文档(PRD / 策划案)

特点:

  • 自然语言为主

  • 包含大量实体定义(英雄、技能、Buff)

  • 存在跨文档引用(“详见 4.3 节”)

检索需求:

  • “暴击系统有哪些设计约束?”

  • “上次赛季的类似机制是怎么定义的?”

这类语义聚合查询,向量检索远优于关键字搜索。

2. 框架与 SDK 说明文档

特点:

  • 混合了叙述、API 签名、代码示例

  • 更新频率低于代码,但高于教科书

  • 需要精确召回特定函数说明

3. 企业知识库

  • 技术规范

  • 历史 RFC

  • On-call 记录

这些不在文件系统工作流内的知识,必须通过 RAG 接入。


四、关键工程细节:从切词到召回

1. 代码文档的切分策略(前文回顾)

  • 原子单元:以单个 API 符号(类 / 方法 / 结构体)为 chunk。

  • 代码保护:代码块(```)绝不跨 chunk。

  • 元数据注入:在 chunk 头部写入结构化前缀:

    纯文本

    纯文本

    [proj: XCore][type: api][symbol: Scheduler::Tick][module: core]说明:驱动调度器前进 dt 秒...
  • 反向链接:记录哪些文档引用了该 API,便于召回教程类内容。

2. 游戏需求文档的切分策略

  • RI(Requirement Item)驱动:每个需求条目(如 RI-120)独立成块。

  • 实体归一:通过 Glossary 将“暴击率”“CRT”“CriticalHit”映射到同一实体。

  • 公式独立:数值公式单独切块,检索“伤害怎么算”时优先命中。

3. 检索链路设计

无论哪种文档,检索链路应遵循 Hybrid Retrieval

  1. 向量检索:BGE-M3 / Voyage-code-2 负责语义召回。

  2. 关键词检索:BM25 负责精确命中符号名(Scheduler::Tick)、ID(RI-120)。

  3. Rerank:使用 Qwen3-Reranker 对 Top-20 候选精排。

  4. 元数据过滤:根据 Agent 提供的上下文(如“当前在战斗模块”)过滤无关结果。


五、混合架构:Agent 作为编排中枢

最终的落地方案不是二选一,而是构建一个路由 Agent

纯文本

纯文本

用户输入   │   ▼┌───────────────────┐│ Intent Classifier │ ← 判断意图类型└───────────────────┘   │   ├─── [代码/符号/修改/Debug] ──→ Agentic Search (Glob/Grep/Read)   │                                     │   │                                     ▼   │                              文件系统(代码真理源)   │   ├─── [设计/需求/概念/历史] ─────→ Hybrid RAG   │                                     │   │                                     ▼   │                              Vector DB + BM25   │   └─── [复杂跨域问题] ───────────→ RAG(缩范围)→ Agentic Search(精读)                                             │                                             ▼                                       文件系统 + 文档库

典型工作流示例

问题:“我们要新增一个暴击触发后的二次判定,会影响哪些代码?”

  1. RAG 阶段:从游戏需求库中召回 RI-120(暴击规则),确认实体为 critical_hit

  2. Agentic Search 阶段:Agent 拿到实体名,执行 grep -n "critical_hit" 或 grep -n "CriticalHit",遍历代码库。

  3. 验证阶段:Read 相关文件的上下文,确认调用链。

  4. 生成阶段:基于检索到的真实代码和规则,生成修改建议。


六、总结与最佳实践

  1. 代码即真理,文件即权威:活跃代码库坚决使用 Agentic Search,拒绝过时的向量索引。

  2. 文档即知识,向量即语义:需求文档、框架手册、历史知识库使用 RAG,注重分块质量。

  3. Chunk 是语义原子:无论是代码还是文档,分块的目标是“能独立回答一个问题”,而非“填满 512 token”。

  4. 混合检索是标配:向量 + 关键词 + Rerank + Metadata 过滤。

  5. Agent 负责决策:让 LLM 判断何时搜索、搜索什么、读到哪里停,而不是硬编码流程。


写在最后

编码 Agent 的检索方案,本质上是在模拟一名高级工程师的工作流:遇到陌生代码就去翻文件,遇到设计问题就去查文档,遇到复杂改动就把两者结合起来。Claude Code 的成功不在于它用了什么新奇算法,而在于它尊重了这一朴素的工程常识。未来的竞争焦点,将从“谁的向量库更大”转向“谁的 Agent 更会找资料”。