同一个 AI 模型,换个 Agent 框架,为什么表现差这么多?

把小模型放进 Claude Code、Codex、OpenCode 等真实 Agent 工具里训练,既学习完成任务,也学习少走弯路。

作者:Hugging Face / Liquid AI10-04 · 25 分钟
🎧 听播客版0:00 / --:--
0:00 / --:--

Hugging Face 与 Liquid AI 团队发表了一篇技术指南:《The ultimate guide to multi-harness RL》。

它要解决做 AI Agent(智能体)时的一个现实问题:同一个大模型,放进不同的 Agent 框架(Harness)里,表现可能差很多;针对某个框架训练好之后,换到另一个框架,能力也未必能直接迁移。

指南不仅解释这种差异从何而来,还展示了怎样把 Claude Code、Codex、OpenCode 等现成工具接入强化学习,让模型在多种真实工具中练习完成任务、减少不必要的操作。下面先讲清问题,再拆解训练方法与实验结果。

同一个大模型,换个壳为什么判若两人?

在测试中,研究者 Joel Niklaus 拿同一个 GLM-5.2 模型在 SWE-bench Pro 代码基准上做横向评测:换了外层程序,同一模型的任务解决率就明显改变。 更令人惊讶的是,工具的优劣排名无法在不同模型间直接套用——在十种脚手架中,Codex 是 GLM-5.2 的第二强搭档,但换成 Gemma 4 26B-A4B 时,Codex 却排到了倒数第二(第九名)。

Joel Niklaus 原图:两个模型在不同 Harness 下的解决率与每题成本,蓝色 GLM、红色 Gemma
原始配对图,作者 Joel Niklaus。纵轴是首次尝试解决率,横轴是按 API 标价计算的每题美元成本,使用对数刻度。手机上可点图打开大图。

读这张图要分别追踪蓝点与红点:GLM 的最佳搭档是 Mini-SWE-Agent,Gemma 的最佳搭档却是 Crush。向上代表做对更多,向左代表每题成本更低;高成本也不保证更高解决率。因此,所谓“工具最好用”要连同模型一起判断,横轴也不能当作这篇 RL 实验的训练费用。

指南还引用了一项控制变量的评测:同样的 100 道 SWE-bench Verified 题,同样的容器、50 步操作预算与超时,只改变 Harness 配置。最简版没有压缩和重试;改进版精简工具、加入压缩和重试;完整版再加自检、漂移检查与回滚。

模型 最简版 改进版 完整版
GLM-5.1 52.5% 56.5% 65.5%
GPT-5.4 55.0% 58.5% 63.5%
Kimi K2.6 52.0% 59.0% 60.5%

三个配置下的第一名分别是 GPT、Kimi、GLM。这个例子把“模型排名也受 Harness 影响”讲得更具体:榜单上更高的模型,放进你自己的执行循环里未必仍更高。数据来自指南引述的 Zhang 等人的 Table 2。

这种包裹在底层大模型外面的程序,在学术界和工业界被称为 Agent Harness(智能体脚手架)。

搞清几个容易混淆的角色

为了讲清楚背后的机制,我们先把几个常见的概念排个序:

  • Policy(策略 / 底座模型):纯粹的模型权重。它的输入是提示词,输出是生成的文本或 Token,没有跨调用记忆;模型选择工具与参数,外层程序负责执行。
  • Harness(脚手架):模型身外的程序外壳。它负责构建上下文提示词、给模型暴露哪些工具、拦截并执行工具调用、解析模型吐出的参数、处理报错并决定何时重试或结束。
  • Agent(智能体):Policy 穿上 Harness 外衣后的整体。用户实际打交道的就是这个跑在循环里的完整程序。
  • Sandbox(沙箱):工具真正跑代码的隔离环境,比如 Docker 容器、轻量虚拟机或远程 Linux 会话。
  • Benchmark(基准测试):一套固定的测试题库加上打分判定规则。

模型提出工具调用或结束回答,Harness 实际执行操作、组织下一轮上下文,并按自身规则重试或停止。换了脚手架,模型读到的信息与可调用的工具就变了。

