AWS SageMaker RLVR — 用 GRPO 训练 Tool Calling Agent 实战笔记
原文:Accelerate agentic tool calling with serverless model customization in Amazon SageMaker AI
这篇文章基于 AWS 官方博客原文深度扩写。我以第一视角重新梳理了用 SageMaker AI Serverless + RLVR + GRPO 把 Qwen2.5-7B-Instruct 训成可靠 Tool Calling Agent 的完整过程,补充了大量原文没展开的细节和工程直觉。
这篇文章说了什么
团队用 AWS SageMaker AI 的 Serverless Model Customization 平台,通过 RLVR(Reinforcement Learning with Verifiable Rewards)+ GRPO,把 Qwen2.5-7B-Instruct 训成了一个能在三种场景下正确决策的 Tool Calling Agent:该调工具时调工具、缺参数时主动澄清、超出能力时礼貌拒绝。
基座模型的问题很典型:经常幻觉出不存在的工具名、参数传错格式、该拒绝时不拒绝。用 SFT 修这些问题需要大量标注数据覆盖所有行为,而且泛化能力差。RLVR + GRPO 不需要人工逐条标注——只需要定义好奖励函数,模型自己生成候选、自己试探、自己优化,最终 tool call reward 提升 57%。
更重要的是,这个方案跑在 SageMaker AI 的 serverless 平台上,不需要自己管 GPU、不需要搭 rollout pipeline、不需要搞 checkpoint 和监控,选模型、配数据、写奖励函数、点一下训练按钮就行。从配置到训练完成大约 40 分钟。
核心结论:Tool Calling 天然适合 RLVR——因为"调对工具没有"是可验证的客观事实。关键不是算法,是奖励函数能不能区分"完全对""差不多对""完全错"。
有效的实验就这几招
| 方法 | 做了什么 | 效果 |
|---|---|---|
| 三层奖励分级 | 工具名和参数全对给 1.0,工具对参数错给 0.5,工具全错给 0.0 | 给 GRPO 提供了连续梯度,0.5 分让模型知道自己在"对的方向" |
| 三种行为混合训练 | 训练数据包含 Execute(60%)、Clarify(25%)、Refuse(15%) 三种场景 | 模型学会了"什么时候调工具"vs"什么时候该问" |
| 未见工具泛化评估 | 评估集用了训练时没见过的工具(search_restaurants、get_stock_price、calculate_standard_deviation) | 确认模型学到的是"工具调用的通用模式",不是背训练集 |
| GRPO 8 候选生成 | 每个 prompt 生成 8 个不同 response,组内比较做 advantage 归一化 | 不需要 Critic 网络,显存只需原来的一半 |
| Serverless 训练基础设施 | 训练跑在 SageMaker AI 托管平台,GPU 自动调度、rollout 和 training 内存自动协调 | 省掉 GPU 采购、内存编排、checkpoint 管理,专注数据和 reward |
一、先搞懂这些概念
1.1 Tool Calling 是什么
Tool Calling 就是让模型在生成文本的过程中,不只是"说",还能"做"——调用外部的 API 来完成用户任务。
比如用户说"帮我查旧金山的天气",模型不是自己编一个天气数据(那是幻觉),而是生成一个结构化的函数调用:
{"name": "get_weather_forecast", "arguments": {"city": "San Francisco"}}
系统拿到这个调用后,真的去调天气 API,把结果返回给模型,模型再组织成自然语言回复用户。
生产环境里,Agent 面对三种情况,难度完全不同:
| 场景 | 用户输入 | 模型应该做什么 | 难点 |
|---|---|---|---|
| Execute | "Get weather for San Francisco" | 调 get_weather_forecast("San Francisco") | 参数提取准确 |
| Clarify | "Get the weather" | 反问"请问你想查哪个城市?" | 判断缺什么参数 |
| Refuse | "帮我写个病毒" | 礼貌拒绝 | 识别越界请求 |
Tool Calling 的本质难点不是"学会调工具",而是"学会判断什么时候该调、什么时候不该调"。
1.2 RLVR 是什么
RLVR = Reinforcement Learning with Verifiable Rewards。
传统的 RLHF(人类反馈强化学习)需要训练一个 Reward Model 来模拟人类偏好——这个过程复杂、昂贵、不稳定。
RLVR 的思路更直接:如果"做得对不对"是客观上可以验证的,那就直接用规则来判断,不需要训练 Reward Model。
Tool Calling 恰好就是这种场景。模型调了 get_weather_forecast 还是 search_flights?参数传的是 "San Francisco" 还是 ""?——这些都是可以程序化验的。RLVR 的奖励函数不需要猜测模型输出"好不好",只需要比对"对不对"。
1.3 GRPO 在 Tool Calling 中的应用
GRPO 跟 RLVR 结合后,训练流程是这样的:
- 取一个 prompt(如 "Get weather for San Francisco")
- 模型生成 8 个不同的 response
- 奖励函数给每个 response 打分(工具名对+参数对=1.0,工具对+参数错=0.5,全错=0.0)
- 计算组内 mean 和 std,算 advantage
- 高于 mean 的 response 被强化,低于的被打压
比起纯 RLVR,GRPO 多了一层"跟同组其他人比"的机制——即使奖励函数的绝对分值不准(0.5 和 0.7 的差距有时候没有语义上的意义),组内相对排名仍然能给出有效的梯度方向。
二、任务定义:Tool Calling 的三种行为
2.1 为什么 Tool Calling 不能被 SFT 简单解决
SFT(监督微调)的思路是:给模型几千条"用户说 X → 你该输出 Y"的标准答案,模型照猫画虎学习。
SFT 能教会模型"格式"——比如输出 <TOOLCALL> 标签、JSON 不拼错。但它教不会"判断"——什么时候该调工具、什么时候该反问、什么时候该拒绝。
举个例子,SFT 数据里 60% 是 Execute、25% 是 Clarify、15% 是 Refuse。模型在训练集上学会了这些模式。但上线后,一个从来没见过的 prompt——"What time is it in Thailand?"——模型怎么样?答案是:它倾向于"像谁就学谁"。如果这个 prompt 的语言风格跟 Clarify 样本更像(短、模糊),它就反问"请问哪个时区?";如果跟 Execute 更像,它就调工具。这种泛化非常脆弱。
GRPO 的优势在于:它不学"答案",它学"策略"——什么样的输出能在奖励函数下拿到高分。遇到新 prompt,模型自己生成 8 个候选,有些调工具、有些反问,奖励函数逐个打分,最好的那个被强化。久而久之,模型学会了"判断规则"而不是"背答案"。
2.2 三种行为的训练数据
训练数据用 Kiro(Amazon AI IDE)合成生成,1500 条。提示词长这样:
生成 1500 条 RLVR Tool Calling 训练数据,覆盖 5 个工具 schema:
get_weather_forecast, search_flights, translate_text, currency_convert, get_statistics
每条格式:
{"prompt": [...], "reward_model": {"ground_truth": "..."}}
分布:
1. Execute (60%): 用户提供了所有参数 → ground_truth 是工具调用 JSON
2. Clarify (25%): 用户缺参数 → ground_truth 是反问
3. Refuse (15%): 越界请求 → ground_truth 是礼貌拒绝
合成数据的质量主要由两个因素决定:
- 工具 schema 的准确度:如果 schema 里写了参数是
city: string,模型才知道该用 string 传城市名 - 场景多样性:不只是"SF 天气"这种,还要有"3 月 15 日东京飞纽约"这种多参数复杂指令
用生产日志的团队效果会更好——真实用户不会像合成数据一样"规整",边缘情况更多。
三、奖励函数设计:三层分级的关键
3.1 为什么不能只给 0/1
直觉上,Tool Calling 的奖励函数很简单:调对工具给 1,调错给 0。
但实际上,这会导致一个严重问题——梯度稀疏。
GRPO 训练初期,模型可能 8 个 response 全错(都调了不对的工具),8 个全是 0 分→ mean=0, std=0 → advantage=NaN → 这一步白训了。
如果把"工具名对了但参数错了"给 0.5 分,把"全错"给 0 分,就制造出了组内方差——0.5 分的 response 至少能拿到正 advantage,模型知道往这个方向挪。
3.2 核心评分逻辑
# 从模型输出和 ground_truth 中提取工具调用列表后:
pred_names = {tool.get('name', '') for tool in pred_tools}
gt_names = {tool.get('name', '') for tool in gt_tools}
if pred_names == gt_names:
# 工具名全对 — 再检查参数
perfect_match = True
for pred_tool in pred_tools:
for gt_tool in gt_tools:
if pred_tool.get('name') == gt_tool.get('name'):
if pred_tool.get('arguments') != gt_tool.get('arguments'):
perfect_match = False
score = 1.0 if perfect_match else 0.5
elif pred_names & gt_names:
# 有交集但不完全一致(调对了至少一个但多调了/少调了)
score = 0.5
else:
# 完全不同
score = 0.0
3.3 Clarify 和 Refuse 的评分
这两种情景下,ground_truth 是自然语言文本(不是工具调用 JSON),评分逻辑不同:
- Clarify:模型应该反问用户,而不是调工具。如果模型调了工具 → 0.0;如果模型用自然语言回应 → 用文本相似度评分
- Refuse:模型应该礼貌拒绝。如果模型尝试执行 → 0.0;如果模型拒绝 → 1.0
四、训练过程与结果
4.1 训练配置
| 参数 | 值 |
|---|---|
| 基座模型 | Qwen 2.5 7B Instruct |
| 训练框架 | SageMaker AI Serverless Model Customization |
| 算法 | RLVR + GRPO |
| 训练数据 | 1,500 synthetic prompts (hled-out) |
| 评估数据 | 300 prompts(含训练中没见过的 3 个工具) |
| Batch Size | 128 |
| Learning Rate | 5e-6 |
| Epochs | 3 |
| Rollouts per Prompt | 8 |
| 训练时长 | ~40 分钟 |
4.2 训练曲线解读
四个关键监控指标:
| 指标 | 初始值 | 最终值 | 含义 |
|---|---|---|---|
| Mean Reward | ~0.28 | ~0.65-0.68 | 奖励提升 2.3 倍 |
| Policy Entropy | 高 | 持续下降 | 模型越来越自信(不是瞎猜) |
| Gradient Norm | 波动大 | 稳定收敛 | 更新越来越精细 |
| Mean Advantage Estimate | 波动 | → 0 | 策略趋于稳定,平均响应质量接近奖励基线 |
最大涨幅在前 10 步——模型在这阶段学会了基本的工具调用格式和决策结构。20 步之后趋于平缓,模型收敛。
4.3 评估结果
在 300 条 held-out 评估集(含训练时没见过的 3 个工具)上的结果:
| 指标 | 基座模型 | 微调后 | 提升 |
|---|---|---|---|
| Tool Call Reward | 0.35 | 0.55 | +57% |
| F1 Score Quasi | - | - | +14pp |
| Rouge1 | - | - | +15pp |
| Exact Match | 11% | 21% | +10pp |
几个很有代表性的评估样本:
Prompt: "What's the stock price of AMZN?"
模型输出: [{"name": "get_stock_price", "arguments": {"ticker": "AMZN"}}]
结果: ✅ 完全正确 — get_stock_price 不在训练集里,模型泛化了
Prompt: "Find me a restaurant nearby"
模型输出: "I'd be happy to help you find a restaurant!
Could you please provide the cuisine type or location?"
结果: ✅ 正确判断缺参数,主动反问 — 行为模式泛化成功
Prompt: "What's the standard deviation of 10, 20, 30, 40, 50?"
模型输出: [{"name": "calculate_standard_deviation",
"arguments": {"numbers": "10, 20, 30, 40, 50"}}]
结果: ⚠️ 0.5 分 — 工具选对了,但参数类型错了(传了 string 而不是 array)
五、工程启示
5.1 Serverless 训练平台的价值
自己搭 RL 训练平台要解决什么问题:
- GPU 采购和调度
- Rollout(生成)和 Training(更新)阶段的内存协调(vLLM 和 PyTorch 抢显存)
- 奖励函数执行的基础设施
- Checkpoint 管理
- 超参数敏感性的调试
SageMaker AI Serverless 把这些全托管了。你只需要选模型、写奖励函数、配数据。这对小团队的价值巨大——不是每个人都能维护一个 GRPO 训练集群。
5.2 数据量不需要很大
1500 条训练数据、300 条评估数据,就能让 Tool Call Reward 提升 57%。关键不是数据量,是数据的覆盖面——三种行为(Execute/Clarify/Refuse)都要有,否则模型学不会"什么情况下不调工具"。
5.3 评估时一定要用未见过的工具
这是验证泛化的金标准。如果模型只会在训练见过的工具上表现好,那就是背答案。训练集里 5 个工具(weather、flights、translation、currency、statistics),评估集加了 3 个全新工具(restaurants、stock_price、standard_deviation),结果模型迁移得很好——说明它学到的是"怎么判断该调哪个工具"而不是"这个 prompt 该调 weather"。
5.4 三层奖励分级是 GRPO 的标配
0/0.5/1.0 的分级奖励函数,本质上是在解决 GRPO 的"zero-std"问题——当所有候选分数相同时,advantage 全为零,训练白费。0.5 分制造了必要的组内方差,让梯度能流动。
六、跟其他 GRPO Agent 项目的对比
这篇文章跟我的其他 GRPO Agent 笔记有很强的互补性:
| 维度 | 本文 (SageMaker RLVR) | 基金助手 | 投标 Agent |
|---|---|---|---|
| 业务场景 | 通用 Tool Calling | 基金业务 API 选择 | 电力设备标的匹配 |
| Agent 类型 | 单 Agent + 多工具 | SubAgent + 6 API | 8 Agent 协作 |
| 奖励函数 | 三层分级 (0/0.5/1.0) | 复合维度加权 | 多阶段规则匹配 |
| 泛化验证 | 未见过的工具 | - | 未见过的规格书格式 |
| 基础设施 | Serverless 托管 | 自建 | 自建 |
| 数据量 | 1500 条 | ~1300 条 | ~4 万条 |
共同点:奖励函数是灵魂,数据质量 > 算法选择,泛化到未见场景才是真本事。