ToolRL: Reward is All Tool Learning Needs
Paper: arXiv:2504.13958 Code: qiancheng0/ToolRL Code reference:
main@8cee13ec(2025-10-16)
1. Motivation
现有 tool use 训练主要依赖 SFT,把工具轨迹先离线蒸馏出来再模仿;问题是这种方式容易学到表面模板,遇到陌生工具、复杂参数或多步调用时泛化差,且容易过度思考但不会真正做对工具选择。本文要解决的不是“再做一个工具数据集”,而是:在 general-purpose tool selection / application 场景里,RL 到底该怎么设计 reward,才能让模型既学会结构化调用,又能泛化到没见过的任务。这个问题值得做,是因为 tool-integrated reasoning 是 agent 能力的核心入口。reward 设计一旦成立,就不只是提升某个 benchmark,而是在给“LLM-as-agent”提供可迁移的训练配方。
论文把问题定义得很具体:tool use 不是普通的单轮 answer generation,而是一个包含 reasoning、tool selection、argument filling、environment feedback assimilation 的序列决策过程。只要其中任一环节错了,最终答案就可能错,但传统 end-to-end reward 往往只在最后一步给一个整体分数,无法告诉模型究竟是“选错工具”“参数名写错”“参数值填错”还是“格式根本不可执行”。这导致 credit assignment 非常粗糙。作者特别强调,现有工作有两类常见局限。
第一类是 imitation-heavy 路线:先由强模型或人工构造出 tool-augmented trajectory,再用 SFT 蒸馏给较小模型。这种方法在 in-distribution format mimicry 上有效,但模型容易把 <tool_call> 当作模板补全任务,而不是把调用行为当成策略优化对象。第二类是 task-specific RL:例如只针对 search、code generation 或某个封闭工具空间设计 reward。这样能在窄环境里做出高分,却很难回答“通用工具调用需要什么样的 reward decomposition”这个更基础的问题。
从 Figure 1 的失败案例能看到作者真正担心的现象:SFT 模型表面上能输出很长的 <think> 和看似合理的调用,但它在关键动作上并不稳。要么把简单问题过度复杂化,产生 overthinking;要么在需要精确参数的时候仍然沿着训练模板硬补,导致执行级错误。对 agent 而言,这比普通文本生成更糟,因为错误会被工具执行放大,甚至导致后续 observation 全部偏离。因此,本文不是在争论“RL 要不要用于 tool use”,而是在更上游地问:如果已经接受 RL 是必要的,那么 reward 应该怎样拆、怎样缩放、怎样随训练阶段变化,才能让模型从 rollout 中学到真正可迁移的工具调用能力。
作者的出发点是,tool use 的 reward 不该只看最终 answer correctness,而应该显式反映工具接口结构本身。这个问题值得做,还有一个现实原因:通用 agent 往往面临多工具、多 schema、多轮状态累积。一个模型今天要调用 calculator,明天要调用 weather API,后天又要做 multi-turn search;它们共享的并不是某个答案模板,而是“先判断是否要调工具,再选对工具,再把参数拼对”的抽象能力。若 reward 能在这个层面成立,它的价值会超出本文 benchmarks。论文也顺带挑战了一个流行假设:更长的 reasoning 往往被当成更强 agent 的证据,但作者在 tool use 里看到的并不是单调收益。
若 reward 不精确,模型可能学会写更长的 <think> 来“看起来更努力”,却没有提升 action quality。于是本文把 length reward 也纳入研究对象,明确检验“鼓励更长思考”到底是在帮忙,还是在制造表面进步。
2. Idea
核心 insight 是:tool use 的难点不在“有没有 reward”,而在 reward 的粒度、尺度和时间动态是否和多步调用过程对齐。ToolRL 不是只给最终答案打分,而是把 format、tool name、parameter name、parameter value 拆开,并研究它们在训练中的组合方式。最关键的创新是把 reward 设计本身当成研究对象,系统比较 coarse / intermediate / finegrained,以及固定 / 动态 scale、固定 / 动态 length reward,再把这些 reward 接到 GRPO 和 PPO 上验证。
与 Search-R1、TORL 这类只覆盖搜索或代码工具的工作不同,ToolRL 面向 general tool selection and application,强调多工具、多参数、多轮交互的通用设置。更具体地说,ToolRL 的想法可以概括成三层。第一层是 reward granularity:不要把“工具调用正确”视作一个不可分的黑箱事件,而要拆成结构合法、工具名匹配、参数名匹配、参数值匹配几个可比较的子信号。第二层是 reward scale:不同训练阶段模型能力不同,reward 上限和各项权重不能一刀切,否则粗奖励切到细奖励时容易造成训练不稳定。
第三层是 reward temporal behavior:像 reasoning length 这种信号可能只在某些阶段有用,需要用 schedule 控制,而不是永久强推。这套思路和普通“在 GRPO 上塞一个 tool reward”不同。作者真正的新意不是选择了哪种 policy optimizer,而是把 optimizer 视作验证平台,把问题焦点放回 reward engineering 的结构化设计。论文中 GRPO 和 PPO 都被使用,但它们主要用于回答同一问题:在不同优化器下,这套 reward family 是否仍然能稳定改善 general tool-use 能力。
与现有方法相比,ToolRL 的 fundamental difference 在于它不假设单一 oracle trajectory。SFT 路线默认正确轨迹是唯一且可直接模仿的;ToolRL 则假设 rollout 中可以出现部分正确的调用,并且这些“部分正确”本身就有学习价值。例如工具名对了但参数值错了,或者参数名集合大体匹配但格式稍有偏差,这些都不该和完全错误的输出拿同一个零分处理。作者因此把 credit assignment 颗粒度前移到了 action interface。对于文本任务,人们常说 token-level reward 很难;
但对 tool use 来说,最自然的中间层并不是单 token,而是 tool name / argument key / argument value 这些语义槽位。ToolRL 的核心贡献,就是把这些槽位层级的匹配关系转成 RL 可优化的连续信号。另一个值得注意的 insight 是,作者并不把 cold start RL 当成劣势基线。相反,论文多次展示:从 Raw Instruct Model 直接做 GRPO,虽然训练初期波动更大,但在若干 benchmark 上能超过先做 SFT 再上 RL 的路线。这隐含说明,SFT 初始化并不总是在帮 RL,它也可能把策略空间锁得太死,使模型沿着 imitation prior 过拟合。
因此可以把 ToolRL 看成一个更 general 的主张:在 agent training 中,reward 不是后处理细节,而是决定模型最终学到“语言模板”还是“可执行策略”的核心归纳偏置。本文的各种 ablation,本质上都在验证这个主张。
3. Method
Overall framework
Figure 2 解读:左侧是主结果,右侧是 GRPO cold start 在四个模型上的 reward 曲线。它传达了两个关键信号:一是 reward 设计确实能带来稳定提升;二是训练 reward 上升并不等价于泛化一定更强,因此必须看下游 benchmark。
Figure 3 解读:这是 TIR rollout 和 reward 计算框架。模型在每轮输出 <think>、<tool_call>、<response>,环境把历史对话、工具观察和多轮调用一起拼进 prompt;reward 则同时看结构是否正确、工具调用是否匹配。