还要区分 RL 环境与 Harness:环境保存任务状态,接收动作、改变状态、返回观察,并在满足条件时给出奖励;Harness 负责让模型持续与它交互。比如文件与计算结果属于环境中的状态,何时读文件、何时再问模型、何时停下来则由 Harness 的循环安排。

为什么只练一种工具容易偏科?

自己部署开源模型时,仅有权重并不保证它能适应所选工具的接口:

快手 KwaiKAT 团队在 KAT-Coder 研究中指出,如果智能体强化学习只在一个固定的脚手架里训,模型学到的往往不是“怎么解决问题”,而是“如何迎合这个脚手架的特定规矩”。他们总结了三种过拟合:

  1. 动作格式过拟合(Format):工具调用的接口方言(API Dialect)千差万别。有的用标准 OpenAI 格式,有的用 Anthropic 结构,还有的像 Aider 直接要求模型在普通文本里输出特定的代码编辑块。更关键的是工具名:A 叫 bash,B 叫 terminal,C 叫 execute_command;一旦模型把 A 的参数名字背熟了,到 B 里就会因为参数不合规被系统直接拒绝。
  2. 上下文结构过拟合(Context):脚手架怎么压缩历史记录?是用 Markdown 拼装上下文,还是维护结构化消息列表?模型习惯了某种排版后,换一种格式就可能抓瞎。
  3. 控制流过拟合(Control Flow):有的脚手架提供较多自动容错,自动帮你做语法修复、提供重试保护;有的脚手架则很严苛,错一次直接退出。依赖这些容错机制的模型,一旦离开熟悉的环境可能难以继续。

Orchard 论文曾记录过一次“明显退步”:把在 OpenHands 环境里精调出来的 OpenSWE-32B 拿去另一个极简工具 Mini-SWE-Agent 里跑,解决率掉了 7.5 个百分点;放到没见过的 Kimi-CLI 里跑,在 SWE-bench Verified 上的得分直接从 60% 级断崖式下跌到 3.6%,在 Terminal-Bench 2.0 上得分为零。同一论文还观察到另一个系统 Scale-SWE 离开原生工具后无法产生有效工具调用。解决率下降与格式失效是两种不同问题。

指南梳理的 Poolside Laguna、Kimi K3、Qwen 与小米 MiMo 等工作,都在增加训练接触的 Harness 种类。但它们做法不同:Poolside 在监督训练中混入外部工具轨迹,后续 RL 只用生产工具 pool;Kimi 通过模块组合形成不同执行配置;Qwen3-Coder-Next 在六种工具里生成编码数据。所谓“多工具训练”,还需要看它发生在收集数据、监督微调还是强化学习阶段。但摆在所有开源开发者面前的难题是:市面上的成熟工具都是别人写好的独立程序,根本不是为了训练而设计的,我们怎么把强化学习接进去?


驯服“黑盒脚手架”的底层技术坎:为什么不能只看最终文本?

在传统强化学习里,环境通常是一个标准函数库(比如经典的 Gymnasium 接口)。微软 Agent Lightning 团队形象地将这种差别划分为白盒环境与黑盒环境:

  • 白盒环境(训练器主控循环):训练引擎自己说了算。它每吐一个 Token、每调用一次环境的 env.step(),都在训练器的完全掌控下。TRL 这类训练框架原本也是这么预设的。
  • 黑盒环境(脚手架主控循环):像 Claude Code 或 Codex 这种真实工具,一旦启动就在沙箱里跑自己的逻辑:自己压缩上下文、自己静默重试、自己修补格式残缺的 JSON。训练器在外面根本插不上手,只能眼睁睁看着它向模型接口发来一串串 HTTP 请求。

这里的白盒、黑盒按“谁负责交互循环”区分。其他报告有时用它们区分是否压缩历史,含义不同;一个内部循环很简单的 Harness,也可以由它自己主控任务。

面对这种黑盒脚手架,一个直观想法是:等工具跑完,把屏幕上打印的完整对话文本存下来,拿去给模型算奖励不就行了?

