Appearance
第 11 章 Agent 怎样找到任务相关的代码
本章要解决的问题
第 10 章让 grep、glob、read 等工具能在同一套调度规则下执行,但工具只提供能力,不会自动告诉模型先查哪里、何时扩大范围、哪个候选必须读原文确认。这也是第 6 章留下的问题:上下文的结构已经确定,内容从哪里来还没有解决。项目稍大,就不可能把全部代码放进一次请求。
本章回答:模型怎样逐步找到当前任务需要的代码。先看四个 Agent 都在使用的 Shell 或 glob、grep、read 路线,再说明什么时候值得增加 LSP、混合检索或嵌入索引。
11.1 模型怎样从目录逐步找到所需代码?
先看模型可以用哪些工具找代码。常见能力可以分成四类:
| 工具 | 找什么 | 适合 | 局限 |
|---|---|---|---|
| Glob | 文件名模式(**/*.test.ts) | 摸清项目结构的第一步 | 不看内容 |
| Grep/ripgrep | 内容正则 | 已知要找什么文本(函数名/报错) | 找不到"换个说法"的语义 |
| 语义检索 | 向量+BM25+重排序 | "处理用户认证的代码在哪"这种概念级查询 | 要建索引、要嵌入服务 |
| 符号级(LSP) | 定义 vs 引用 | 重构:精确区分"定义处"和"字符串里的同名" | 要语言服务器、较重 |
没有语义索引时,常见顺序是:先用 glob/list 了解目录,再用 grep 定位文字,然后 read 当前文件,需要追踪定义和引用时再用 LSP。概念查询较多、这条路线经常需要很多轮时,再考虑语义检索。
语义检索(RAG):按"意思"找代码
grep 的根本局限是只能找文本上匹配的内容——搜"用户认证"找不到名叫 check_credentials 的函数。语义检索(即 RAG,检索增强生成,在代码场景下的形态)补的就是这块。一套可落地的方案有四个部件:
text
分块 按函数/类等语义单元切,不按固定字符数盲切;
每块带元数据(路径/符号/语言/内容哈希)
稠密 嵌入向量:语义相近 → 向量相近,负责"换个说法也能找到"
稀疏 BM25 关键词:按词频与稀有度打分,负责精确匹配标识符和报错原文
合并 两路并行召回,重排序模型(reranker)对候选精排,取 top-k优缺点一句话版:
| 擅长 | 概念级查询("处理用户输入验证的代码在哪");陌生代码库探索;跨文件语义召回 |
| 代价 | 索引基础设施(嵌入服务 + 向量库);代码变更后索引过期,要增量重建;代码可能外发给嵌入服务(取决于部署方式) |
| 不适用 | 已知确切函数名或报错文本——grep 一轮就到,语义检索反而绕路 |
它与 grep/read 不是二选一:常见组合是索引出候选,grep/read 验证——索引只给路径和位置,下结论前仍要读磁盘上的真实文件(上面的分页与版本规则同样适用于索引结果)。
符号级检索(LSP):分清"定义"与"引用"
文本搜索的另一个盲区:它分不清 authenticate 在第 42 行是函数定义、第 189 行是调用、注释里那处只是同名文本。LSP(Language Server Protocol,语言服务器协议)就是编辑器"跳转到定义 / 查找所有引用"背后那套机制,让 Agent 拿到符号级的精确答案。
最有价值的场景是重构:重命名一个函数绝不能靠文本替换——函数名可能出现在注释、字符串甚至无关变量里;只有符号级查找能定位真正的定义和全部调用点。这也是 grok-build 和 dsh 在 grep/glob 之外保留可选 LSP 工具的原因,而 codex 连 LSP 都不给——多数场景下 shell 里的 rg 已经够用。
| 擅长 | 重构(改名/移动/找调用链);精确区分定义与引用 |
| 代价 | 每种语言一个语言服务器,进程重、启动慢;构建配置复杂的项目里服务器可能起不来或给出过时符号表 |
| 不适用 | 语义查询(LSP 不理解"处理认证的代码");纯文本扫描(直接 grep 更轻) |
检索和读取结果怎样分页,又不读到过期内容?
目录、搜索结果和文件内容都可能超过单次工具输出上限。不要只返回一段被截断的文本;工具结果至少应包含:
text
items 或 content 当前页内容
truncated 是否还有未返回内容
nextCursor 下一页游标;没有下一页时为空
sourceRevision 工作区或索引版本
contentHash 当前文件或结果来源的内容哈希游标应绑定查询条件和 sourceRevision,不能只是裸行号。分页期间文件或索引版本变化时,旧游标应失效,让 Agent 从新版本重新查询;否则前后两页可能来自不同文件状态。索引只能给候选路径和位置,做修改或下最终结论前仍要读取磁盘上的当前文件。
11.2 四个 Agent 为什么都用现场检索,什么时候才需要索引?
11.1 列出了从 grep/read 到 LSP 和语义检索的能力光谱。接下来不是继续增加工具,而是回答选型问题:为什么四个参考实现都先用当场搜索,哪些任务足以让索引的成本变得值得。
代码检索有哪两种做法?
Cursor 的做法(建索引):为代码库建嵌入索引,从而找到用词不同但含义相关的跨文件代码。代价是需要索引基础设施、代码变化后索引会过期,而且生成嵌入时可能需要外发代码。
不建嵌入索引的做法:让模型反复调用 grep、glob 和 read,逐步缩小查找范围。这样模型最终读到的是磁盘上的当前代码,不需要维护向量索引。代价是可能增加工具轮次和延迟,也不擅长“表达不同但意思相近”的查询。是否会外发代码仍取决于模型部署方式,不能由“不建索引”直接推出。
四个 Agent 现在实际怎样检索?
我们翻了四个源码样本,结论高度一致——提供给模型的检索工具都是朴素的现场搜索+读取,没有一个给模型接入嵌入索引:
text
codex: 干脆没有专用检索工具——模型用 shell 跑 rg/find/cat
("shell 即万能工具"的极简主义)
grok-build: 专用 grep(ripgrep 封装)+ read_file + list_dir + 可选 LSP 工具
dsh: read / grep / glob(ripgrep 驱动)+ lsp 工具
pi: 默认 read + bash;只读模式可启用 grep / find / ls
工具直接读取当前文件,不维护嵌入索引两个容易误读的地方,考据时专门核实过:
- grok 有
xai-codebase-graph(tree-sitter 作用域图:定义/引用/导入,增量索引,缓存在~/.grok/indexes/)——但它不在模型的工具列表里。它服务的是 Web 客户端的导航 UI(用户在浏览器里点"跳转到定义"),走的是 ACP 扩展协议,模型看不到它。 - codex 的
agent-graph-store名字像代码图,实际只保存多 Agent 的 parent/child 关系——它与代码检索无关;不要从名字推导出防止循环创建或限制深度的能力:关系存储层不做环校验,V2 spawn 也不按最大深度拒绝(第 16 章详述)。
产业实证的读法
本次抽查的四个 CLI 形态项目(codex/grok/dsh/pi)均未给模型暴露嵌入索引——但这只是四个样本,不是全行业结论:Cursor 官方明确把语义索引用于 Agent 的上下文与性能,说明其他产品会选择不同做法。是否值得建索引,取决于谁消费索引:
- 服务人:grok 的图谱给了人类用户(IDE 式点击导航)
- 服务 IDE/Agent 产品:Cursor 的嵌入索引同时也喂给它的 Agent
- 这四个 CLI Agent 的共同选择:给模型真文件,不给模型二手索引
下面是基于实现的作者推断,不是四个项目团队公开给出的理由:CLI 更容易直接读取磁盘当前内容,无需处理索引更新;有些企业不允许向额外的嵌入服务发送代码;索引的延迟和收益也要与多轮 grep/read 的成本实测。深度集成 IDE、超大仓库或概念查询较多的产品可能作出相反选择。
那什么时候该建索引?
- 你的产品是 IDE 形态(用户在编辑器里,召回要快、要跨文件)——Cursor 的选择
- 你的用户是人,需要导航/探索 UI——grok 的选择(给 UI 建图)
- 代码库大到需要十几轮 grep/read,且查询偏概念级——考虑混合使用:索引返回候选文件,read 再读取真实内容确认
决定建索引后,最少要实现什么?
建索引不能只是“把文件随便切成 500 token 然后做 embedding”。对代码更实用的起点是:
text
切块 文件 / 符号 / AST 节点 / Markdown 标题,保留父级上下文
元数据 path + symbol + parent heading + language + source kind(原文/生成内容)
+ git/workspace revision + content hash;多租户产品还要带访问范围
召回 BM25 找标识符和报错原文;向量找概念;RRF 等简单方法合并
精排 可选 reranker,只处理已召回的小候选集
确认 返回路径与行号,Agent 必须 read 当前真实文件后才能下结论
更新 文件变更增量重建;删除写 tombstone;查询结果声明 index revision多租户或托管产品必须在召回和排序前先按用户、仓库和分支过滤权限,不能先找出全局 top-k 再把越权结果删掉,否则既可能泄露元数据,也会让用户得到不足 k 条的偏斜结果。
指标名称要分清:前 k 个结果里至少出现一个相关项是 hit@k;全部相关项中有多少进入前 k 个结果是 recall@k;返回的 k 项中有多少相关是 precision@k;第一个相关项平均排在多靠前的位置可用 MRR。还要监控索引多久没有更新、无结果率,以及“候选经真实文件读取后仍被确认”的比例。最后一项持续下降,通常说明索引过期或无关结果在变多。这个方案适合某些 IDE 或超大仓库,不能反过来写成 codex、grok-build、dsh 当前都使用向量索引。
11.3 把镜头拉远:不建索引在 RAG 光谱上的位置
"grep + read 多跳"不是孤例,它处在检索技术光谱的一个明确位置上。这条光谱大致是:
传统 RAG(一次检索,直接生成)。 文档切块、建嵌入索引,查询时召回 top-k 拼进提示词。它先检索一次再生成答案,适合信息需求明确、单一的简单问题。一些实验中,这类问题使用传统 RAG 响应更快,质量与允许模型反复检索的方案相近;面对需要分解和交叉验证的复杂问题时,一次检索往往不够。
让 Agent 自己决定何时检索。 检索不再只在回答前执行一次,而是成为 Agent 随时可调用的工具。模型可以反复查找、换关键词并交叉验证。四个实现的现场检索就是代码场景下的例子:它们检索的是本地代码而不是嵌入库,依靠文件系统找到候选文件。
用文件系统组织知识(OpenViking 一类方案)。 把记忆、资源和技能映射成虚拟文件系统里的目录和文件,再用文件链接连接相关内容。它有一个容易忽视的前提:条目之间必须真的建立链接。否则最终只会得到一堆难以查找的文件夹;写入知识时需要明确要求模型建立这些关系。
结构化索引(RAPTOR / GraphRAG)。 多层摘要树、实体关系图谱,擅长跨文档综合与多跳关系推理。代价是索引构建需要大量模型调用——判断标准很简单:查询经常需要"跨文档综合"才值得投入,只是"找到包含某信息的片段"用混合检索就够。
还有一个值得思考的问题:如果模型能力持续变强,分块和索引会不会最终被“全量输入”取代?四个样本当前都没有这样做:窗口再大,代码库仍可能更大,无关内容也会增加成本和干扰。
四家没做的事,不代表其他场景不需要。超大仓库、跨仓库问答、自然语言找代码时,可以从 grep/read 升级到混合检索,最后再考虑结构化索引。
本章小结
- 四家都不建嵌入索引:Shell 或 grep/glob 多跳 + 读文件确认,靠文件系统当索引
- 这条路线省掉了索引维护、基础设施和代码外发,代价是每次现场检索的 token 与轮次
- 升级选项一,语义检索(RAG):稠密向量 + BM25 混合召回 + 重排,擅长概念级查询;代价是索引基础设施、过期重建与代码外发风险
- 升级选项二,LSP 符号级检索:精确区分定义与引用,重构必备;代价是语言服务器进程与项目配置依赖
- 什么时候升级:超大仓库、跨仓库问答、自然语言找代码;从 grep/read → 混合检索 → 结构化索引逐级上
- RAG 光谱上,四家做的是"智能体化检索"的代码版;传统 RAG 适合单一明确的信息需求,OpenViking/GraphRAG 适合跨文档综合
检索让 Agent 只读取相关代码,但每次 read、grep 和工具结果仍会进入历史。下一章先讨论上下文的分层与渐进式加载——更好的办法是让不相关的内容一开始就不进上下文;第 13 章再处理上下文接近上限时先裁掉什么、何时压缩,以及压缩对缓存的影响。