在大模型落地到软件工程的实践中,一个核心矛盾逐渐浮现:代码是活的,文档是死的,而模型上下文是有限的。编码 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:
向量检索:BGE-M3 / Voyage-code-2 负责语义召回。
关键词检索:BM25 负责精确命中符号名(
Scheduler::Tick)、ID(RI-120)。Rerank:使用 Qwen3-Reranker 对 Top-20 候选精排。
元数据过滤:根据 Agent 提供的上下文(如“当前在战斗模块”)过滤无关结果。
五、混合架构:Agent 作为编排中枢
最终的落地方案不是二选一,而是构建一个路由 Agent:
纯文本
纯文本
用户输入 │ ▼┌───────────────────┐│ Intent Classifier │ ← 判断意图类型└───────────────────┘ │ ├─── [代码/符号/修改/Debug] ──→ Agentic Search (Glob/Grep/Read) │ │ │ ▼ │ 文件系统(代码真理源) │ ├─── [设计/需求/概念/历史] ─────→ Hybrid RAG │ │ │ ▼ │ Vector DB + BM25 │ └─── [复杂跨域问题] ───────────→ RAG(缩范围)→ Agentic Search(精读) │ ▼ 文件系统 + 文档库
典型工作流示例
问题:“我们要新增一个暴击触发后的二次判定,会影响哪些代码?”
RAG 阶段:从游戏需求库中召回
RI-120(暴击规则),确认实体为critical_hit。Agentic Search 阶段:Agent 拿到实体名,执行
grep -n "critical_hit"或grep -n "CriticalHit",遍历代码库。验证阶段:Read 相关文件的上下文,确认调用链。
生成阶段:基于检索到的真实代码和规则,生成修改建议。
六、总结与最佳实践
代码即真理,文件即权威:活跃代码库坚决使用 Agentic Search,拒绝过时的向量索引。
文档即知识,向量即语义:需求文档、框架手册、历史知识库使用 RAG,注重分块质量。
Chunk 是语义原子:无论是代码还是文档,分块的目标是“能独立回答一个问题”,而非“填满 512 token”。
混合检索是标配:向量 + 关键词 + Rerank + Metadata 过滤。
Agent 负责决策:让 LLM 判断何时搜索、搜索什么、读到哪里停,而不是硬编码流程。
写在最后
编码 Agent 的检索方案,本质上是在模拟一名高级工程师的工作流:遇到陌生代码就去翻文件,遇到设计问题就去查文档,遇到复杂改动就把两者结合起来。Claude Code 的成功不在于它用了什么新奇算法,而在于它尊重了这一朴素的工程常识。未来的竞争焦点,将从“谁的向量库更大”转向“谁的 Agent 更会找资料”。
夜雨聆风