答案是:对这套需要精确采样记录的策略梯度训练而言,文本与最终分数不足以可靠重建生成时发生的事。

关键的 Token 漂移与对数概率缺失

强化学习里的策略梯度算法(如 PPO、GRPO)要算出正确的参数更新方向,必须精准依赖两样东西:

  1. 生成时到底采样了哪些具体的 Token ID?
  2. 模型当时以多大的概率生成了它们?Logprob 是这一概率的对数。

如果只存文本,事后再用分词器重新编码(Re-tokenize),会引发一系列关键漏洞:

  • 分词不唯一(Token Drift):同一串英文字符和空格,在不同的上下文边界下重新分词,切出来的 Token ID 可能完全不同。训练器可能对不是模型实际采样的 token 计算更新。
  • 脚手架“善意修补”的污染:很多脚手架为了提高容错率,在把历史送进下一步之前,会自作主张清理空格、修复漏掉的括号、甚至补齐错误的 JSON 字段。如果你拿最终文本去算损失,相当于在强行让模型为“它自己根本没生成过的正确答案”承担责任。
  • 异步训练下的比率失效:在大规模强化学习中,生成轨迹和反向传播是异步并行的。当训练器准备更新参数时,手头的模型权重已经比生成这条轨迹时更新了几个步长。我们必须拿“现在的概率”去除以“生成当时的真实概率”(重要性采样)。如果不记录生成当时的对数概率,强行用当前权重去重算,新旧概率比值就会变成失去区分作用的 1,它就失去了修正新旧策略差异所需的信息。

这项工作选择保留推理引擎返回的原始 Token ID 与生成时的 Logprob,避免从文本猜回去。原文也介绍了通过请求响应重建样本的 OpenForgeRL,它同样报告了有效结果;精确记录是本文采用的路线,并不等于其他方法已被证明无法训练。

看起来一样的文字,token 边界却变了

原文的边界示意:每个格子是一个 token。从第三格起,两条序列不再一致。

保留采样序列,再按 ID 接模板后缀
"150"}\n<|im_end|>\n<|im_start|>user
先拼成文本,再重新分词
"150"}\n<|im_end|>\n<|im_start|>user

上排把模型采样的两个 token 与模板后缀分别保留;下排把换行与特殊标记合并成了别的 token。文本相同,不代表生成时的 token ID 相同。示意展示分词边界,不列具体词表 ID。

根据原文 Figure 7 的 token drift 图本地化。


三件套破局方案:OpenEnv 抓包代理、Harbor 任务沙箱与 TRL

为了打通“未经修改的现成工具”与“严格的强化学习训练”,本研究组合了一套开源三件套架构:

OpenEnv 提供环境、Harness 与训练器共同使用的接口和服务,采集代理是它的一个部件。奖励来自任务验证器,参数更新由 TRL 执行;把通信接起来,本身并不会教会模型完成任务。

一次任务中,Harbor 在隔离沙箱里启动真实 Harness。每次模型请求先经过 OpenEnv 采集代理,再送到 vLLM 或 SGLang。代理保留生成记录,把回复转回 Harness 所需格式。结束后,训练器取得代理导出的轨迹和 Harbor 给出的评分,再由 TRL 更新权重。

这条路径能保留现成工具,是因为所有 Harness 都必须调用模型接口。每次 rollout 获得独立的采集会话,Harness 使用代理的 base URL,并把会话 ID 当作 API key。代理据此将并发请求归入各自任务,工具仍按自己的规则执行、重试和整理上下文。

工具照常干活,训练器从旁记录

架构示意:上方是一次模型调用,下方是训练所需的两路数据。

真实 Harness管理工具、历史与重试
⇄
OpenEnv 采集代理转换接口格式,记录原始 token 与概率
⇄
推理引擎用当前模型生成回复
代理 → 原始生成记录哪些内容由模型生成,哪些是工具返回的上下文
Harbor → 任务评分隔离沙箱执行;验证答案;失败保留无评分状态
两路一起交给 TRL按组比较奖励,只对模型生成的目标计算损失,再更新权重

