先记住这个答案
编码 Agent 应采用分层检索策略,而非加载整个仓库。具体包括:先解析仓库结构建立符号索引(文件、类、函数),再针对任务构建调用图和多级依赖分析,在修改前查询相关文件的近期变更历史。这样能将上下文从几十万 token 压缩到数万 token,且保留定位与验证所需的语义信息。
- 分层检索:符号、调用图、变更历史
- 调用图构建需要精确的多级解析
- 近期变更用于避免覆盖未同步代码
符号索引、调用图与变更历史的协同机制
为控制上下文规模,检索需先经过符号索引层。该层解析仓库内的函数、类和全局变量定义,形成全量符号表,但只加载任务相关的入口符号及其定义位置。例如修复 UserService 中的 bug,先按名称召回 UserService 的所有方法列表与所在文件,从而跳过无关文件的全量正文。
接下来构建调用图:以入口函数为起点,递归查询其调用的函数及其被调用位置,直到没有新文件或达到深度上限。该层需要工具支持精确的符号解析,例如 ctags 或语言服务器提供的定位结果。典型配置为深度 3 层,若再扩展则用摘要替代全文,以减少 token 消耗。
最后一层读取近期变更:执行 git log --oneline -n 5 -- <relevant-files> 查看这些文件的最近提交信息,识别哪个文件或函数最近被修改过。此信息用于判断当前任务是否需要感知新增参数或废弃接口,防止生成的修补基于过时的符号签名。
实际场景:修复 50 万行仓库中的空指针异常
假设仓库为 Java Spring Boot 项目,约 50 万行代码,分布在 120 个模块中。用户报告 OrderService.createOrder 在特定条件下抛出空指针,要求 Agent 定位并修复。仓库未使用语言服务器,仅有文件系统访问与 grep。若直接全量加载则需 30 万 token,远超当前模型的 12 万窗口。
Agent 的策略是先从入口符号 createOrder 找出定义文件,grep -n "createOrder" src/main/java/**/*.java 定位到 OrderService.java,再提取该方法的完整源代码块并扫描方法内调用的函数列表:checkStock、applyDiscount 及 saveOrder。对每个内部调用执行同样的检索,按当前栈深度递归,形成调用图,最终获得约 20 个相关文件,每个取该方法前后各 30 行,上下文合计约 2 万 token。
接着以这 20 个文件为条件运行 git log --pretty=format:"%h %an %s" -n 5 --,发现其中 applyDiscount 的最近一次提交新增了 customer.getLevel() 判空条件,而 createOrder 未同步,导致传入 null 时触发异常。Agent 据此推测根因并修改 createOrder 补上判空,再请求相关测试运行验证。结果修复通过 5 个单元测试,且输出的补丁只涉及一处逻辑变更,未引入无关重构。
rel_files=$(cat files.txt); for f in $rel_files; do echo "## $f"; git log --format="%h %an %s" -n 5 -- "$f"; done在项目仓库根目录运行,files.txt 包含 Agent 检索出的相关文件路径。输出每个文件最近 5 条提交信息,用于识别近期变更。实际工具接口应接受路径列表并合并返回提交摘要。
分层检索的失效条件与应对
当任务模糊且入口函数不确定时,符号检索可能无法定位。例如仅一句“修复登录超时问题”却没有明确的函数名,Token 消耗会转向依赖关键词搜索,召回大量页面代码,分层策略退化成漏斗式扫描。此时应让 Agent 先解构问题描述以提取可检索的符号,或运行 grep 定位关键术语对应的配置项,没有可靠的起点时允许它向用户询问入口信息。
调用图深度上限可能遗漏间接依赖的异常。若仓库的 createOrder 通过反射调用 ExecuteOrder,而后者依赖的 logger 为 null,直接调用图查不到该分支。后果是 Agent 生成的补丁未能真正修复根因。处理代价在于需要加入运行追踪与测试反馈:当修复后测试仍失败,应抓取异常堆栈并回溯来源文件,把这些新文件并入检索集,再修正调用图。实际上,为保证覆盖率,有些 Agent 选择在调用图推理失败时回退到 grep 全文检索,以权衡精度。
容易答错的地方
- 检索越全越好导致上下文膨胀
- 一些开发者认为多加载代码能让模型更准确,于是把符号、调用图、甚至测试摘要全部灌入提示词。这种思路忽略了模型注意力机制的容量局限。事实是令牌预算应留给关键上下文,其余信息可用独立工具查询,超出窗口的代码不在提示中反而减少混淆。
- 调用图等同于文件依赖
- 仅分析文件间的 import 关系不足以确定运行时调用路径。import 了一个模块不代表当前任务会调用其中的函数,而反射或动态分发也无法仅靠静态 import 正确表示。正确做法是构建函数级的调用图,而非停留在包依赖层级。
面试官还会怎么问?
为什么不能在首次查询时就构建全仓库调用图?
构建全仓库函数级调用图需解析全部源文件,计算代价接近一次性嵌入整个仓库,违背缩减上下文的初衷。且任务可能只触及仓库一小部分。应当按任务需要做分层延迟构建,而非预先全量索引。
变更历史如何修正 Agent 的上下文准确性?
当相关文件近期有提交,旧代码片段可能缺失参数改动或线程同步调整。加载 git log 能标记活跃度,Agent 可以据此刷新局部提取或采用当前分支快照。对于快速演进的仓库,还应检查提交间隔是否超过阈值。
若任务跨度达到上百个调用点是否可行?
上百个点会超过上下文预算,此时应进行分段规划:让 Agent 先分析主干调用路径并抽象出子问题,为每个子问题单独分层检索和生成补丁。链路可拆解成多个会话,保留中间决策记录。
参考资料
示例用于理解所注明的运行环境与边界;延伸学习可结合原文中的更多案例。