agent的强化学习
Agent SFT 与 RL 训练 —— 学习笔记
一、SFT 基础
Q:什么是对智能体的 SFT?
SFT = Supervised Fine-Tuning(监督微调),用"输入-输出"标注数据对模型进行有监督训练,让模型学会特定的行为模式。微调的是智能体底层的基础模型(如 Qwen、LLaMA 等),不是智能体的"外壳"逻辑。
Q:智能体 SFT 和普通 SFT 有什么区别?
| 训练目标 | 普通 SFT | 智能体 SFT |
|---|---|---|
| 核心能力 | 回答问题 | 规划、调用工具、反思 |
| 数据格式 | 问→答 | 任务→思考→工具调用→观察→结果 |
| 输出形式 | 自然语言 | 结构化动作(JSON/函数调用) |
简单说:智能体 SFT = 教模型"怎么干活",而不只是"怎么说话"。
Q:智能体 SFT 的训练数据长什么样?
一条训练样本是一条完整的轨迹(Trajectory):
用户:帮我查一下明天北京的天气,然后写一封出行建议邮件
模型思考:需要先调用天气API,再根据结果写邮件
→ 调用工具:weather_api(city="北京", date="明天")
→ 观察结果:晴,25°C
→ 生成邮件内容:...
→ 最终回复用户
整条链路打包成一条样本去训练。
Q:为什么要对智能体做 SFT?
- 基础模型不会用工具,需要教它什么时候调用、怎么调用
- 减少幻觉动作,避免模型"凭空捏造"工具调用结果
- 提升规划能力,让模型学会把复杂任务拆解成合理步骤
- 针对垂直场景(客服、编程、搜索)定制行为
二、Observation Mask 问题
Q:为什么要在计算 loss 时把 <observation> 内容 Mask 掉?
训练的本质是让模型学会预测下一个 token,并对预测错误的地方进行惩罚(梯度更新)。
但 observation 的内容是工具调用后外部环境返回的结果(如天气 API、搜索结果),模型根本无法预测,也不应该去预测,因为它们取决于真实世界,不是模型的输出。
如果不 Mask 会怎样?
| 问题 | 具体表现 |
|---|---|
| 学到错误目标 | 模型以为自己要"生成"工具结果 |
| 幻觉加剧 | 推理时不调用工具,直接编造 observation |
| 梯度污染 | 无意义的 loss 干扰真正需要学习的部分 |
Mask 之后,模型只学什么?
<thought> 我需要先查天气 </thought> ← ✅ 学,这是模型的思考
<action> weather_api(city="北京") </action> ← ✅ 学,这是模型的决策
<observation> 晴天,25°C </observation> ← ❌ Mask,不计算loss
<thought> 已获得天气,现在写邮件 </thought> ← ✅ 学,这是模型的思考
<action> 生成邮件... </action> ← ✅ 学,这是模型的决策
核心原则:模型说的话算 loss,环境给的信息不算 loss。
Q:Mask 的边界问题(最大坑点)是什么?
不同的前缀或后缀组合会改变分词(tokenization)结果,进而干扰训练效果。
原因:tokenizer 是贪心地对整段文本做分词,不是逐个字符独立处理的,所以标签周围的字符会影响标签本身被切成哪些 token。
例如:
单独编码:"</observation>\n\n" → [512, 89, 13, 13]
在完整文本里整体编码后,同样位置可能变成 → [512, 91, 13, 13]
↑ 变了!
Q:Mask 的正确做法是什么?不依赖字符串匹配,而是依赖 token 匹配。
错误做法(会踩坑):
在原始文本里找 "</observation>" 的字符位置
→ 然后映射到 token 序列里做 mask
问题在于:字符位置 → token 位置的映射,会因为上下文不同而出错。
正确做法:
先把 "</observation>\n\n" 单独 encode 成 token 序列
→ 在整体 token 序列里查找这个子序列
→ 直接在 token 层面做 mask
例子:
假设 "</observation>\n\n" 单独编码为 [512, 89, 13, 13]
整条轨迹的 token 序列:
[...前面... | 101, 205, 333 | 512, 89, 13, 13 | ...后面...]
↑observation内容↑ ↑结束标签↑
直接在序列里搜索 [512, 89, 13, 13],找到位置,前面全部 mask。
Q:固定格式(observation 后跟空格/换行)的作用是什么?——一种解决方案
不是保证 tokenization 结果绝对一致,而是降低匹配失败的概率,让预处理阶段的子序列搜索更稳定可靠。
Q:Mask 是在训练时做还是训练前做?
训练之前的预处理阶段就算好,不是训练时实时匹配的。
完整流程:
第一步:数据预处理阶段
把完整轨迹文本 → 整体 tokenize → 得到 token id 序列
在序列里搜索标签子序列 → 标记哪些位置需要 mask
生成 mask 数组,例如:
[0, 0, 1, 1, 1, 0, 0, 0, 1, 1]
↑prompt↑ ↑obs内容↑ ↑model输出↑
第二步:训练阶段
token id 序列 + mask 数组 一起喂给模型
模型计算 loss 时直接查 mask 数组
mask=0 的位置不计入 loss
Q:预处理时匹配不上怎么办?
实践中的处理方式:
- 匹配失败直接丢弃样本:宁可少数据,不要错误 mask
- 加校验统计:统计匹配失败率,失败率太高说明格式有问题
- 从根源控制格式:生成训练数据时严格约束标签前后的字符
最根本的解法:把标签注册为 special token。 Special token 不受上下文影响,永远是固定的 token id,彻底解决匹配问题。不需要重新训练 tokenizer,只需要在词表里注册新 token。
三、普通 RL vs Agent RL
Q:普通模型的 RL 和智能体的 RL 有什么不同?
普通模型 RL(如 RLHF):
用户问题 → 模型生成回答 → 奖励模型打分 → 更新参数
- 单轮交互,一问一答
- 奖励来源简单
- 轨迹短,就一次输出
Agent RL:
任务 → 思考 → 调工具 → 观察结果 → 再思考 → 再调工具 → ... → 最终答案
- 多轮交互,一个任务可能走 10~20 步
- 奖励稀疏:只有最终结果才能拿到奖励
- 环境有状态:每次工具调用都会改变环境
- 轨迹很长:梯度计算、信用分配都更难
核心差异:长程信用分配(Credit Assignment)
普通 RL:输出好 → 这次生成整体加分 ✅ 简单
Agent RL:最终答案对了 → 但到底是第3步调对了工具功劳大?
还是第7步推理正确功劳大? ❓ 很难判断
一句话:普通模型 RL 像考一道选择题打分,Agent RL 像考一场需要查资料、多步推理的开卷大题。
四、GRPO 的 Rollout 阶段
Q:什么是 Rollout?
Rollout = 让模型跑一遍,生成完整轨迹的过程。RL 训练每次更新参数之前,需要先"采样"一批数据,这个采样过程就叫 rollout。
类比:训练下棋 AI 时,先让 AI 实际下几盘棋记录每一步决策,再根据最终输赢更新参数。
Rollout 阶段不需要计算梯度,只需要快速生成文本,所以用 vllm(高速推理引擎)。训练阶段才用 PyTorch 算梯度更新参数。
Q:常规 GRPO 的 Rollout 和 Agent GRPO 的 Rollout 有什么区别?
常规:
prompt → vllm → response(一次性输入,一次性输出)
Agent(以搜索 Agent 为例):
Step 1:
prompt → vllm → think + plan + web_search(中断生成)
模型生成到"我要调用搜索工具"时,暂停,把控制权交给工具。
中间:
系统执行工具,拿到结果,填入 <observation>...</observation>
Step 2:
[prompt + think + plan + web_search + observation] → vllm → think + answer(终止生成)
把完整上下文重新喂给模型,继续生成直到最终答案。
Step2 的输入 = Step1 的输入 + Step1 的输出 + 工具执行结果,上下文是连续的,不会遗忘。
Q:GRPO loss 计算时,哪些部分参与训练?
常规:
prompt(❌ mask) response(✅ loss)
Agent:
prompt(❌) think+plan+web_search(✅) observation(❌) think+answer(✅)
规律:模型自己输出的内容算 loss,环境给的内容 mask 掉。
五、veRL 框架
Q:veRL 是什么?
veRL(Volcano Engine Reinforcement Learning)是字节跳动火山引擎团队开源的强化学习训练框架,专为大型语言模型的后训练(Post-Training)设计,是 HybridFlow 论文的开源实现。
特点:
- 通过 Hybrid 编程模型,结合单控制器和多控制器范式,灵活表示复杂的后训练数据流
- 与 PyTorch FSDP、Megatron-LM、vLLM 等主流框架无缝集成
- 支持 PPO、GRPO 等多种 RL 算法
- 已被 TinyZero、RAGEN、Logic R1 等优秀项目采用
在 Agent 训练中的定位:
你写好:奖励函数 + 训练数据
veRL 负责:分布式调度、多模型管理、高效 GPU 利用
六、GRPO 算法
Q:GRPO 的核心思路是什么?
GRPO = Group Relative Policy Optimization,关键词是 Group(组) 和 Relative(相对)。
具体做法:
第一步:对同一个问题,采样一组轨迹(比如 8 条)
问题:"北京明天天气怎样,要不要带伞?"
轨迹1:搜索天气→分析→回答"要带伞" 最终得分 1.0
轨迹2:搜索天气→直接回答"不用带伞" 最终得分 0.0
轨迹3:搜索天气→搜索历史→回答"要带伞" 最终得分 0.8
轨迹4:乱调工具→回答"不知道" 最终得分 0.0
第二步:计算相对优势(不用绝对分数,看比组内平均好多少)
组内平均分 = (1.0+0.0+0.8+0.0...) / 8 = 0.45
轨迹1的优势 = 1.0 - 0.45 = +0.55 (比平均好,强化它)
轨迹2的优势 = 0.0 - 0.45 = -0.45 (比平均差,抑制它)
轨迹3的优势 = 0.8 - 0.45 = +0.35 (比平均好,强化它)
轨迹4的优势 = 0.0 - 0.45 = -0.45 (比平均差,抑制它)
第三步:把优势分数摊到轨迹的每一步
轨迹1得了 +0.55
→ 轨迹1的每一步(think、tool_call、answer)都获得 +0.55 的强化信号
GRPO vs PPO:
PPO: Actor模型 + Critic模型 (两个大模型,成本高)
GRPO: Actor模型 (一个模型)
用组内相对比较替代 Critic
局限性: 同一条轨迹里,好步骤和坏步骤会被一起奖励或惩罚。比如轨迹3多搜了一次(浪费),但最终答对了,那次多余的搜索也会被正向强化。这就是为什么还需要过程奖励来补充。
七、奖励函数设计
Q:奖励函数和奖励模型是一个东西吗?
不是,但目标相同——都是给模型输出打分。
| 奖励函数 | 奖励模型 | |
|---|---|---|
| 本质 | 人写的规则 | 训练出的神经网络 |
| 适用场景 | 有标准答案(数学、代码、工具调用) | 主观评价(写作质量、是否有帮助) |
| 典型应用 | Agent 工具调用 | RLHF 对齐训练 |
| 缺点 | 覆盖场景有限 | 可能被模型钻空子 |
Agent RL 里基本用奖励函数,因为任务结果相对客观,可以用规则直接判断。
Q:奖励函数一般关注哪些维度?
1. 结果正确性(最重要,权重最高)
最终答案正确 → +1.0
最终答案错误 → 0 或 -1
2. 行为规范性
工具调用格式合法(合法JSON)→ +0.2
输出了指定的结束标签 → +0.1
格式完全乱掉 → -0.5
调用了不存在的工具 → -0.3
3. 效率
步骤数合理 → +0.1
步骤数过多 → -0.05 × 超出步数
没有死循环调用 → +0.1
组合示例:
def reward(trajectory, final_answer, ground_truth):
score = 0
if final_answer == ground_truth:
score += 1.0 # 结果对不对(权重最高)
if all_tool_calls_valid(trajectory):
score += 0.2 # 工具调用格式合法
if no_repeated_calls(trajectory):
score += 0.1 # 没有死循环
steps = len(trajectory)
if steps > 10:
score -= 0.05 * (steps - 10) # 步骤数惩罚
return score
重要原则:结果奖励权重远大于过程奖励,过程奖励只是辅助引导。
Q:奖励函数最大的坑是什么?
Reward Hacking(奖励攻击):模型会找到"钻空子"的方式拿高分。
| 你的意图 | 模型学到的捷径 |
|---|---|
| 鼓励调用工具 | 疯狂调工具但不看结果 |
| 鼓励答案简洁 | 输出空答案 |
| 惩罚错误答案 | 拒绝回答 |
| 格式规范 | 内容空洞但格式完美 |
本质:奖励函数定义的是你能度量的,不一定是你真正想要的。 需要持续观察模型行为,发现钻空子就补漏洞,是一个迭代过程。
Q:奖励函数中,奖励是只在最终结果后计算吗?
不一定,分两种:
稀疏奖励(只在最终结果计算):
step1 → step2 → step3 → 最终答案 → 算奖励
0 0 0 ↑只有这里打分
优点:简单。缺点:信号太稀疏,模型很难知道哪一步做对了。
密集奖励(每步都计算):
step1 → step2 → step3 → 最终答案
+0.3 -0.1 +0.2 +1.0
优点:信号丰富。缺点:中间步骤奖励很难设计准确。
实践中常用的组合:
工具调用格式对? → 即时给小奖励 (过程奖励)
工具返回了有效结果? → 即时给小奖励 (过程奖励)
最终答案正确? → 给大奖励 (结果奖励,权重远大于过程奖励)
还有一种回溯分配(Monte Carlo):最终结果出来后把奖励往前摊分给每一步,GRPO 本质上就是这类思路。
Q:怎么判断一个回答"正确"?
按能不能客观验证分两类:
第一类:有标准答案,可直接验证(最适合做 RL)
# 数学题
if extract_number(response) == ground_truth:
return 1.0
# 代码生成
if run_tests(response) == "all passed":
return 1.0
# 工具调用
if task_completed(environment):
return 1.0
DeepSeek-R1 最早在数学和代码上突破,原因就是验证容易。
第二类:没有标准答案,三种解法:
① LLM as Judge:用另一个大模型打分
response → GPT-4/Claude → "这个回答质量如何?" → 0.85分
优点:覆盖面广。缺点:成本高,裁判模型本身可能有偏见。
② 训练奖励模型:收集人类偏好数据训练 Reward Model(RLHF 的做法)。
③ 规则拆解主观问题:
有没有回答用户的核心问题? +0.3
有没有事实性错误? -0.5
长度是否合适? +0.1
实践中最常用的组合:
能直接验证的部分 → 规则函数(准确、便宜)
不能验证的部分 → LLM Judge(灵活、贵)
两者加权组合 → 最终奖励分数

浙公网安备 33010602011771号