同一训练器可接不同 Harness;每个八次尝试的组使用同一种 Harness,组与组之间切换。

抓包代理(Capture Proxy)的巧妙设计

抓包代理是整个架构的关键部分,它充当一个兼容多协议的模型网关:

  1. 多方言动态翻译:现成脚手架各说各的语言——有的发 Anthropic Messages,有的发 OpenAI Chat Completions,有的发 Gemini 格式,还有 OpenAI Responses。代理通过请求路径、Header 和 Body 自动识别,利用集成自 NVIDIA Polar 网关的转换器,把它们全部归一化为标准的补全请求。无论前端工具怎么折腾,送往底层推理引擎的永远是统一格式。
  2. 整包拦截与假流式回放:工具如果要求流式输出(Streaming),代理并不会真去推理引擎那里分块抓取残缺的 Delta 包,而是等引擎把完整回复一口气吐完、妥善记录下全部 Token ID 和原始对数概率后,再由代理充当打字机流式推送给脚手架。Harness 仍收到所需协议格式,不过等待第一个 token 的时间会变长。
  3. 消除采样偏差:代理强制推理引擎在 top_p=1.0(全分布)下采样,并启用 processed_logprobs。如果在推理时截断低概率词,会导致模型生成的分布被外力扭曲,进而引发强化学习中的“熵崩溃”。实验证明,把 top_p 设为 1.0 后,重要性采样比率从有偏的 0.985~0.993 接近 1,达到 0.9984~0.9999。
  4. 把网状调用梳理成“训练树”:真实的编码任务绝非一问一答,工具会重试报错、会召唤子智能体、会在超出限制后压缩历史。代理通过寻找最大 Token 公共前缀,把每一次模型调用连成有分支的图:
    • 失败后放弃的重试,是树上没有后继的分支;
    • 子智能体有独立的系统提示词,自动开辟为一棵新树的根节点;
    • 最终从根节点到叶子节点的每一条连通路径,被干净地导成一条训练序列,给工具自己输入的上下文打上 Mask=0(不计算损失),只对模型自主生成的步骤打上 Mask=1(计算梯度)。

Harbor 与 TRL 的衔接

  • Harbor 的任务沙箱调度:Harbor 原生支持 40 多种主流脚手架和 26 种沙箱执行后端(如 E2B、Daytona、本地 Docker 等)。每个任务目录提供指令、运行环境配置和验证脚本,实际使用哪种 Harness 与沙箱后端在运行时选择。在训练时,每次 Rollout 只需要单条指令指定“跑什么任务、用什么工具、在什么沙箱跑”,实现了任务与工具的解耦。
  • 与官方轨迹(ATIF)双重核验:为了防止通信异常导致的漏训,系统会将抓包代理记录的 Token 序列与 Harbor 自带的 ATIF 轨迹文件逐步对账(核对每步的 Token 长度与步数)。之前团队就曾抓到过某工具发送空工具数组触发 400 报错、导致轨迹中途悄悄被掐断的问题。
  • TRL 异步 GRPO 训练:利用 TRL 的 AsyncGRPOTrainer,生成轨迹和训练反向传播完全异步解耦。两张 H100 显卡分工明确:一张卡跑 vLLM 专职推理生轨迹,另一张卡吃进训练序列专心算梯度,生成与训练并行,减少等待最慢 Agent 的时间。轨迹通常来自两步之前、最多四步之前的权重,因此生成时的概率必须保留。

环境服务器与训练器之间还隔着 HTTP 边界。早期把 Harness 放进训练进程,异常可能令一个进程崩溃,其他进程一直等它同步。现在失败返回 ok=False、reward=None,由训练器处理失败状态;验证器没有运行,不会被误当成“答案错了,奖励 0”。

代理会探测推理后端能力:能回答不等于能提供训练数据。若托管接口不返回精确 token 与对应概率,仍可评测工具,却不能用于这条训练路线。缺失或不对齐概率的调用只留作上下文,不当作训练目标;历史压缩也可能产生新的调用图根节点。

小模型放进四种工具里练,究竟改善了什么?

