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?

  1. 基础模型不会用工具,需要教它什么时候调用、怎么调用
  2. 减少幻觉动作,避免模型"凭空捏造"工具调用结果
  3. 提升规划能力,让模型学会把复杂任务拆解成合理步骤
  4. 针对垂直场景(客服、编程、搜索)定制行为

二、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:预处理时匹配不上怎么办?

实践中的处理方式:

  1. 匹配失败直接丢弃样本:宁可少数据,不要错误 mask
  2. 加校验统计:统计匹配失败率,失败率太高说明格式有问题
  3. 从根源控制格式:生成训练数据时严格约束标签前后的字符

最根本的解法:把标签注册为 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(灵活、贵)
两者加权组合      → 最终奖励分数
posted @ 2026-05-20 19:41  Noamateur  阅读(42)  评论(0)    收藏  举报