函数组件概述
RL 训练需要您开发自定义函数来控制模型的交互行为和评分逻辑。函数代码会自动部署到云端运行:
组件 | 职责 | 是否必须 |
|---|---|---|
Rollout | 定义 Agent 如何调用 LLM 和工具,生成交互轨迹 | 必须(1 个) |
Reward | 对单条 Agent 输出评分,定义训练的优化方向 | 可选(1 个或多个) |
Group Reward | 对同一问题的多条输出集体评分(排序或对比评分) | 可选 |
共享数据模型
三个函数组件通过统一的 AgentOutput 对象传递数据,使用 TaskStatus 标记执行状态。
AgentOutput
AgentOutput 是 Rollout 与 Reward 之间的数据桥梁,由 Rollout 函数构造,传递给 Reward 函数:
字段 | 类型 | 说明 |
|---|---|---|
| list | 对话消息列表,包含 user、assistant 和 tool 角色。取最后一条 assistant 消息可获取模型回答 |
| float / None | 预置评分。在 Rollout 中通过 |
| dict | 训练数据中 |
| dict | 自定义指标,如 |
TaskStatus 与错误处理
所有函数组件的 process() 返回结果中必须设置 status 字段,训练框架据此决定是否重试或丢弃该样本:
TaskStatus.SUCCESS:执行成功TaskStatus.FAILED:执行失败,需设置error字段说明原因
Rollout 函数开发
Rollout 函数负责生成 Agent 与 LLM、外部工具的交互轨迹。
基本结构
继承 AbstractRolloutProcessor,实现 setup() 和 process() 方法:
process() 支持 async def 和普通 def 两种写法,SDK 会自动处理。
base_url)和 API Key 由训练框架运行时自动注入到 RolloutInput.model_resource,您在 setup() / process() 中直接读取即可,无需在代码或配置中硬编码。RolloutInput 关键字段
字段 | 类型 | 说明 |
|---|---|---|
| list | 对话消息列表 |
| object | 模型信息(model_name, base_url, api_key) |
| object | 采样参数(temperature, max_tokens, max_turns, timeout) |
| str | 参考答案 |
| dict | 训练数据中的 rollout_extra 字段,透传业务数据 |
TaskStatus.FAILED 并设置 error 字段。详见上方 TaskStatus 与错误处理。
process() 中。在返回 RolloutOutput 时,通过 AgentOutput(reward_score=0.95) 直接给出评分即可。内嵌评分的指标同样会出现在控制台的指标页面中。详见 AgentOutput。Reward 函数开发
Reward 函数对单条 Agent 输出进行评分,是 RL 训练的核心。
基本结构
继承 AbstractRewardProcessor,在 process() 中计算分数:
RewardInput 关键字段
字段 | 类型 | 说明 |
|---|---|---|
| object | Agent 输出。字段详见上方 AgentOutput |
| str | 参考答案 |
装饰器模式(多维度评分)
对于需要从多个维度评分的场景,可使用装饰器模式将评分拆分为多个子维度,再聚合:
装饰器 | 作用 |
|---|---|
| 标记 Reward 处理器类,定义名称 |
| 定义子评分维度和权重 |
| 定义聚合逻辑,合并所有子维度得分 |
多 Reward 函数组合
提交任务时可以配置多个 Reward 函数,每个函数有独立的权重:
reward_metric_weight 进一步细分子指标的权重:
Group Reward 函数开发
Group Reward 对同一问题的多条 Agent 输出进行集体评分,适用于排序或对比评分场景。
基本结构
继承 AbstractGroupRewardProcessor,在 process() 中处理多条输出:
process() 方法支持 async def 和普通 def 两种写法,SDK 会自动处理。这一规则适用于所有函数组件(Rollout、Reward、Group Reward)。下面的示例采用同步写法。
与 Reward 的区别
- Reward:对单条输出评分,输入是 agent_output(单数)
- Group Reward:对多条输出评分,输入是 agent_outputs(复数列表),返回一组分数
Reward 设计哲学:先想清楚再写代码
Reward 是 RL 训练唯一的优化方向,先把"评什么"想清楚再动键盘。三组核心权衡决定写法:
- 稀疏 vs 稠密:稀疏 Reward 不易作弊但样本利用率低;稠密 Reward 学得快但容易引入 shaping 偏差
- 规则 vs LLM-as-judge:规则廉价、确定、可复现;LLM 判分覆盖语义但有偏差和成本
- 单维 vs 多维:单维易迭代但天花板低;多维表达力强但权重设计与解释成本高
从任务反推 Reward:先答 4 个问题
- Q1 有可机器判定的 ground truth?(数学/代码/JSON → 规则;写作/对话 → 模型/人类)
- Q2 失败是二元还是有梯度?(二元 → 稀疏 0/1;有梯度 → 拆维度)
- Q3 是否需要中间步骤激励?(长链推理、Agent 多轮 → process reward / 步骤分)
- Q4 哪些 hack 路径成本最低?(先列再防——见下文 §Reward Hacking 识别与防御)
Reward 是合同,不是损失函数
模型只会优化你写下来的东西,不会优化你"想表达"的东西。三点:
- 0/1 边界会被精确逼近——给精确分加 margin 或软化(如准确分 + 格式分 + 简洁分多维组合)
- 硬上限会被堆到顶点——长度满分 = 模型必把回答撑到上限;用归一化或衰减替代
- 空输出 / 超长输出 / 超时必须显式定义——默认行为往往是漏洞,空输出强制 0 分、超长截断后扣分
三种 Reward 表达方式选型
Reward 表达方式不是互斥,而是分层选最轻:A 内嵌(AgentOutput(reward_score=...))→ B 独立(RewardFunctionComponent)→ C 装饰器多维(@reward_func + @sub_reward_func + @aggregate_func)。
表达方式 | 适用场景 | 评分逻辑复杂度 | 资源开销 | 可观测粒度 | 调试便利 | 何时不要用 |
|---|---|---|---|---|---|---|
A 内嵌 Rollout | 评分与生成同步、判定逻辑 ≤10 行 | 低 | 复用 Rollout FC | 仅 reward_score 一个指标 | 难(需 redeploy Rollout) | 评分需独立扩缩容时 |
B 独立 Reward | 单一明确判分规则、复用通用评分器 | 中 | 独立 FC | reward + reward_metrics | 中(独立部署) | 维度 ≥3 个时 |
C 装饰器多维 | 多维度评分、各维度权重需独立调 | 高 | 独立 FC | 子维度独立指标 | 高(按维度排查) | 维度间强耦合无法拆 |
多维评分:三层权重设计
多维 Reward 的权重分三层,互不覆盖、独立调整:
- 层 1 函数级
RewardFunctionComponent.weight(多 Reward 函数之间) - 层 2 子维度
@sub_reward_func(sub_weight=...)(函数内部维度之间) - 层 3 子指标
reward_metric_weight={...}(同一 reward_metrics 内细分)
多维权重设计模板
步骤 | 操作 | 输出 | 经验值 |
|---|---|---|---|
1. 列维度 | 拆 3-5 个原子可判定维度 | 维度清单 | 建议 ≤5 个 |
2. 定优先级 | 业务排序(必须满足 / 加分项 / 风格项) | 排序后的列表 | - |
3. 初始权重 | 必须项占 0.5-0.7,加分项 0.2-0.3,风格 0.05-0.1 | 权重向量 | 总和 = 1 |
4. 量纲对齐 | 每维度先归一到 [0,1] 再加权 | 归一函数 | 避免某维度天然占主导 |
5. 灰度验证 | 100 条样本人工标注与 reward 对照 | 相关系数 | Spearman ρ ≥ 0.6 |
权重调参方法
- 先训"均匀权重"baseline 看哪个 sub_metric 主导;看
trace/reward_metrics/{reward}/{sub}/avg曲线方差,方差小的维度提权 - validation reward 涨但训练集某维度急降 → 该维度权重过低被牺牲,需补;出现 hack 信号 → 临时降低相关维度权重而非直接禁用
Group Reward 应用场景
Group Reward vs 独立 Reward 的选择
- 独立 Reward 评"这一条回答好不好"(绝对分);Group Reward 评"同 prompt N 条相对好坏"(排序/对比)
- GRPO/GSPO 天然需组内相对优势,Group Reward 直接产出排序信号省去归一化偏差
- 评分天然主观(写作、对话)时,相对比较比绝对打分更稳定
与 GRPO/GSPO 算法的配合
- GRPO/GSPO 用同 prompt N 个 rollout 计算 group-relative advantage;独立 Reward 出绝对分 → 框架组内归一化(可能受 outlier 拉偏)
- Group Reward 直接出组内分布 → 减少分数尺度漂移;
n_rollouts影响输入规模,建议 4-16
实现注意点
- 输入
agent_outputs列表 → 输出rewards长度严格相等;不要在组内硬归一化(除以 max)会与算法层冲突 - 允许并列分数(三条都 1.0)但避免全部相同(zero advantage);失败单条建议给 0 分保持长度一致,框架按 TaskStatus 判定
Reward Hacking 识别与防御
Hack 是几乎必然发生的
- Goodhart 定律:当度量变成目标,它就不再是好度量
- RL 越收敛、奖励越高,hack 概率越大(不是 bug,是优化器在干正事)
- 防御策略不是"消除 hack",而是"让 hack 比真解更难"
Hack 模式 → 检测信号 → 防御策略
Hack 模式 | 检测信号(指标 + 现象) | 防御策略 |
|---|---|---|
长度爆炸 / 啰嗦堆砌 |
| 长度归一化、加 |
关键词堆砌 | reward 涨但人工抽查发现答案重复关键词; | 正则去重、用 LLM judge 替代关键词匹配、引入语义相似度阈值 |
格式 hack(markdown / emoji 堆砌) | 输出格式异常 + 用户问题与回答相关性下降 | 格式归一化预处理、风格维度独立打分但权重低 |
拒答 / 空输出 | reward 中位数高但 | 空输出强制 0 分、最小长度约束、引入"是否回答了问题"维度 |
循环输出 / 复述 | response_length 高 + n-gram 重复率高 + entropy 急降 | 重复检测正则、 |
Judge 串通(LLM-as-judge 场景) | judge 分高但与规则分 / 人工分背离 | 规则分 + judge 分混合(见下文)、定期 judge 校准、换不同模型族 judge |
工具滥用 / 工具不调用 |
| 工具调用次数惩罚 / 奖励、必需工具调用约束 |
Entropy 崩塌 |
| 调大 KL 系数、Early stop、entropy bonus |
4 类防御手段
- Reward shaping:把 hack 路径显式扣分(直接但要持续打补丁)
- 正则约束:长度/重复/格式硬约束(廉价但僵硬)
- Validation 监控:用与训练 reward 不同源的指标做哨兵(必须)
- 多 Reward 组合:规则 + 模型混合互相牵制
监控操作手册
- 北极星
critic/rewards/mean上升趋势 + 反作弊哨兵validation/data/reward/mean@1(背离即 hack)+ 行为体检trajectory/response_length/actor/entropy - 每隔 N 步抽查轨迹(控制台"轨迹"页签)人工对照分数
评分稳定性:让分数可信
LLM-as-judge 的偏差控制
- 位置偏差:pairwise 先后顺序影响 → 双向各跑一次取一致项
- 自偏好偏差:judge 偏向同模型族 → 用不同模型族(Qwen 训 → GPT judge)
- 长度偏差:judge 偏好长输出 → prompt 明确"不因长短给分" + 长度归一化
- 评分尺度 1-5 / 1-7 离散 > 1-100 连续,带 anchor example 校准
规则 + 模型混合评分
- 规则做硬约束(格式/长度/必含字段/敏感词)→ 不通过 0 分短路
- 模型做软评价(流畅度/相关性/推理质量)
- 组合:
final = rule_pass × (w_rule × rule_score + w_model × model_score);规则失败不传给 model judge → 省钱 + 防 hack
评分场景的 TaskStatus 判定
- 评分得 0 分(规则未通过、答错)→
SUCCESS + reward_score=0,是有效训练信号 - 临时性错误(judge 超时、网络抖动)→
FAILED让框架重试,不写成 0 分 - 滥用 FAILED 会让数据分布偏移(难样本被框架重试若干次后丢弃 → 训练集越变越简单)
评分一致性自检
- 准备 50-200 条 gold set(人工标注);每次改 Reward 后跑 gold set 比 Spearman ρ / Cohen κ
- judge 模型升级 / 切换前后对比避免静默漂移;周期性把训练中分数最高/最低 N 条样本送人工 review
Reward 函数撰写前自检(10 问)
写完 Reward 函数发任务之前过一遍:
- 评分逻辑能在 1 句话内说清楚吗?
- 有 ground truth 吗?是机器可判定吗?
- 最低成本的 Hack 路径是什么?已经防了吗?
- 多维度的话每个维度都能独立人工标注吗?
- 权重总和归一了吗?量纲对齐了吗?
- 边界条件(空输出、超长、超时)都定义了吗?
- validation 用了与训练独立的判定方式吗?
- 评分耗时是否 < Rollout 耗时的 1/3?
reward_metrics字段名是否稳定(改名会断指标曲线)?- 是否有 gold set 用于回归?
Reward 设计反模式
反模式 | 后果 |
|---|---|
reward 写成"越长越好" | 必 Hack(长度爆炸) |
单维度评分但维度本身高度主观 | 噪声压过信号 |
所有维度 weight 都设 1.0 | 等价于没设权重 + 量纲混乱 |
训练 judge 与 validation judge 同 prompt 同模型 | 自欺欺人 |
临时网络错误返回 SUCCESS + 0 分 | 数据分布污染(难样本被当成"答错") |
可观测性配置
函数测试
注册函数
将函数代码上传并注册为云端函数组件:
远程测试
使用 test_functions 向已注册的函数实例发送测试数据:
测试输入格式
Rollout 测试输入(rollout_input.json):
本地测试
项目中的 scripts/ 目录提供了本地测试脚本:
后续步骤
- 了解提交方式与配置 → 强化学习训练配置 — 提交与配置
- 训练指标参考字典 → 训练指标参考
- 回顾概述 → 强化学习
- 了解可观测性配置与轨迹查看 → 强化学习的可观测配置