团队把 Liquid AI 的 LFM2.5-2.6B(26 亿参数)作为起点,在从 Kaggle notebooks 构建的 SmolDataEnvs 数据分析题上训练。题目要求模型查数据、执行计算、读取结果并提交可验证答案。一个从开始到结束的尝试叫 rollout,包含多轮工具操作,而非一句回答。

原文展示的一道中等难度题是:“全部足球比赛中,平局占多少百分比?”沙箱提供 database.sqlite,题面要求先查看数据,再计算,把单一数值写入 /workdir/answer.txt,然后停止。预设参考答案是 25.4%,通过任务配置交给验证器;模型要从输入数据算出来并提交文件。这样,测试的不只是能否说出一个数字,还包括能否读对数据、调用工具、遵守提交格式,并让验证器取得答案。

训练池选择 1,000 题,400 个中等难度、600 个高难度。两组按同一任务顺序各训练 1,000 步:一组只用 OpenCode;另一组在 OpenCode、Claude Code、Codex、Mini-SWE-Agent 之间切换。同一道题的八条 GRPO 尝试都使用同一种工具,组与组之间再切换,避免把工具本身的差异当成动作优劣。

评测使用留出的 250 题,与训练数据在 notebook、问题和指令上不重叠。每题在四种工具下各尝试一次,共 1,000 个评测单元。Pass@1 表示第一次可评分尝试是否答对;这些是数据分析任务的结果,不能直接当作修复真实代码仓库的成功率。

答对才有效率奖金,答错不能靠少操作拿分

早期 Qwen 实验只奖励正确与否,一些过程变得越来越长。LFM 因此加入小额效率奖励:答错为 0;答对为 1 + 1.5 ÷(15 + 工具调用次数)。效率加分最多 0.1,调用越多,加分越少。

GRPO(群组相对策略优化)比较同题八次尝试的奖励,用它们相对组平均值的高低来产生学习信号。若八次全对,单纯 0/1 奖励使八次都是 1 分,没有奖励差异。效率奖金使较短的正确过程得到更高分,较长的正确过程得到较低分。若答错,哪怕只调用三次,仍是 0 分。

在贡献给保留训练更新的组中,OpenCode-only 有 97/555 组(17.5%)、多工具有 155/686 组(22.6%),可评分尝试全部正确但调用次数不同;奖金是这些组唯一的奖励差异来源。这里统计的是组,并非优化步数。

八次尝试全做对,模型还能学什么?

原实验中的真实一组:Claude Code,第 80 步,同一个难题。只看答对与否,八次都是 1 分;加入效率奖励,调用更少的尝试得到更高分。

八次全部答对,奖励相同,组内没有可用的奖励差异。

尝试结果工具调用奖励相对组均值
1正确101.00000.0000
2正确41.00000.0000
3正确61.00000.0000
4正确41.00000.0000
5正确91.00000.0000
6正确81.00000.0000
7正确51.00000.0000
8正确91.00000.0000

原实验公式:正确时奖励 = 1 + 1.5 ÷(15 + 调用次数);错误时为 0。这里展示奖励减去组均值的差,方便看方向;实际 GRPO 还会按组内标准差归一化。无脚本时保留上方默认对照。

先看每种工具,再看总体平均

最终第 1,000 步的结果如下。基线有 998/1,000 个单元完成评分,最终两组均有 1,000 个。

工具 基础模型 仅 OpenCode 训练 四工具训练
OpenCode 33.6% 58.0% 49.6%
Claude Code 33.2% 42.0% 48.8%
Codex 40.0% 43.2% 53.6%
Mini-SWE-Agent 62.1% 66.0% 64.8%
总体 42.2% 52.3% 54.2%

同一基础模型,在 Mini-SWE-Agent 中能做对约六成,在 Claude Code 中只有约三成。这就是模型与工具搭配的影响。训练只用 OpenCode,最大增益也落在 OpenCode;训练四种工具,则四种接口下都超过基础模型,Claude Code 与 Codex 下还优于单工具训练结果。

