在《历时两个月的GRPO 训练踩坑实录:63% -> 96% tool selection》中,我们把工具选择准确率从 63% 做到了 97%。模型上线后,我们以为可以收工了。然而,当我们拆开线上数据仔细分析时,却发现了一个严峻的问题:在多步组合场景下,模型的准确率仅为63%。 一切仿佛又回到了原点。
97% 的假象
模型在线上平稳运行了两周,整体指标看起来相当漂亮:工具选择准确率稳定在 95% 以上。但当我们按场景进行细分统计时,真相浮出了水面。对于需要多步组合完成的复杂场景(例如“如果余额够买X股,就帮我下单”),模型的准确率只有可怜的 63%。
多步场景到底难在哪?
我们抽取了数十条多步场景下的错误案例进行分析,发现了一个明显的规律:模型的推理过程(reasoning)写得逻辑清晰、头头是道,但最终选择的工具(tool)却经常是错误的。
来看一个典型错误案例:
用户: “先查一下余额,如果够的话帮我转 10 万到理财账户”
模型输出:
{
"thought": “用户想先确认余额是否充足,然后转账。需要先查余额。”,
"action": {"tool": “transfer_funds”, “params”: {“amount”: 100000}},
"plan": [“check balance”, “transfer if sufficient”]
}
你看,reasoning 写得完全正确——“需要先查余额”。但是 action 却直接跳到了 transfer_funds(转账)。thought 和 action 完全脱节了。
在单步场景下,这个问题几乎不会出现。“查余额”只对应 check_balance,“买基金”只对应 place_order,用户的意图和所需工具是一对一的映射关系。但在多步场景中,一句话同时涉及 check_balance 和 transfer_funds,模型不仅需要理解用户的完整意图,还必须判断出当前步骤应该执行哪个工具。这对模型推理能力的要求是完全不同的。
问题根源出在两个层面:一是原有的输出格式不足以支持复杂的多步推理表达;二是奖励函数的精度跟不上复杂场景的评判需求。
奖励函数升级:从规则匹配到 Ground Truth 比对
我们在上一版中使用的奖励函数是基于规则的:检查输出格式是否完整、工具是否在合法列表中、是否包含了 reasoning 字段。这在单步场景下基本够用——因为格式正确的输出,其工具选择大概率也是正确的。
但在多步场景下,这套规则就失效了。check_balance 和 transfer_funds 都是合法工具,输出格式也都完整,规则匹配给出的分数几乎一样。这种粗糙的奖励函数根本无法区分“对的第一步”和“对的第二步”。
升级方向非常明确:放弃猜测,直接比对 Ground Truth。不再依赖规则去推测好坏,而是直接将模型的输出与人工标注的标准答案进行精确匹配。
class MultistepGTReward:
def __call__(self, prompts, completions, **kwargs):
solutions = kwargs.get("solution", [])
rewards = []
for completion, solution in zip(completions, solutions):
parsed = parse_json(completion)
expected_tool = parse_json(solution).get(
"action", {}).get("tool")
actual_tool = parsed.get("action", {}).get("tool")
# Tool correctness — 占最大权重
if expected_tool and actual_tool == expected_tool:
t_score = 0.5 # 选对了
elif actual_tool in VALID_TOOLS:
t_score = 0.1 # 选错了但合法,保留格式激励
else:
t_score = 0.0
# Params bonus
p_score = 0.2 if has_valid_params(parsed) else 0.0
# Reasoning + Plan
r_score = reasoning_score(parsed) # 最高 0.3
rewards.append(t_score + p_score + r_score)
return rewards
这个奖励函数的设计有几个关键考量:
1. 工具正确性(Tool correctness)占 0.5 分,这是最大的单项分数。选对和选错之间的分差达到 0.4,足以产生明确、强烈的学习信号,告诉模型“选对工具至关重要”。
2. 选错但合法的工具给 0.1 分,而不是 0 分。这很关键。如果完全不给分,模型可能会发现“什么都不输出”的期望奖励比“输出一个错误但合法的工具”更高,从而导致它连基本的输出格式都不愿意保持。
3. Reasoning 和 Plan 作为辅助信号保留,鼓励模型进行思考,但其权重(最高0.3)明确低于工具选择的正确性。
设计奖励函数的核心原则就一条:你最关心什么,就把最大的权重分配给什么。对于工具选择任务,工具的正确性就是最高优先级。
多步场景下的格式强化
奖励函数的升级解决了“如何评判”的问题,但多步场景还需要解决“如何表达”的问题。原有的输出格式过于紧凑,模型没有足够的空间展开复杂的逐步推理。
我们做了以下几项调整来强化格式:
加长思考空间:将 max_completion_length 从 200 提高到 350。原来的 200 个 token 对简单单步场景够用,但多步推理需要模型把“先做 A,因为 B,然后才能做 C”这个完整的逻辑链条写清楚。
增加探索多样性:将 num_generations 从 4 提高到 8。多步场景的正确答案路径往往不那么直观,需要更多的采样才能覆盖到正确的推理序列。
奖励推理深度:我们设计了一个逻辑关键词列表,如果模型的 thought 字段中包含这些词(如 “first”、“then”、“because”、“before”),会获得额外的加分。这不是为了鼓励凑字数,而是为了激励模型将内在的推理链显式地写出来——写出来的、可追溯的推理,远比模型内部的“心算”更可靠,也便于后续调试和分析。
LOGIC_KEYWORDS = ["first", "then", "before", "because",
"therefore", "need to", "in order to", "since"]
# thought 中包含逻辑词 → +0.05
if any(kw in thought.lower() for kw in LOGIC_KEYWORDS):
r_score += 0.05
Plan 字段从装饰品变为核心组件:之前 plan 只是一个可选的加分项,现在要求 plan 字段非空才能获得这部分分数。在多步场景中,plan 不是锦上添花,而是模型理解任务整体结构和步骤顺序的关键证据。
数据准备:将多步场景展开
多步训练数据需要特殊的处理方式。每一个多步用户指令,都会被展开成多条训练样本,每条样本对应执行链条中的一步:
场景: “查余额,够的话转账 10 万”
样本 1 (第一步):
input: “查余额,够的话转账 10 万”
output: {"thought": “先查余额...”, “action”: {“tool”: “check_balance”}, “plan”: [“check”, “transfer”]}
样本 2 (第二步):
input: “查余额,够的话转账 10 万\n\nPrevious steps:\nStep 1: check_balance → 余额 50 万”
output: {"thought": “余额充足,执行转账...”, “action”: {“tool”: “transfer_funds”}, “plan”: [“transfer”]}
请注意第二步的 input,它包含了前一步的执行结果(“余额 50 万”)。这意味着模型在决定当前步骤的行动时,不仅要理解用户的原始意图,还必须理解和结合当前的执行状态。这比单步场景的难度高出一个数量级。
最终,我们构建的数据集包含 454 条基线单步数据 + 823 条由多步场景展开得到的数据。
NGRPO 依然不可或缺
训练完成后,我们查看了 NGRPO 的统计信息:Zero std: 3243/5108 (63.5%)。
这意味着超过六成的采样组(group)内,所有生成结果获得的 reward 是完全相同的。试想一下,如果没有 NGRPO 机制向这些零方差组中注入虚拟的满分样本,这些组产生的梯度就是零,模型从这些样本中将什么也学不到。
一个有趣的现象是,上一轮在简单场景训练时,zero-std 的比例是 67%,而在这次更复杂的多步场景训练中,这个比例反而降到了 63.5%。这可能是因为任务变难了,模型对于同一个问题的生成结果更加多样化,导致 reward 的方差自然增大了一些。
无论如何,这个数据再次证明,NGRPO 不是一次性 Tricks,而是 GRPO 这类离线强化学习训练方法的基础设施。只要你的任务中存在大量模型“已经掌握”或容易产生同质化输出的样本,NGRPO 就是保证训练信号持续流动的关键。在探索复杂任务如多步推理时,理解并善用这类技巧至关重要。
最终结果
经过上述一系列优化,我们得到了令人满意的结果:
- 多步场景准确率:从 63% 提升至 97%。
- 整体准确率:维持在 97%,且单步场景的性能没有发生退化。
剩余的 3% 错误案例经分析全部属于同一类型:
“I need to take out 75 rupees from ACC888” → 模型选 withdraw,标注是 check_balance
“Withdraw 50 rupees from ACC002” → 模型选 withdraw,标注是 check_balance
“Made profit in day trading. Can I withdraw it tonight?” → 模型选 withdraw,标注是 check_balance
在这些案例中,用户明确说出了“取钱”、“withdraw”,模型选择 withdraw 工具是完全符合直觉的。而标注人员出于业务安全流程的考虑,认为应该先 check_balance 确认余额。这本质上是标注标准层面的歧义,而非模型本身的能力问题。
因此,我们判断 97% 的准确率已经接近当前数据集和任务定义下的性能天花板。
经验总结
- 奖励函数的精度必须与任务难度同步升级。简单任务用规则匹配或许足够,但面对复杂任务(如多步推理),必须采用与 Ground Truth 直接比对的精确奖励。这不是说最初的方案错了,而是当场景发生变化时,我们的技术方案必须随之进化。
- 分数分配直接反映了优化优先级。Tool correctness 0.5分,Params 0.2分,Reasoning 0.3分——这个权重分配清晰地告诉模型:选对工具是最重要的。你最关心什么,就应该在奖励函数中给予最大的权重。
- 为模型提供足够的“思考”空间。
completion_length(输出长度)和 num_generations(采样数量)等超参数需要与任务复杂度匹配。多步推理需要更长的文本来展开逻辑,也需要更多的采样来覆盖正确的推理路径。
- 知道何时该停止优化。当剩余的错误主要由标注歧义或任务定义模糊导致时,继续在模型层面进行优化是徒劳的。此时应该转而审视数据和质量标准。
希望这篇针对复杂任务进行强化学习优化的实战记录,能为你提供一些可行的思路。如果你在实践GRPO或类似技术时遇到瓶颈,或许可以来云栈社区与更多同行交流探讨。