CodeGrep:RL 训练的检索智能体,为 LLM 编码 Agent 提效
- 论文标题:CodeGrep: An RL-Trained Retrieval Agent for LLM Coding Agents
- arXiv 链接:https://arxiv.org/abs/2608.05886(v1,2026-08-06 提交)
- 作者:Wuya Chen(网易广州 AI Lab)、Yihao Yang、Yang Cao(独立研究员)、Yue Lin(网易广州 AI Lab)
- 领域:软件工程(cs.SE)、人工智能(cs.AI)
一、核心问题与动机
现代 LLM 编码智能体(如 Claude Code、OpenHands)存在一个共同的低效问题:大部分 token 预算花在"找到要修改的文件"上,而不是"写补丁"本身。
关键数据:一个 30B 的 OpenHands 智能体在 SWE-Bench Verified 上,每个已解决 issue 平均消耗 23 轮、631K tokens,其中很大比例被 grep、glob、view_file 调用消耗——这些调用常陷入"模糊的探索循环",经常在错误方向上探索 20+ 轮后才放弃。
论文提出两个此前未被联合检验的假设:
| 假设 | 内容 |
|---|---|
| H1(有效性) | 更好的文件级检索能引导智能体避开死胡同探索,提高 resolve rate |
| H2(效率) | 智能体解决大致相同的 issue 集合,但 rollout 更短 |
核心发现:两个假设都不一致地成立。检索质量通过精度阈值(precision threshold)而非线性收益传导到下游:
- BM25(precision 0.375)→ 损害智能体;
- Jina(precision 0.445)→ 中性;
- CodeGrep(precision 0.677)→ 越过阈值,开始"买到"效率(-15% 轮次、-19% tokens、+1.2pp resolve rate)。
二、方法
2.1 系统总览(图 1)
系统分为两个解耦阶段:
- 训练阶段:CodeGrep 在自定义 RL 环境中用 GRPO 优化,监督信号来自 CATM 挖掘的相关性标签;
- 推理阶段:训练好的 CodeGrep 接收 issue 描述,输出一小份候选文件列表,注入冻结的 OpenHands 下游智能体的 prompt 中。
关键设计:只有检索器被训练,下游智能体完全不动,因此任何下游测量结果都可以毫无歧义地归因于检索器对 prompt 的贡献。
2.2 CodeGrep 模型架构
| 项目 | 细节 |
|---|---|
| 基座模型 | Qwen3-14B-Instruct(14B 参数) |
| 工具原语 | 三个只读原语:grep(正则搜索)、glob(路径匹配)、read(文件内容)——模仿开发者浏览陌生代码库的方式 |
| 并行度 | 每轮最多发出 8 个工具调用,并发执行 |
| 轮次上限 | 最多 4 轮(3 轮探索 + 1 轮回答,24 次有效读取) |
| 终止条件 | 产出最终答案或达到轮次上限 |
| 输出格式 | 固定 JSON schema:<answer>{"files": [...], "line_ranges": [...]}</answer> |
注意:line_ranges 字段在下游是"死信息"(OpenHands 编辑工具只消费文件路径),因此在 v3 中将其从训练目标中移除。
2.3 RL 环境:轻量级 Git-worktree 沙箱(图 3)
问题:复用官方 SWE-Bench Docker 镜像在单机训练规模下不可行——每个镜像 1–3 GB,拉取需数分钟,数千个实例需要 TB 级磁盘。而 CodeGrep 训练只读代码,镜像内的 Python 环境纯属累赘。
解决方案(三层架构):
- Layer 1:从任意 SWE-Bench 风格数据集中提取唯一的
(repo, commit)对; - Layer 2:每个 repo 只保留一个
git clone --bare,用git worktree add为每个 commit 实例化轻量工作树——磁盘占用从 N×repo_size 降至 repo_size + N×worktree_size,且无网络 I/O; - Layer 3:rollout 时 grep/glob/read 作为原生子进程在目标 worktree 内运行,带路径穿越保护、64 路并发限制(asyncio 信号量)和每工具超时(grep/glob 10s、read 5s);输出截断至 4096 字符。
效果:每次 rollout 的环境交互从分钟级降到毫秒级,使单机 8×B200 节点上的多轮智能体 RL 可行。此外使用原生 grep -rn --include=*.py 而非纯 Python 实现,吞吐提升约一个数量级。
2.4 CATM:训练数据挖掘
为什么不用 gold patch? SWE-Bench gold patch 触及的文件作为"相关文件"标签是不完整的:它遗漏了那些必须被阅读以理解修复、但本身不被编辑的辅助文件。直接对 patch 信号优化会使检索器偏离期望行为。
CATM(Code Agent Trajectory Mining)采用行为相关性定义:如果某个过去的智能体在解决 issue 时打开了某文件并基于其内容产生了非平凡的推理,则该文件是相关的。
三阶段流水线(作用于来自 Nebius AI 的 67,074 条开源 OpenHands 轨迹):
| 阶段 | 操作 |
|---|---|
| Stage 1(挖掘) | 提取每个文件读取工具调用(str_replace_editor 的 view 命令),规范化路径,丢弃目录、文档(README.md、issue.md)和无扩展名文件。对每个幸存文件 f,记录其后的 assistant 消息作为 post-reasoning,token 长度记为 l(f) |
| Stage 2(裁判过滤) | LLM 裁判(GLM-5.1-FP8,16 路并行,约 5 小时)将每个文件的 post-reasoning 分类为 RELEVANT 或 NOT_RELEVANT;采取保守偏置——任何非明确 NOT_RELEVANT 的输出都视为 RELEVANT |
| Stage 3(强度感知加权) | 沿用 LRAT,每个文件获得其推理长度的指数饱和分数 |
加权公式(β 是 l(f) 的中位数,μ_raw 是 w̃ 的均值,归一化使 E[w]≈1):
真值集构造:对每个 issue x,奖励的目标集为 G(x) = G_patch(x) ∪ {f ∈ L(x) : w_f ≥ 0.15},其中 G_patch(x) 是 gold-patch 文件,L(x) 是 CATM 挖掘集。权重起噪声过滤作用;每个幸存文件都是 F_β 奖励中的硬正例,与 patch 文件地位平等。
产出:31,977 个有效训练样本(47.7% 保留率);被丢弃的部分主要是误入歧途的读取和文档查找,证实了裁判过滤的必要性。
2.5 奖励设计与三次迭代(核心贡献)
组件分数:两个精度偏置的 F_β 统计量(β=0.5):
- 文件级 F_β^file:预测文件集与目标文件集的标准 F_β(边界情形:两者都空得 1 分,恰好一个空得 0 分)。精度偏置反映下游约束:假阳性会污染智能体上下文,而漏检通常可由智能体自身工具调用弥补;
- 行范围级 F_β^lr:预测 p 匹配 gold g 当且仅当文件名相同且 p 覆盖 g 至少 50%;贪心一对一匹配。
效率信号:设 C_total 为 rollout 发起的工具调用总数,T 为轮次数,则每轮平均工具调用数 c̄ = C_total/T(0≤c̄≤8),将 c̄=4 视为自然饱和点。
表 1:三次奖励迭代总结
| 组件 | v1 | v2 | v3 |
|---|---|---|---|
| 基础任务分数 | ½(F_β^file + F_β^lr) | 同 v1 | 仅 F_β^file |
| Reward 层缩放 | 1/max(1, c̄/4) | 1_{c̄>0} | 1_{c̄>0} |
| Advantage 层缩放 | — | s(c̄) | 同 v2 |
- v1(reward 层效率缩放):R = ½(F_β^file + F_β^lr) · σ(c̄),σ(c̄) = 1/max(1, c̄/4) ∈ (0,1]。失败模式:策略漂移升高(KL 达 ~0.31)+ 下游效率几乎为零。
- v2(缩放移至 advantage 层):A_i = A_i · s(c̄_i),s(c̄) = √min(c̄/4, 1)。平方根软化折扣:c̄=1 时 s=0.50(仍保留非平凡梯度信号),c̄≥4 时 s=1。两个动机:(1)原始奖励的组内排名被保留,GRPO 的组内比较始终锚定在任务表现上;(2)折扣只作用于特定 rollout 的梯度步,不影响组内其他成员。v2 解决了漂移,但产生新症状:长度利用(completion 长度冲到 ~2000 tokens,clipping ratio 接近 20%)。
- v3(从基础奖励中移除 line-range):R = F_β^file · 1_{c̄>0},沿用 v2 的 advantage 缩放。理由:(1)接口不匹配——下游编辑器没有 view_range 参数,预测的行范围在推理时是死信息;(2)系统性标签偏置——CATM-only 条目按构造没有 gold 行范围,导致大部分训练数据上 F_β^lr=0。
2.6 训练配方
| 项目 | 配置 |
|---|---|
| 框架 | GRPO,基于 ms-swift,单机 8×B200 |
| 参数高效微调 | LoRA(rank 32, α=64),应用于 14B 基座所有线性投影;基座权重冻结 |
| 显存方案 | 优化器与模型状态空闲时 offload 到 CPU;训练与 rollout 通过 vLLM colocated 模式共享 GPU(tensor-parallel 8,35% 显存给 vLLM,开启 prefix caching) |
| 优化器 | 峰值 LR 5×10⁻⁶,cosine 衰减,5% 线性 warmup;GRPO KL 系数 β=0.02 |
| 批次 | 每步 64 个 prompt(per-device batch 1,梯度累积 8,8 卡),每 prompt 采 8 个 rollout = 每步 512 个有效 rollout |
| 采样 | rollout 上限 4096 生成 token、4 轮工具调用;temperature 1.0,top-p 0.9,top-k 50 |
| 早停 | 第 897 步早停(计划 2000 步),单次运行端到端约 27 小时 |
训练动态(图 2):
- 奖励:v3 收敛最高(0.60–0.65 vs v1/v2 的 0.45–0.48);
- KL-to-reference:v1 在第 900 步漂移到 ~0.31,而 v2/v3 保持在 0.09/0.15;
- 平均工具使用轮次:v3 降至 ~2.1 并保持稳定;v1/v2 在第 500 步后反弹至 ~2.6。
训练稳定性诊断(图 4):(a) 平均 completion 长度——v2 剧烈膨胀(平滑峰值 ~1150 tokens,原始峰值 ~2000),v1 有温和隆起,v3 全程保持 ~300–400;(b) 梯度范数三者全程有界;(c) completion clipping ratio——v2 峰值 ~20%,v1 ~14%,v3 全程低于 6%。
三、实验设置
| 项目 | 设置 |
|---|---|
| 数据集 | SWE-Bench Verified(500 实例) |
| 下游智能体 | OpenHands + Qwen3-30B-A3B-Instruct-2507(temperature 0,最多 100 轮) |
| 报告指标 | resolve rate;效率指标(已解决实例的平均轮次与总 token;未解决实例的平均轮次) |
| 基线复现 | 无检索基线解决 25.8%(23.0 轮、631K tokens/已解决 issue),与公开报告的 25.2±0.7% 在一个标准差内 |
| 对比配置(6 个) | baseline、BM25(top-2)、Jina-1.5B(top-2,稠密嵌入检索器)、CodeGrep v1/v2/v3 |
检索质量内部评估集:从 held-out 的 swe-rebench 实例构建,使用 CATM 挖掘的标签并由团队高级软件工程师审计。评分用 F_β(β=0.5)+ 文件级 precision/recall。选 β<1 的原因:假阳性(注入无关文件)比假阴性代价更高——前者膨胀上下文、用干扰代码稀释注意力,后者常能被智能体自身工具调用恢复。
四、实验结果
4.1 检索质量(表 2,内部评估集)
| Retriever | F_β mean | F_β median | Precision | Recall | F_β≥0.8 | Turns |
|---|---|---|---|---|---|---|
| BM25 | 0.359 | 0.455 | 0.375 | 0.386 | 7.0% | – |
| Jina-1.5B | 0.427 | 0.500 | 0.445 | 0.468 | 7.0% | – |
| CodeGrep v1 | 0.562 | 0.556 | 0.641 | 0.486 | 36.7% | 3.8 |
| CodeGrep v2 | 0.526 | 0.556 | 0.589 | 0.483 | 31.7% | 2.9 |
| CodeGrep v3 | 0.576 | 0.714 | 0.677 | 0.435 | 43.0% | 2.3 |
三个观察:
- CodeGrep 全面压制 BM25 和 Jina,差距主要由 precision 驱动:v3 的 mean F_β 是 BM25 的 1.6×、Jina 的 1.35×;v3 在 43.0% 的实例上产出高质量检索(F_β≥0.8),是 BM25/Jina(各 7.0%)的 6.1×——这正是下游 token 节省最关键的注入区间;
- v3 在质量与成本上 Pareto 占优:median F_β 达 0.714(比 v1/v2 高 +28%),平均轮次沿迭代单调下降 3.8→2.9→2.3;
- v1 与 v2 聚合质量相当:v2 的 advantage 层效率信号"用少量检索质量换训练稳定性";line-range 组件并不妨碍文件定位,只是沿下游从不消费的方向消耗了优化容量。
4.2 下游评估(表 3,SWE-Bench Verified 全 500 实例)
| Config | Resolve | 已解决轮次 | 已解决 token | 未解决轮次 |
|---|---|---|---|---|
| Baseline | 25.8% | 23.0 | 631K | 32.0 |
| BM25 | 25.2% | 22.9 | 763K | 29.7 |
| Jina-1.5B | 25.8% | 23.2 | 587K | 27.8 |
| CodeGrep v1 | 27.0% | 22.7 | 627K | 26.2 |
| CodeGrep v2 | 26.6% | 21.4 | 584K | 26.4 |
| CodeGrep v3 | 27.0% | 19.6 | 514K | 27.5 |
四个观察:
- v3 实现小而可复现的 resolve 提升 + 大得多的效率红利:resolve +1.2pp(25.8%→27.0%),已解决轮次 23.0→19.6(-15%),已解决 tokens 631K→514K(-19%);
- BM25 注入损害下游:resolve 降 0.6pp,已解决 tokens 膨胀 21%;在两者都解决的 94 个实例内,BM25 rollout 多花 6.6% 轮次和 38.6% tokens;
- Jina 注入大致中性:resolve 与基线持平,已解决 tokens 仅降 7%——方向与 CodeGrep 一致但幅度小一个数量级;
- v1 追平 v3 的 resolve rate 但效率不行:v1 也达 27.0%,但仅 -1.3% 轮次、-0.6% tokens;v2 居中。
4.3 精度阈值分析(核心洞见)
检索质量不线性地传导为下游效用,而是呈现单调精度梯度 + 三种状态:
| 状态 | 检索器(precision) | 下游表现 |
|---|---|---|
| (i) 阈值以下:检索有害 | BM25(0.375) | resolve -0.6pp,已解决 tokens +21%;低精度候选成为智能体必须摆脱的"干扰项" |
| (ii) 阈值附近:大致中性 | Jina-1.5B(0.445) | resolve 持平 25.8%,已解决 tokens 仅 -7% |
| (iii) 阈值以上:边际质量增益被 rollout 效率吸收 | CodeGrep(0.677) | v2→v3 median F_β +28%,但 resolve 仅 +0.4pp,而轮次 -8%、tokens -12% |
两个一致机制:
- (a) 文件定位不是 resolve rate 的瓶颈——下游智能体在能解决实例上本就能通过自己的 grep/view 恢复大部分相关上下文,在不能解决的实例上失败通常在补丁合成而非定位;因此更锐利的检索压缩 rollout 而非扩大可解决集;
- (b) 假阳性的边际危害与真阳性的边际收益量级相当,故低精度检索器总体上可为净负面。
要点:三个数据点——BM25(0.375,hurts)、Jina(0.445,neutral)、CodeGrep(0.677,buys efficiency)——描绘出单调梯度,中性到净正面的转变点位于 precision 0.45 与 0.68 之间。该阈值是否迁移到其他下游智能体尚待研究。
4.4 奖励迭代的三种失败模式
v1→v2:reward 层缩放破坏训练稳定性且无法转化为下游效率
- 优势估计失真:GRPO 优势是组内相对的,reward 上乘性缩放重塑组内方差、扭曲优势的符号结构。可观测后果:策略漂移三倍——v1 的 KL 达 ~0.31,是 v2(~0.09)的 3.4×;
- 推理成本更高:v1 平均每次推理 3.8 轮,是 v3(2.3)的 1.65×;
- 下游效率未兑现:v1 虽有 27.0% resolve,但轮次仅 -1.3%、tokens 仅 -0.6%。
v2→v3:修复漂移后暴露长度利用(length exploitation)
- 胜利:v2 最终 KL 稳定在 ~0.09;
- 新失败模式:平均 completion 长度冲到 ~2000 tokens,clipping ratio 接近 20%——advantage 层缩放隐式奖励那些"碰巧得分好的长 completion";
- 检索质量无净增益:v2 的文件级 mean F_β 0.526 反低于 v1 的 0.562;reward 侧的稳定性改善被消耗在 completion 长度而非文件定位上。
v3 闭环:移除 F_β^lr 消除了一个"无下游消费者"且在 CATM-only 条目上系统性为零的信号。相同超参下,v3 奖励收敛更高更快,rollout 轮次稳定在 ~2.1,并交付头条下游收益。
4.5 Pooled vs Paired 效率核算
表 3 的效率数字是汇集(pooled)在各自配置的已解决集合上(baseline 129 个、v3 135 个),混合了真实效率效应与选择效应。限定到两者都解决的 96 个实例做配对比较:轮次 20.1→18.3(-9%),tokens 525K→448K(-15%)——保留了汇集效应的 60–80%,证实改进是真实的逐 issue 加速。
4.6 案例研究:django-15278(表 8)
问题:通过 migration 添加 nullable OneToOneField 在 SQLite 上崩溃(Cannot add a UNIQUE column):django/db/models/fields/related.py 中 OneToOneField 构造函数无条件设 unique=True。
| Baseline | CodeGrep 注入 | |
|---|---|---|
| 动作数 | 74(未解决) | 15(patch 通过) |
| Tokens | 3.2M | 307K(10× 减少) |
| 行为 | 35 次终端调用、17 轮 CoT、18 次文件编辑触及 13 个文件,无一包含修复;前 18 次调用在搜索 oauth2(issue 第三方复现器中的 token,Django 代码库中不存在);最终误改 django/db/migrations/operations/fields.py | 注入 3 个候选文件(含 related.py);动作 6 打开 related.py,动作 8 grep OneToOneField,动作 12 做最小编辑,动作 14 调用 finish;仅触及 1 个工作区文件 |
机制总结:CodeGrep 并不提供修复本身——智能体仍需 grep 定位确切定义。它压缩的是冻结智能体否则要燃烧的探索性前缀(针对 issue 散文中从不出现于代码库的 token 做 repo 级 find/grep 扫射),token 节省正源于此前缀。
五、补充对比表
表 4:CATM vs LRAT
| 维度 | LRAT | CATM(本文) |
|---|---|---|
| 目标场景 | Web 检索(Wikipedia QA) | 代码检索(SWE-Bench) |
| 动作模式 | 顺序,每轮 1 次 browse | 并行,每轮最多 8 次 read |
| 推理归因 | 每文档一条推理(一对一) | 一条推理共享给 N 个并行读取(一对多) |
| 裁判模型 | Qwen3-30B-A3B-Thinking-2507 | GLM-5.1-FP8 |
| 数据规模 | 26K 轨迹 → 91K 对 | 67K 轨迹 → 32K 样本(47.7% 保留) |
| 训练方法 | Weighted InfoNCE(稠密检索器) | GRPO(多轮工具调用 RL) |
表 5:关键工程数字
| 项目 | 数值 |
|---|---|
| 工具并发上限 | 64(asyncio 信号量) |
| 每轮最大并行工具数 | 8 |
| 每次 rollout 最大工具执行数 | 32(4 轮 × 8) |
| 原始轨迹 | 67,074 |
| 裁判阈值 | reasoning_tokens ≥ 30,w ≥ 0.15 |
| 有效训练样本 | 31,977(47.7% 保留) |
| 裁判阶段运行时间 | ~5 小时 |
| 训练总时长 | 27 小时(8×B200) |
表 6:与 Cognition SWE-grep 的奖励设计对比(n/s = 未披露)
| 奖励组件 | Cognition SWE-grep | 本文(v3) |
|---|---|---|
| 文件分数 β | 精度偏置(β 未披露) | β=0.5(精度偏置) |
| 行范围评分规则 | n/s | Jaccard ≥50% 重叠 → F_{β=0.5} |
| 基础分数构成 | 文件 F1 与行 F1 的平均 | 仅文件 F_β;行范围已移除 |
| 训练数据/标签 | 专有 | 开放无监督 CATM 流水线 |
| Advantage 层效率信号 | "按每轮平均工具调用数缩放"(函数形式 n/s) | s(c̄)=√min(c̄/4,1),monkey-patch 到 GRPO advantage |
| 显式奖励设计研究 | 无(单一最终配置) | v1→v2→v3 三次训练运行 |
表 7:传统 RLHF vs 本文 Agent RL
| 维度 | 传统 RLHF | Agent RL(本文) |
|---|---|---|
| Rollout 结构 | 单轮 | 多轮(最多 8 轮) |
| 动作空间 | 单个 completion | 每轮最多 8 个并行工具调用 |
| 奖励来源 | 人类偏好模型 | 环境 + 基于规则的 F_β |
| 环境 | 无 | 带真实代码执行的文件系统沙箱 |
| Loss mask | 整个 completion | 工具响应必须被 mask 掉 |
| 数据构造成本 | 低 | 高(裁判推理 + 全局 β 统计) |
六、图表说明
| 图/表 | 内容 |
|---|---|
| 图 1 | 系统总览:训练(蓝)从开源智能体轨迹经 GRPO 产出 CodeGrep 权重;推理(橙)将训练好的 CodeGrep 作为检索子模块,输出注入冻结下游智能体的 prompt |
| 图 2 | 三次奖励迭代的训练动态:(a) Reward:v3 爬升更快、平台更高;(b) KL-to-reference:v1 的 reward 层缩放将 KL 推至 ~0.31,v2/v3 保持 0.09/0.15;(c) 工具使用轮次:v3 稳定在 ~2.1 |
| 图 3 | 三层 RL 环境架构(repo/commit 提取 → bare clone + worktree → 原生工具子进程),右侧与 Docker 沙箱对比 |
| 图 4 | 训练稳定性诊断:(a) 平均 completion 长度(v2 剧烈膨胀、v3 稳定);(b) 梯度范数(三者有界);(c) clipping ratio(v2 ~20%、v3 <6%) |
| 表 1 | 三次奖励设计迭代总结 |
| 表 2 | 内部评估集上的检索质量 |
| 表 3 | 六种配置的下游对比 |
| 表 8 | django-15278 的动作级轨迹对比 |
七、局限与结论
局限性:CodeGrep 的贡献集中在 rollout 成本而非 resolve rate 上;自然的下一步是针对下游 resolve-rate 信号做端到端协同训练,检验检索能否同时买到效率与有效性。精度阈值是否迁移到其他下游智能体也仍是开放问题。
结论:本文提出 CodeGrep——一个开源的 14B RL 训练 agent-style 代码检索器。注入冻结的 OpenHands 下游智能体后,它在 SWE-Bench Verified 上提升 resolve rate +1.2pp,并在已解决实例上削减 15% 轮次和 19% tokens。研究还揭示了:(1) 支配"检索何时有助于下游"的精度阈值;(2) 在本文训练设定下,advantage 层(而非 reward 层)是 GRPO 风格多轮训练中效率信号的恰当施加位置。所有工件(模型、训练流水线、RL 环境、评估工具)将被开源。
八、个人点评
- 务实的问题定位:把"检索"从编码智能体中解耦出来单独训练、单独归因,下游智能体完全冻结,实验归因干净,是这篇论文方法论上最值得借鉴的地方。
- 精度阈值是核心概念贡献:检索对下游智能体不是线性增益,而是 hurts / neutral / buys efficiency 三态,转变点在 precision 0.45–0.68 之间。这解释了为什么很多"给 agent 加 RAG"的工作效果不显著。
- 奖励工程的三次迭代(v1→v2→v3)是少见的公开失败案例记录:reward 层缩放导致 KL 漂移 3.4×,advantage 层缩放又引发长度利用,最终靠"砍掉下游不消费的监督信号"闭环——对做 agentic RL 的人有直接参考价值。
- 工程细节扎实:git-worktree 沙箱把环境搭建从分钟级降到毫秒级、CATM 从 67K 真实轨迹挖掘弱监督标签,两者都是可复用的基础设施贡献。