但总体 54.2% 与 52.3% 只差 1.9 个百分点,原文判断差距在噪声范围内,不能凭此宣称多工具训练总体正确率显著更好。Mini-SWE-Agent 的 64.8% 与 66.0% 接近,也不是精确相等。多工具组测试的是已参与训练的四种接口下的新任务,尚未检验完全未见过的 Harness。

曲线补上了终点表格看不到的信息:两组主要在前 200 步涨分,随后虽有波动,提升总体保持到第 1,000 步。每个检查点在每题每工具下只跑一次,一两个百分点的起伏不能直接当成能力变化;这里更有意义的是整段走势。

LFM 留出测试解决率随训练步数变化:两组前两百步上涨,之后维持在约五成
据原文公开数据重绘:LFM 在留出测试上的综合 Pass@1。实点为已评测检查点,连线用于追踪走势;早期部分评测未齐,分母随点略有变化。

调用次数还出现了一个看似矛盾的现象:多工具组在训练时平均调用更多,评测时却在四种工具下都比单工具组调用更少。训练平均混入了 Claude Code、Codex、Mini-SWE-Agent,它们原本就比 OpenCode 调用更多;评测时两模型都跑相同四种工具,才能在同一接口下比较。训练平均反映的是不同工具组合,不能单独用来判定谁更省操作。

做对的题更省操作,但“省 31%”有明确分母

只比较基础模型与训练后模型都答对的同一任务与工具配对,多工具训练最终减少约 31% 的工具调用,单工具约 11%。多工具在 OpenCode 下减少 32.8%、Claude Code 下减少 28.3%、Codex 下减少 53.0%、Mini-SWE-Agent 下减少 16.2%。

减少调用也减少每轮反复发送的历史,输入 token 随之下降。这里的 31% 是匹配任务上调用总量的相对减少,不是全体任务平均值,也不是账单或耗时节省比例。单工具训练在 Claude Code 下的正确率上升了,但最终调用和输入 token 反而更多;第 500 步的输出 token 曾多约 62%。准确率与执行效率要分别看。

最终工具调用节省:多工具训练在四种接口下均节省,单工具训练在 Claude Code 下反而增加调用
据原文效率数据重绘,第 1,000 步相对基础模型的工具调用节省。向右为更少,向左为更多。两组各自匹配双方都答对的任务,匹配题数不同,具体题数见下表。
工具 单工具组匹配题数 多工具组匹配题数 多工具组输出 token 节省 多工具组输入 token 节省
OpenCode 74 66 37.9% 27.2%
Claude Code 55 69 16.8% 24.8%
Codex 71 89 39.7% 45.9%
Mini-SWE-Agent 137 132 16.6% 11.2%

调用、输出与输入是三种不同工作量。以 Codex 为例,调用减少 53.0%,输出 token 减少 39.7%,输入 token 减少 45.9%,所以“少调用一半”不能直接替换为“所有计算都少一半”。

LFM 没有去掉效率奖励的对照,无法把全部节省单独归因于奖金。两组的训练曝光也不相等:单工具与多工具实际见到 555、626 个不同任务,监督 token 为 12.5M、18.7M,处理 token 为 162M、451M。Claude Code 会重写历史,每条轨迹平均生成约八条训练行,其他工具约一条。每种设置只有一个随机种子,这是观察性比较。

模仿大模型成功的过程,这次得到了什么?

团队还用 Qwen3.8-27B 做教师:每题每种工具最多尝试三次,保留第一个被验证器判定正确的过程,得到来自 888 题的 3,189 条成功轨迹。SFT(监督微调)把此前对话和工具输出作为上下文,学习教师的下一条响应;RL 则从小模型自身尝试的奖励学习。

单工具 SFT 使用 801 条 OpenCode 轨迹,多工具 SFT 混合全部轨迹,没有按工具平衡。两者都训练两轮。

一条成功轨迹也不是一个训练样本:每个 assistant 回合都展开成“此前上下文 → 下一次响应”,只训练这次响应。原文使用学生 LFM 自己的聊天模板分词。这与前面的 RL 记录要求并不冲突:SFT 学教师答案在学生模型下的概率;本文的 RL 则要对齐学生当时真正采样的 token 与生成概率,才能比较新旧策略。

