1.1 从预测 token 到采取动作
把语言模型写成条件分布 ,即给定前缀 时下一个 token 的分布。预训练和监督微调(SFT)让这个分布去拟合别人写的文本。在标准的 teacher forcing 训练中,下一个前缀取自数据集;模型当前的预测不会改变下一步输入。 智能体(agent)是把同一个分布放进一个与环境(environment)交互的循环中。模型的部分输出被解析为一个动作(action),即对某个工具的调用。环境执行它并返回一个观测(observation),观测被追加到上下文中。模型随后从一个新的前缀继续生成,这个前缀包含不是它写的、而且取决于它做了什么的文本。经过若干轮之后,回合结束,由一个奖励(reward)为结果打分。此时模型就成了强化学习意义上的策略(policy):一条选择动作的规则,而这些动作的后果会反馈到它自己未来的输入中。第 2 章会把这些词定义得更精确。clamp 问题
我们的贯穿示例是一个单行缺陷。它是第 4 章所构建的 BugFix gym 中的任务 0。一个本应把值限制到某个区间内的函数把min 和 max 写反了。我们把问题描述和源代码的两个版本存为常量,这是本章唯一保存的代码。
max(value, upper) 至少为 upper,而 upper 至少为 lower,因此 min(lower, ...) 总是返回 lower。有缺陷的函数恰好能通过答案碰巧为 lower 的那些用例。只检查第二个用例的奖励会认为这段代码是正确的,这是验证器可能被愚弄的第一个迹象。
测试定义成功
问题描述用自然语言陈述意图;测试让意图变得可检查。我们编写两组测试。公开测试(public tests)是智能体工作时可以用它的test 工具运行的测试。隐藏测试(hidden tests)被保留起来,只用于计算最终奖励。这种划分区分了智能体能看到的训练信号和它无法直接拟合的测量。
1.2 逐步走完一个回合
智能体需要工具(tool)、调用工具的动作语法(action grammar),以及执行调用并保存状态的环境。我们使用与第 4 章完整 gym 相同的四个工具和语法:read返回当前的源文件。test运行公开测试,并返回PASS/FAIL行。edit <op>从一个固定菜单中应用一个具名的源代码变换。submit结束回合;随后由隐藏测试计算奖励。
<call> tool [arg] </call>。其他任何写法都是格式错误。下面是一个最简解析器。
swap_minmax 是这一类缺陷的真正修复。hardcode_tests 是一个漏洞利用(exploit):它插入特殊分支,恰好对公开测试的输入返回期望答案,而把缺陷原封不动地留下。我们之所以包含它,是因为一个能接触到类似操作的学习型智能体终究会找到它,而本书需要一种可复现的方式来研究这种情况(第 11 章)。
step 接收策略的文本,解析它,执行工具,并返回 (observation, reward, done)。除 submit 之外,每一步的奖励都是 0;在 submit 时,如果最终源代码通过了所有公开测试和隐藏测试,奖励为 1。格式错误的调用不会抛出异常;它返回一条错误信息作为观测,并消耗一轮,这正是实际的评测框架(harness)必须采取的做法。
step,直到回合结束或轮数预算耗尽。这里的消息来自脚本,而不是模型。对话记录(transcript)记下了每段文本由谁产生。
1.3 智能体强化学习有何不同
先看语言模型后训练中两种常见的强化学习设置。在 RLHF 中,模型对一个提示写出一个回答,由一个学习得到的奖励模型(reward model)为其打分,然后更新策略以提高分数,同时保持与参考模型接近。在 RLVR(基于可验证奖励的强化学习)中,奖励模型被一个程序取代,例如数学答案检查器或单元测试运行。在这里讨论的常见单轮设置中,两者都是一个提示、一个回答、一个奖励;RLHF 和 RLVR 本身并不限定交互轮数。第 2 章表明它们是上下文老虎机(contextual bandit)。上面的回合在六个方面与之不同,每一方面都带来本书其余部分要解决的工程问题。 1. 长时程。 上面的回合做了五个决策,而真实的编程回合要进行几十轮、消耗数万个 token。最终奖励要为 1,每个决策都必须正确。如果每一轮以概率 独立成功,那么一个 轮的回合以概率 成功。hardcode_tests 所展示的。工具集越强大,漏洞利用的空间就越大:编辑测试、读取评分器,或者对特定输入做特殊处理。因此,奖励设计和环境隔离本身就是学习算法的一部分(第 11 章)。
6. 部分可观测。 智能体永远看不到隐藏测试,只能通过它选择读取的内容来看到仓库,并且只有通过测试才能知道一次编辑是否奏效。read 和 test 这类信息收集动作本身不直接获得奖励,但会改变智能体所知道的东西。第 2 章在一个小型 MDP 上对此进行了量化。
1.4 训练栈
模型如何学会产生求解者那样的对话记录,而不是一份随机的记录?典型的智能体强化学习系统包含下面这些组件,本书会逐一实现。在任何组件存在之前,我们就可以用最简单的策略运行这个循环:一个未经训练的智能体,从一个小菜单中均匀随机地挑选每条消息。菜单中包含一条格式错误的消息,使格式错误成为可能。hacked 标记那些提交的源代码通过了所有公开测试、却没有拿到隐藏测试奖励的回合,这正是该漏洞利用的特征。运行 2,000 个回合。
1.5 SFT、偏好、RLVR 与蒸馏的位置
强化学习只是对智能体做后训练的几种方式之一,一个训练流水线可能组合其中的几种。把它们放在同一个框架中看会很有帮助。下面每种方法都会提高某些 token 序列的对数概率,并降低另一些序列的对数概率。它们的区别在于序列从哪里来,以及每条序列如何加权:
在一个小批次上很容易看出权重的差异。假设对同一任务的四次尝试得到的奖励分别为 1、0、0、1。
1.6 公开的参考系统
有几个公开系统表明,在真实仓库上进行强化学习能够提升编程智能体。我们把它们用作规模和设计上的参考点,而不是我们要复现的结果。下面的每个数字都由其作者报告。本书自己的实验是 CPU 规模的机制演示,不是基准测试结果。- SWE-bench(Jimenez et al., 2023)定义了这一任务类型。它的 2,294 个实例中,每一个都是来自 12 个流行 Python 仓库之一的真实 GitHub issue,并配有解决该 issue 的 pull request。当智能体的补丁使仓库的测试通过时即算成功。该基准发布时,它所评测的最好的模型解决了 1.96% 的 issue。下面的智能体系统报告的是在 SWE-bench Verified(SWE-bench 的一个子集)上的结果。
- SWE-RL(Wei et al., 2025)使用开源软件演化数据(issue、代码快照和 pull request)对 Llama 3 进行强化学习。它的奖励是一个轻量的基于规则的分数,衡量生成补丁与真实补丁之间的相似度,而不是执行测试。作者报告其 70B 模型在 SWE-bench Verified 上的解决率为 41.0%。
- R2E-Gym(Jain et al., 2025)从仓库提交出发,通过测试生成和 issue 的反向翻译,以程序化方式构建可执行的训练环境。其环境集被描述为包含超过 8.7K 个任务。作者报告一个 32B 模型在 SWE-bench Verified 上取得 34.4% 的 pass@1,在结合基于执行和不基于执行的验证器进行测试时扩展(test-time scaling)后达到 51%。
- DeepSWE(Together AI and Agentica, 2025)在 R2E-Gym 环境中仅用强化学习训练 Qwen3-32B,在过滤掉出现在 SWE-bench Verified 中的仓库之后使用了 4.5K 个问题。训练框架是 rLLM,算法名为 GRPO++,它组合了若干已发表的 GRPO 改进。其中包括 clip-higher、不使用 KL 损失、不做奖励标准差归一化,以及留一法(leave-one-out)基线,另外还有作者自己的紧凑过滤(compact filtering)。报告给出在 SWE-bench Verified 上 42.2% 的 pass@1(16 次运行的平均值),以及使用混合测试时扩展后的 59%。训练使用了 64 块 H100 GPU,历时六天。
证据。 这些是所引来源报告的论断,已于 2026-10-13 对照这些来源核查(见 sources.md)。本书没有复现它们。基准测试数字取决于评测框架、脚手架(scaffold)、轮数预算和测试时计算量;不同报告之间的数字不能直接比较。DeepSWE 方案的每个组成部分都对应本书的一章。GRPO 及其变体在第 9 章,KL 项及其去除在第 10 章,可执行环境在第 4 章,截断或退化回合的过滤在第 12 章。到综合实战(capstone)一章时,读者应当能够把这样一个方案读成一系列设计决策,并对每一项给出支持或反对的理由。
1.7 路线图
本书组织为一次完整构建。每章把它推导出的函数保存进agentrl 库,后续章节导入它们(第 0 章)。
- 第 I 部分,基础(第 1–5 章)。 在一个表格型修复 MDP 上建立强化学习形式化框架(第 2 章);作为策略的语言模型,以及本书训练的小型 transformer TinyAgent(第 3 章);包含工具、解析器和测试运行器的 BugFix gym(第 4 章);以及把多轮回合转换为 token id、掩码和对数概率(第 5 章)。
- 第 II 部分,策略优化(第 6–10 章)。 从第一性原理推导策略梯度(6),优势与回报归因(7),PPO 与安全的样本复用(8),GRPO 与不依赖 critic 的方法(critic-free methods),以及本书的主训练循环(9),KL 与熵正则化(10)。
- 第 III 部分,奖励与智能体(第 11–12 章)。 验证器、奖励模型与奖励投机(11),多轮行为与长时程(12)。
- 第 IV 部分,系统(第 13–14 章)。 rollout 架构、异步,以及训练与推理对数概率之间的不一致(13),打包、批处理与数据过滤(14)。
- 第 V 部分,超越纯强化学习(第 15–16 章)。 SFT、拒绝采样与偏好(15),面向智能体的蒸馏(16)。
- 第 VI 部分,实践(第 17–20 章)。 评测(17),调试强化学习训练(18),端到端训练一个编程智能体的综合实战(19),以及研究前沿(20)。
1.8 实践中的设计问题
问题:选择动作的单位。 现象: 团队争论优势、比率和 KL 惩罚应当按 token、按轮还是按回合计算,而代码库的不同部分悄悄地采用了不同的答案。 原因: 一条轨迹可以有三种合理的 MDP 建模方式(第 2 章 2.8 节)。模型的概率是按 token 的,环境按轮行动,而奖励按回合到达。 可选方案: (a) 一切都在 token 级,这与模型完全匹配,但时间跨度长达数千个决策;(b) 轮级优势配合 token 级对数概率,回报归因更好,但需要逐轮的价值估计或奖励;(c) 单个回合级优势广播到每个策略 token,简单且无需 critic,但对无用动作和决定性动作分配相同的功劳。 建议做法: 按 token 计算对数概率和比率,按轮施加掩码,并从回合级优势开始。当测量表明无用动作正在被强化时,再转向轮级回报归因(第 12 章)。
问题:决定什么算作成功。
现象: 训练中奖励不断攀升,而留出集上的质量没有提升;或者智能体学会了 hardcode_tests 这样的行为。
原因: 奖励是对意图的一种测量,而任何策略能观察或影响的测量都可以被直接优化。在 1.2 节中,公开测试的成功与真正的成功在一次编辑之后就分道扬镳了。
可选方案: (a) 智能体无法运行的隐藏测试,稳健但每个任务需要更多测试;(b) 与参考补丁的相似度,如 SWE-RL 所报告的,廉价且无需执行,但会惩罚同样有效的其他修复;(c) 学习得到的评判模型,灵活但本身可被利用;(d) 组合使用,并监控已知的漏洞利用。
建议做法: 在隔离环境中执行留出测试,智能体的工具无法读取或修改它们,再加上对你能说出名字的漏洞利用的显式检查。让评测奖励与训练奖励保持分离(第 11 章)。
问题:判断强化学习相比 SFT 何时值得其代价。 现象: 一次强化学习训练烧掉了若干 GPU 天,奖励却纹丝不动;或者基于示范的 SFT 在需要从错误中恢复的任务上进入平台期。 原因: 强化学习只能从它抽取的样本之间的奖励差异中学习。如果某个任务在当前策略下的成功率为 ,我们采样一个包含 次尝试的组,那么只有当组内同时包含成功和失败时,该组才携带学习信号,其概率为 。 可选方案: (a) 当大多数任务上 接近 0 或格式错误占主导时,先做 SFT 或冷启动;(b) 当存在可靠的验证器、且在相当比例的任务上 既不为 0 也不为 1 时,使用强化学习;(c) 使用课程或过滤,把任务保持在可学习的范围内(第 14 章);(d) 当存在更强且负担得起的教师时,使用蒸馏(第 16 章)。 建议做法: 在做选择之前,先逐任务测量起始策略的通过率。当这一分布有一部分任务的通过率介于全失败和全成功之间时使用强化学习;否则先修正起始策略。有信息量的组所占的比例很容易计算。
小结
- 智能体是一个被放进与环境交互的循环中的语言模型:它的动作是被解析的工具调用,它的观测是追加到上下文中的工具输出,一个奖励为完成的回合打分。
clamp回合展示了完整的接口:工具read、test、edit <op>和submit,调用语法为<call> tool [arg] </call>。公开测试引导智能体,隐藏测试定义奖励。- 智能体强化学习与单轮的 RLHF 和 RLVR 的区别在于:时间跨度长,存在不是策略生成的观测 token,有环境延迟,依赖验证器,容易被漏洞利用,以及部分可观测。
- 训练栈由 rollout、奖励、优势、带掩码的更新和评测组成,并由系统层进行调度。后面的每一章构建其中的一部分。
- SFT、拒绝采样、偏好学习、强化学习和蒸馏都在提高和降低序列的对数概率。它们的区别在于序列从哪里来,以及每条序列如何加权。强化学习和 on-policy 蒸馏在策略自身的状态上学习。
- 强化学习需要奖励存在差异:在选择它之前,先测量起始策略的通过率。
练习
- 编写第三个编辑操作
drop_min,把min(lower, max(value, upper))替换为max(value, upper)。结果能通过哪些公开测试和隐藏测试?一个基于公开测试通过比例的奖励会把它排在有缺陷的源代码之上还是之下? - 添加一个
hardcode_tests碰巧能通过的隐藏测试。这说明隐藏测试相对于公开测试应当如何选择? - 修改
random_rollouts,从菜单中去掉格式错误的消息。先预测、再测量成功率和被篡改比例的变化。 - 使用
run_tests设计一个给予部分分数的奖励(隐藏测试通过的比例)。对有缺陷的、被篡改的和已修复的源代码分别计算它。部分分数会招致哪种漏洞利用? - 对每轮成功率 ,找出使回合成功率高于 50% 的最大时间跨度 。对 重复这一计算。
- 改变一个假设:在最后一个代码块中把组大小设为 和 。使大多数组都有信息量的通过率范围如何变化?这对在困难任务上选择组大小意味着什么?
- 设计评审:一位同事提议在 SWE 类任务上,用来自更强模型的 10,000 条成功智能体轨迹做 SFT,且不做强化学习。列出这种做法很可能学好的行为、很可能遗漏的行为,以及一项能显示二者差异的测量。