Figure 1 解读:SFT 蒸馏轨迹的失败案例。论文借这个图说明,单纯模仿深思考轨迹会导致 overthinking 和有限泛化,不能替代 RL 的策略探索。
方法上,训练目标可以概括为:先把多步 tool-integrated trajectory 形式化,再在 rollout 中生成多个候选响应,最后用 group-wise advantage 做 GRPO 更新。论文明确说明 GRPO 这里不加 reference-model KL penalty,目的是让模型更自由地适配工具调用格式和结构化 reward。 从系统角度看,这个框架把 policy、tool environment、reward function 三部分解耦了。policy 负责输出带特殊标记的文本;environment 负责执行 <tool_call> 并把 observation 回填;reward function 不直接依赖 hidden state,而是对完整轨迹的结构与调用结果做匹配。这样一来,研究重点就从“换更强 backbone”转成“如何让同一 backbone 从更好的反馈结构里学到 tool policy”。 作者的方法论也很克制:并没有发明新的 RL objective,而是故意在相对标准的 GRPO/PPO 框架内比较不同 reward 设计。这样实验结论更容易解释为 reward 本身的贡献,而不是某个复杂训练技巧带来的偶然增益。
Key components
3.1 TIR rollout
TIR trajectory 记为
其中 是第 步 reasoning, 是本步选择的工具集合, 是观察结果。输出格式要求同时支持 <think>、<tool_call> 和 <response>,而且三者并不互斥,可以同一轮里同时出现。
直觉上,这种 rollout 让模型不是只预测最终答案,而是在“想 - 选工具 - 看反馈 - 再修正”的闭环里学习策略。这里的关键不是简单地把 tool observation 拼进上下文,而是把整个 interaction 序列显式建模成 RL rollout。每一步状态都包含此前所有 reasoning、tool calls 和 observations,因此下一步 action 不只是“续写文本”,而是在已有外部反馈条件下继续决策。与普通 chain-of-thought 不同,TIR 中间状态会被 environment 改写,因为一次错误工具调用会生成新的 observation,从而影响后续策略。论文给出的格式设计也有明显的工程考虑。
<think> 为内部分析留出空间,<tool_call> 负责结构化接口输出,<response> 则处理最终面向用户的自然语言回答。把这三者拆开后,reward 才能针对不同失败模式施加不同惩罚。例如输出了正确答案但没有合法 <tool_call>,在真实 agent setting 中可能同样不可接受。作者的 general setting 允许多工具、多步、多参数调用,所以单轮中可能出现一个工具集合而不是单个 tool token。对应地,轨迹表示里的 不是标量动作,而是更接近一个 structured action bundle。
后面的 correctness reward 之所以要区分 tool name、parameter name 和 value,本质上是为了给这种 structured action 提供细粒度监督。从训练信号角度看,TIR rollout 还有一个隐含优势:它让同一个 query 下可以采样出多条“部分正确程度不同”的轨迹,GRPO 的 group comparison 因而有了意义。若任务只给最终答案且中间没有可比较结构,group normalization 只能看最终对错;但在 ToolRL 里,多条 rollout 经常在格式、工具名、参数层面拉开差异,这会显著提高相对优势信号的分辨率。
3.2 Reward design
论文把总 reward 分成:
其中 format reward 是二值的:如果输出中的特殊 token 顺序正确就给 1,否则给 0。correctness reward 进一步拆成三部分:
最终把三者归一化到 的区间。
这里的 format + correctness 二分法很重要。format reward 解决的是“模型是否按 agent 协议说话”,correctness reward 解决的是“协议里的语义内容是否对”。作者没有把二者混成一个分数,是因为 tool use 中存在大量“内容大致对,但格式不可执行”或“格式完全合法,但调用对象错误”的情况。把它们混在一起会让模型很难知道该改哪一部分。论文中 coarse / intermediate / finegrained granularity 的区别,核心在于 correctness reward 的分解深度。coarse reward 更接近整体正确/错误判断;
intermediate reward 至少会分出工具名和参数层;finegrained reward 则继续把 parameter key 与 parameter value 区分开来。作者的假设是,tool use 的错误通常发生在接口的局部槽位上,因此越细的拆分越利于 credit assignment。以一个天气查询例子来说,若模型调用了正确的 weather_api,却把 location 写成 loc,那么 coarse reward 很可能近似把它当成“整次调用错误”;但 finegrained reward 至少能保留“工具名正确”“参数集合部分匹配”这些正信号。这样 policy 不必从零学起,而是在接近正确的轨迹上逐步修补局部接口错误。
另外,论文的 correctness reward 并不要求预测工具顺序必须完全 identical 才给分。公式里通过集合及槽位匹配计算部分重合度,这对应一种更宽容的监督哲学:多工具场景下,不同 rollout 可能用不同但部分正确的调用序列逼近目标。只要 reward 能识别这种接近程度,RL 就比硬匹配的 imitation loss 更有探索空间。论文公式与 released code 实现差异:代码里 match_score() 不是单纯集合交并比,而是带频次的匹配;
compute_tool_call_reward() 还引入了 REFINEDREWARD / INTERMEDIATEREWARD / COARSEREWARD / CORRECTMAX1 / MAX1STEP30MAX3 / SCHEDULEREWARD 等环境变量,允许把同一套 reward 变成更严格或更动态的版本。论文描述的是主公式,代码实现更像一个可切换 reward family。
released code 还透露出一个实践细节:作者把 reward 设计做成了环境变量可控的实验平台,而不是把某个公式硬编码成唯一选择。这说明论文中的 ablation 不是事后分析,而是训练框架从一开始就围绕 reward knobs 搭建。对复现实验的人来说,这比只看论文公式更重要,因为真正决定训练行为的是这些 reward 开关的组合。 若从 RL 视角概括,ToolRL 的正确性奖励相当于把 sparse terminal feedback 重写成结构化 dense signal,但这个 dense signal 不是 token-level shaping,而是 interface-level shaping。它既保留了 agent 任务的执行语义,又避免了纯 token matching 常见的脆弱性。
3.3 Length reward and reward dynamics
长度 reward 用来鼓励更长的 <think>,但论文发现它不是总有益。固定长度 reward:
其中 。 动态长度 reward 则按训练步数渐进调整目标,代码与附录里都体现了 schedule 逻辑。
作者之所以把 length reward 单独拿出来,是因为近年的 reasoning work 很容易把“更长链路”默认成更优策略。但 tool use 任务和纯 reasoning benchmark 不同:这里真正影响执行成败的往往是是否选对工具、是否填对参数,而不是 <think> 写了多少 token。length reward 若权重太大,模型可能把优化预算花在生成更长的思维文本,而不是提升 action quality。论文测试了固定长度奖励与动态长度奖励。固定长度版本对所有训练阶段都用同一 ,等价于长期鼓励模型接近一个预设的推理长度。
动态版本则通过 schedule 让目标长度或 reward 影响随训练推进而变化,希望在早期给模型一点 reasoning 空间,后期再减少对冗长思考的偏置。实验结果说明,length reward 并不是“加了总比不加好”的免费增强项。特别是小模型上,固定 length reward 常导致性能下降,这说明模型会学到 verbosity,而不是更好的 decision policy。这个发现很有价值,因为它直接反驳了一个在 agent 训练里常见但很少被系统检验的直觉。reward scale 的实验也很关键。论文比较了 equal max、two-stage、dynamic 三种方式,核心结论是:突然切换 reward 尺度会伤训练,平滑过渡更稳。
所谓 reward scale,本质上是在问“不同 granularity 奖励的最大值与影响力该如何分配”。如果一开始就让最细粒度奖励主导,模型可能在几乎不会合法调用工具的阶段被过细的误差信号淹没;但如果一直停留在 coarse reward,模型又难以学到参数级精度。ToolRL 因此比较了静态等权、阶段切换和动态调度三种路线。
two-stage 的想法很直观:先用 coarse reward 让模型学会大体结构,再切到 finer reward 做精修。问题在于这种突变会让策略面对非平稳目标,训练曲线容易震荡。dynamic scaling 则试图把奖励重点逐步迁移,让模型先建立基本调用能力,再慢慢被推向参数级精确度。论文的结果显示,后者通常更稳,也更符合实际 credit assignment 的渐进需求。 从更一般的角度看,reward dynamics 是本文最能迁移到其他 agent setting 的部分。它说明 structured reward 不只是“拆得够细”就行,还要考虑模型在不同能力阶段能否消化这些细信号。也就是说,ToolRL 研究的不只是 reward composition,还包括 reward curriculum。
3.4 GRPO / PPO optimization
GRPO 先对同一 query 的多条 rollout 做 group normalization:
再用 clipped PPO 风格 objective 更新 policy:
代码里对应 compute_grpo_outcome_advantage() 和 compute_policy_loss();PPO 版本则在 core_algos.py 里保留 value loss、entropy / KL 等常规项。
直觉上,group normalization 的作用是把“某个 query 本身难不难”与“哪个回答更好”分开,减少 reward 方差,让相对好坏更容易学到。这里采用 GRPO 有两个现实原因。第一,tool-use reward 本身带有显著 query-level variance,不同问题需要的工具数量、参数复杂度和多轮深度差异都很大;若直接比较绝对 reward,policy 容易被 problem difficulty 主导。第二,ToolRL 的 structured reward 使得同一 query 下的多个 rollout 往往会形成细致排序,group-wise normalization 可以充分利用这些相对差别。
论文还特别指出,GRPO 设置中不使用 reference-model KL penalty。这一点和很多 RLHF 配方不同,原因是 tool-call format 本身需要策略有较大自由度去重组输出结构。若 KL 过强,模型容易被 base policy 锁在原本的文本分布附近,反而不利于学出新的结构化调用习惯。PPO 在本文里更多承担对照角色。通过同时报告 PPO cold start、PPO on SFT model,以及 GRPO cold start,作者可以区分“优化器差异”和“reward 设计差异”。
结果显示,虽然 PPO 也能从相同 reward family 受益,但 GRPO 在若干设置下表现更稳,说明 group-based relative comparison 与 tool-use reward 的结构较匹配。若把整个优化过程拆成信息流,可以理解成:policy 产生多条候选 TIR rollout;reward function 将它们映射为结构化分数;GRPO 再把这些分数转换成同组内的相对优势,最后通过 clipped objective 更新 token policy。ToolRL 的贡献就在于,中间那一层 reward mapping 被做得足够有辨识度,才能让后面的 GRPO 真正学到东西。
3.5 Why fine-grained reward helps
作者在方法和实验之间隐含了一条因果链:tool use 的错误通常具有分层结构,因此正确的 reward 也应当具有分层结构。先错可能错在格式,再错可能错在 tool selection,再错才是参数和值。若 reward 只给 terminal correctness,它会把这些不同层级的失败全部压缩成同一种负例。 finegrained reward 的好处,不只是更 dense,而是它更接近环境真实的 error surface。对 agent 来说,tool_name 错和 parameter_value 错代表完全不同的策略改动:前者需要重做工具选择,后者常常只需在同一工具下修正槽位填充。把这两类错误区分出来,策略学习的局部梯度方向就更清晰。 这也解释了为什么本文结果并非单纯“分得越细越多一点小奖励”那么简单。真正有帮助的是,细粒度奖励保留了部分正确轨迹的结构信息,使 policy 能沿着“先学会合法调用,再学会精确调用”的自然路径爬升,而不是在巨大稀疏误差面上随机搜索。
Pseudocode
下面的伪代码按功能拆开写,分别对应轨迹打包、format reward、correctness reward、length scheduling 和 GRPO 更新。它们不是论文原文算法框的翻译,而是根据 released code 的关键函数整理出的可执行思路。
def build_tir_prompt(messages, tool_history, observations):
prompt = []
for msg in messages:
prompt.append(msg)
for thought, tool_calls, obs in zip(
tool_history["thoughts"], tool_history["calls"], observations
):
prompt.append({"role": "assistant", "content": f"<think>{thought}</think>"})
if tool_calls:
prompt.append({"role": "assistant", "content": f"<tool_call>{tool_calls}</tool_call>"})
if obs is not None:
prompt.append({"role": "tool", "content": obs})
return promptdef compute_format_reward(response_text):
required_tags = ["<think>", "</think>"]
has_think = all(tag in response_text for tag in required_tags)
if "<tool_call>" in response_text:
has_tool_pair = "</tool_call>" in response_text
else:
has_tool_pair = True
if "<response>" in response_text:
has_resp_pair = "</response>" in response_text
else:
has_resp_pair = True
return 1.0 if has_think and has_tool_pair and has_resp_pair else 0.0def compute_tool_call_reward(gt_tools, pd_tools, max_reward, min_reward):
if gt_tools == pd_tools:
return max_reward
gt_names = [t["name"] for t in gt_tools]
pd_names = [t["name"] for t in pd_tools]
score = match_score(gt_names, pd_names)
local_max = 1.0
used = set()
for gt in gt_tools:
local_max += 1.0 + len(gt["parameters"])
best = None
best_score = 0.0
for i, pd in enumerate(pd_tools):
if i in used or pd["name"] != gt["name"]:
continue
param_score = match_score(list(gt["parameters"].keys()), list(pd["parameters"].keys()))
value_score = sum(
1.0 for k, v in gt["parameters"].items()
if k in pd["parameters"] and pd["parameters"][k] == v
)
total = param_score + value_score
if total > best_score:
best = i
best_score = total
if best is not None:
used.add(best)
score += best_score
return (max_reward - min_reward) * score / local_max + min_rewarddef schedule_correctness_reward(global_step, warmup_steps, coarse_max, fine_max):
if global_step < warmup_steps:
return coarse_max
alpha = min((global_step - warmup_steps) / max(warmup_steps, 1), 1.0)
return (1 - alpha) * coarse_max + alpha * fine_maxdef compute_length_reward(num_think_tokens, target_len):
return min(num_think_tokens / max(target_len, 1), 1.0)def compute_grpo_outcome_advantage(token_rewards, eos_mask, group_id, eps=1e-6):
scores = (token_rewards * (token_rewards != 0)).sum(dim=-1)
group_stats = {}
for gid in group_id.unique():
group_scores = scores[group_id == gid]
group_stats[int(gid)] = (group_scores.mean(), group_scores.std())
adv = scores.clone()
for i, gid in enumerate(group_id.tolist()):
mu, std = group_stats[gid]
adv[i] = (scores[i] - mu) / (std + eps)
adv = adv.unsqueeze(-1).expand_as(token_rewards) * eos_mask
return advdef grpo_policy_loss(old_logp, logp, advantages, eos_mask, cliprange):
ratio = torch.exp(logp - old_logp)
loss1 = -advantages * ratio
loss2 = -advantages * torch.clamp(ratio, 1 - cliprange, 1 + cliprange)
return masked_mean(torch.max(loss1, loss2), eos_mask)这些伪代码合起来体现了 ToolRL 的真实训练逻辑:模型不是直接优化最终答案,而是先被要求说出合法的 agent protocol,再被鼓励在工具名、参数名和值上逐步贴近 ground-truth 调用,最后通过 group-relative advantage 学会在同一 query 的多个候选里偏向更可执行的一条轨迹。
Code-to-paper mapping
Code reference:
main@8cee13ec(2025-10-16) — pseudocode and mapping based on this commit
| Paper concept | Source file | Key function/class |
|---|---|---|
| Format reward | verl/utils/reward_score/rlla.py | customize_format_reward_func() |
| Length reward | verl/utils/reward_score/rlla.py | customize_length_reward_func() |
| Tool-call correctness reward | verl/utils/reward_score/rlla.py | compute_tool_call_reward(), customize_correctness_reward_tool() |
| Reward switches / ablations | verl/utils/reward_score/rlla.py | REFINEDREWARD, INTERMEDIATEREWARD, COARSEREWARD, SCHEDULEREWARD |
| GRPO advantage normalization | verl/trainer/ppo/core_algos.py | compute_grpo_outcome_advantage() |
| Clipped policy update | verl/trainer/ppo/core_algos.py | compute_policy_loss() |
| PPO value update | verl/trainer/ppo/core_algos.py | compute_value_loss() |
| Training entrypoint | verl/trainer/main_ppo.py | @hydra.main(...), main_task() |
| GRPO launch script | train_grpo.sh, examples/grpo_trainer/run_grpo.sh | env vars + python3 -m verl.trainer.main_ppo |
| PPO launch script | train_ppo.sh, examples/ppo_trainer/run_ppo.sh | env vars + python3 -m verl.trainer.main_ppo |
| Tool-use dataset | dataset/rlla_4k_raw/rlla.py | dataset construction / JSON tool format |
| BFCL eval | benchmarks/BFCL/rlla_qwen.py | tool-call format conversion / evaluation prep |
| Bamboogle eval | benchmarks/Bamboogle/generate.py | QA benchmark generation |
| API-Bank eval | benchmarks/API-Bank/evaluate.py | API benchmark scoring |
4. Experimental Setup
Datasets used and scale
- ToolACE: 2K samples
- Hammer (Masked): 1K samples
- xLAM: 1K samples
- Training total: 4K RL samples
- SFT baseline: 400 samples,另有 4K SFT upper-bound 设定
Baselines compared
- Raw Instruct Model
- SFT on RL Data (400 / 4K)
- GRPO on SFT Model
- PPO(cold start / SFT init)
Metrics
- Accuracy: 主要 benchmark 的最终正确率
- Avg Num Tool Call: 平均工具调用次数,用来观察是否过度调用工具
- BFCL 子指标:Overall Acc、Non-Live AST Acc、Non-Live Exec Acc、Live Acc、Multi Turn Acc、Relevance Detection、Irrelevance Detection
Training config
- Hardware: 2 × A100 (80G)
- Train batch size: 512
- Val batch size: 128
- Max prompt length: 2048(GRPO),1024(PPO)
- Max response length: 1024(GRPO),512(PPO)
- LR: actor
1e-6,PPO critic1e-5 - PPO mini batch size: 128
- PPO micro batch size: 8
- KL coeff:
0.001 - Rollouts:
n=4,vllm, GPU memory utilization0.6(GRPO)/0.3(PPO) - Epochs: 15
- Save/Test freq: 15 / 5
这些训练超参数不是从 README 默认值抄来的,而是来自 released scripts 中的具体 launch 配置:GRPO 相关项可在 train_grpo.sh 与 examples/grpo_trainer/run_grpo.sh 找到,PPO 相关项可在 train_ppo.sh 与 examples/ppo_trainer/run_ppo.sh 找到。对复现者来说,最重要的不是单个数值本身,而是作者把 reward 开关、rollout 数量、prompt/response 截断长度和 optimizer 设置一起联动调节。
5. Experimental Results
Main numbers
- BFCL V3:Qwen2.5-3B-Instruct 的
Ours, GRPO Cold Start达到 52.98%,高于 Raw 的 33.04% 和 SFT400 的 34.08%;Qwen2.5-7B-Instruct 的Ours, GRPO Cold Start达到 58.38%。 - API-Bank:Qwen2.5-3B-Instruct 的
Ours, GRPO Cold Start达到 67.00%;Qwen2.5-1.5B-Instruct 达到 63.15%。SFT400+PPO 的部分设置能很强,但 cold start GRPO 在整体泛化上更稳。 - Bamboogle:cold start GRPO 的 accuracy 最高,同时平均 tool call 数不过度膨胀。
Ablations
- Length reward:固定长度奖励通常会掉点,尤其小模型更明显,说明“更长思考”不必然更好。
- Dynamic length reward:比固定 length 稍稳,但整体仍不如原始 reward 设计。
- Reward scale:dynamic scaling 优于 two-stage coarse;突变式调尺度会伤性能。
- Reward granularity:finegrained > intermediate > coarse,说明把 tool name / param name / value 拆开更利于 credit assignment。
这些 ablation 的共同指向很明确:reward 设计的有效性不是来自某个孤立技巧,而是来自多个设计点之间的一致性。若 granularity 足够细,但 scale 切换生硬,训练仍会不稳;若 scale 很平滑,但 reward 只给 coarse correctness,也学不到参数级精度。ToolRL 的主要实验价值,就在于把这些因素拆开后再重新组合,说明真正有效的是“细粒度 + 合理调度”的组合。
另一个容易忽略的结果是,训练 reward 曲线本身不能直接当作泛化代理。Figure 2 右侧展示了 cold start GRPO reward 随训练上升,但作者没有把这当作充分证据,而是坚持回到 BFCL、API-Bank、Bamboogle 等 benchmark 检查是否真的学到了更通用的 tool policy。这种区分很重要,因为 structured reward 本身就可能被模型“刷分”,而不一定提升真实执行能力。
Limitations
- 论文没有把长推理长度视作普遍收益,length reward 可能引入 overthinking。
- SFT init 并不总是更好,过拟合会限制 RL 探索。
- 方法主要验证在 tool-use / QA benchmark,未覆盖更广泛 agent 环境。
另外,论文虽然覆盖了多 benchmark、多模型规模和多种 reward 变体,但依然建立在有 ground-truth tool trajectory 或可比较工具调用的 setting 上。若未来任务的工具空间更开放、参数 schema 更弱结构化,当前这种基于 name/key/value matching 的 reward 可能需要重新设计。换句话说,ToolRL 证明了 reward decomposition 很重要,但 decomposition 的具体形式仍受工具接口规范程度影响。
Conclusion
结论很直接:对 tool use 来说,reward 设计本身就是核心算法的一部分。把结构、正确性、粒度和动态调度设计好,比单纯堆 SFT 数据更关键。