训练方式 综合 Pass@1 双方答对任务的调用节省
基础模型 42.2% 基准
单工具 SFT,两轮后 47.5% 8.5%
多工具 SFT,两轮后 43.1% 24.2%
单工具 RL,第 900 步 52.4% 13.2%
多工具 RL,第 700 步 54.6% 26.7%

多工具 SFT 改善了 Claude Code(33.2% → 42.4%)、Codex(40.0% → 46.8%)和 OpenCode(33.6% → 38.0%),却让 Mini-SWE-Agent 从 62.1% 降到 45.2%。这抵消了其他工具的收益,使总体 43.1% 与基础模型 42.2% 接近。单工具 SFT 主要改善 OpenCode:33.6% → 51.6%;Claude Code 保持 33.2%,Mini-SWE-Agent 降到 58.8%。团队尚未解释为什么 SFT 损伤了 Mini-SWE-Agent。

多工具 SFT 在三种工具下提升,却使 Mini-SWE-Agent 从62.1降到45.2,多工具RL四种都提升
据原文 SFT/RL 数据重绘:基础模型、多工具 SFT 两轮后、多工具 RL 最佳检查点(第 700 步)。这是具体实验的接口表现,训练数据与预算不相等。

本次 RL 的正确率更高,却不能由此归结为“SFT 只会背答案,RL 才会纠错”。两者的目标、数据来源、任务池与算力不同,每种设置只跑一次;多工具 SFT 的监督 token 约为单工具的七倍。表中 SFT 是两轮后的结果,RL 使用测试集上挑出的最佳检查点,会略偏向 RL;这些 RL 检查点与最终第 1,000 步总体成绩都相差不到半个百分点。

另一个观察是,SFT 没有工具效率奖励,多工具 SFT 仍减少 24.2% 的调用,且四种接口下都减少。效率改善可以出现在不同训练路线中,不能仅凭调用下降反推某项奖励必然是原因。

团队计划的后续对照也针对这些混杂因素:比较“先 SFT 再 RL”与“从基础模型直接 RL”,检验模仿得到的起点是否有帮助;在教师四种工具都解出的 692 题上比较不同混合,减少任务选择差异;再让各工具的监督 token 数相等,减少样本量差异。这些尚待开展的实验,才能更清楚地回答收益来自哪些变化。

Qwen 为什么先涨分,后来又掉下去?

早期 Qwen3.5-2B 使用较容易的 1,000 题池,奖励只有正确与否。多工具组从 14.6% 升到第 500 步的 37.0%,OpenCode-only 在第 700 步达到 39.5%,但两组到第 1,000 步都回落到约 26%。另一组独立 OpenCode 环境实验走势不同,缓慢上升,到第 1,000 步达到最佳 29.8%。

Qwen三组测试曲线:两组Harbor训练先升后降,独立OpenCode组缓慢升至29.8
据原文公开数据重绘:Qwen 三组在相同测试任务上的综合 Pass@1。独立 OpenCode 组另有 15.9% 基线,不能把它和两组 Harbor 的 14.6% 起点混为一谈。

轨迹暴露出不同问题:

  • 多工具组训练允许最多 16,384 个输出 token,评测只允许 4,096。后期回答越来越长,常在该计算时输出长篇解释,触及上限的评测单元从 9 个增到 556 个。训练养成的输出习惯,被评测预算截断;放宽后能否答对这些题,没有在这组结果中得到验证。
  • OpenCode-only 继续操作却不提交。训练时调用约从 10 次升到 20 次;评测中从 17 次升到 21 次。有提交记录的单元中,交出答案的比例从 69% 降到 41%。
  • 多工具组第 684 步重启后,几乎都取到已见过的旧题,改变了后期接触的数据。但下降从第 500 步已开始,不能由重启 Bug 解释起点。

还有两项容易被“轮流训练”掩盖的现象:35%—58% 的优化步里,各组都没有奖励差异;四种工具各占约四分之一的轨迹,Claude Code 却贡献 77% 的训练行、35% 的监督 token。均分轨迹并不等于均分实际训练量。换一批更难的新题续训,也没有恢复多工具组的峰值。

后续 LFM 同时调整三件事:加入效率奖金,任务池改为中等与困难题,并把训练和评测输出上限都设为 4,096。LFM 的提升保持到第 1,000 步,但三项同时改变,不能分离各自的贡献。训练预算、评测预算、是否提交和组内奖励差异,都要一起检查,单看训练奖励上升不够。

开发者可以从中带走什么?

首先,Agent 表现由模型与运行程序共同决定。选型时应在自己的工具、任务、上下文管理与预算下评测,而不是只看脱离 Harness 的模型排名。更换模型或工具,都可能改变整个系统的表现。

其次,这项工作提供了一条开放的工程路径:保持真实 Harness 的工具与循环,通过模型接口收集训练需要的证据,让同一训练器接入不同工具。它支持使用相应模型 API 的程序,仍需兼容推理后端、适配配置和可验证任务;并非给任意程序加一层代理就自动具备训练能力。

奖励也必须对应真正目标。原文记录过一次“提交任何东西就加 0.2 分”的尝试:模型学会立即提交,训练奖励看起来正常,留出任务正确率却从 74.0% 降到 17.8%。正确性与效率的关系要写进目标,不能让省步骤、提前提交替代完成任务。

这次 LFM 实验每组使用两张 H100,单工具约 32 小时、多工具约 46 小时,不含评测、排队与重启;完整费用没有统计。小模型原型证明了一类数据分析任务上的可行性,还没有建立所有模型、任务与工具混合方式的一般排名。

复现入口:先确认能采到训练数据

原文的入口是 OpenEnv 的四个命令:info 检查后端、任务与工具能力,rollout 先独立跑任务,serve 提供环境服务,push 部署同一服务。先让单次任务与记录核对通过,再接训练器,能把工具执行问题和服务层问题分开定位。

依赖包括 Python 3.12、OpenEnv 的 Harbor 扩展与 typed TrainingTrace API,以及 TRL 的 HarnessRolloutWorker。原文发布时,TRL 该 worker 在主分支的实验模块中,尚未进入当时正式版;复现应按配套示例核对依赖。

vLLM 需启用 --return-tokens-as-token-ids 与 --logprobs-mode processed_logprobs。原文称 SGLang 非流式 token 返回从 v0.5.17 起支持,但团队实际只测试 main 构建;这些 vLLM 参数不能照搬给 SGLang。仅完成普通聊天调用,不能证明 token 与概率已完整采集。

还要让服务容量容纳一整组尝试:原文环境服务的 max_concurrent_envs 默认是 4,而一组 GRPO 有 8 条 rollout,超出限制会返回 CAPACITY_REACHED,训练前需按实际并发配置。采集代理本身是单进程,团队曾观察到约 200 个并发会话时健康检查受压、320 个时崩溃;这是该次运行的观测,扩容前仍要测自己的负载。

完整 SmolDataEnvs 训练集约 5,000 题,本实验选择其中 1,000 题。历史实验沙箱使用 E2B,当前教程使用 Daytona,并包含独立 OpenCode 环境比较,所以当前教程不是历史实验的精确重放。

代码、模型与数据入口见配套代码及原文。

模型与数据合集集中提供实验产物;LFM 多工具 RL 模型默认对应第 1,000 步,step-700 分支是最佳测试检查点;训练比较面板可查看原始曲线;SFT 数据集提供按工具拆分与预分词配置。新运行应使用 Harbor 的 OpenCode 适配,原文保留的独立 opencode_env 已标为历史比较入口。

来源
The ultimate guide to multi-harness RLAdithya S Kolavi 等;Hugging Face、Liquid AI·查看主材料
↑ 正文结束
✓ 本篇全文免费开放

会员可复制、下载全文 Markdown,还能让你的 AI 订阅和提问。

阅读原文 ↗
📮 新解读第一时间进邮箱

读完觉得有用?留个邮箱,每篇新解读发布就送达,随时退订。