TRL documentation
用于在环境中训练 LLM 的 OpenReward 集成
用于在环境中训练 LLM 的 OpenReward 集成
OpenReward 是一个基于 Open Reward Standard (ORS) 构建的 RL 环境开放生态系统——ORS 是一种公开的、语言无关的 HTTP/SSE 协议,用于定义环境如何暴露其任务、工具、会话和奖励。由于 ORS 仅是一种协议,同一环境既可以在 OpenReward 平台 上运行,也可以自托管在任何容器服务上,或者在本地 localhost 上运行以用于开发。在 openreward.ai 上可以获取即用型环境的目录。
本指南介绍了如何将 OpenReward 与 TRL 集成。有关该标准本身的更多信息,请参阅ORS 文档。
该集成位于
trl.experimental.openreward,并通过trl[openreward]附加组件进行控制(采用延迟导入——非用户无需承担任何额外开销)。
何时使用 OpenReward 环境
GRPOTrainer 通过 environment_factory 插槽支持基于环境的训练——通用契约请参阅 OpenEnv。如果您希望针对支持 ORS 的环境进行训练,请使用 OpenReward:OpenReward 目录(例如 Eigent/SETA、kanishk/EndlessTerminals、nebius/SWE-rebench-V2)、您自行托管在自有基础设施上的环境,或您正在开发的本地服务器。
安装
pip install trl[openreward]
这将安装 openreward Python SDK。集成本身会延迟导入 openreward,因此未使用 trl.experimental.openreward 的用户不会受到影响。
快速入门
OpenRewardSpec 类通过暴露与 train_dataset、environment_factory、reward_funcs 这三个 TRL 训练器参数名一一对应的属性,将单个 ORS 环境接入这三个插槽:
from trl import GRPOConfig, GRPOTrainer
from trl.experimental.openreward import OpenRewardSpec
spec = OpenRewardSpec("Eigent/SETA", num_tasks=64)
trainer = GRPOTrainer(
model="Qwen/Qwen3-4B",
args=GRPOConfig(
num_generations=2,
max_steps=5,
max_tool_calling_iterations=20,
log_completions=True,
),
train_dataset=spec.train_dataset,
environment_factory=spec.environment_factory,
reward_funcs=spec.reward_funcs,
)
trainer.train()在底层,OpenRewardSpec 会在首次访问时延迟执行以下三项操作:
spec.train_dataset:从环境的任务列表派生出一个datasets.Dataset(通过 SDK 进行一次 HTTP 往返)。至少包含prompt、task_index,以及合并进来的每个任务的元数据列。spec.environment_factory:返回一个无参可调用对象,每次调用都会生成一个新的按 rollout 划分的适配器。该适配器为每个 ORS 工具暴露一个 Python 方法,其类型化签名和文档字符串会根据 env 的 JSON Schema 自动生成。TRL 的工具收集器通过inspect.getmembers来拾取这些方法。spec.reward_funcs:一个仅基于结果的奖励函数(轨迹中最后一个非空奖励),适用于 SETA 等稀疏奖励环境。
使用 Hub 环境
将 openreward.ai 目录名称作为目标传入。SDK 会从环境变量中读取 OPENREWARD_API_KEY 用于身份验证。
spec = OpenRewardSpec("Eigent/SETA", num_tasks=64)使用自托管环境
直接传入 URL。如果您的服务器未强制要求,则无需 API 密钥。
spec = OpenRewardSpec("https://huggingface.co/proxy/my-org-my-env.hf.space", env_name="my_env")
openrewardSDK 默认期望使用双子域名的平台布局(api.<host>用于无状态调用,sessions.<host>用于基于 SSE 的会话调用)。对于单主机自托管服务器(一个 URL 提供所有服务),请在构建OpenRewardSpec之前设置以下覆盖环境变量:import os URL = "https://huggingface.co/proxy/my-org-my-env.hf.space" os.environ["OPENREWARD_API_URL"] = URL os.environ["OPENREWARD_SESSION_URL"] = URL spec = OpenRewardSpec(URL, env_name="my_env")
在本地运行最小化环境
在没有外部依赖的情况下,端到端试用集成的最快方式是使用 openreward SDK 的 Environment + Server 脚手架定义一个小型 ORS 服务器。下面的示例是一个完整的 echo 环境——模型只需调用 echo(text=...) 并传入任务的目标字符串即可获胜。
# server.py
from pydantic import BaseModel
from openreward.environments import Environment, JSONObject, Server, TextBlock, ToolOutput, tool
class EchoTaskSpec(BaseModel):
target: str
class EchoParams(BaseModel):
text: str
class EchoEnvironment(Environment):
def __init__(self, task_spec: JSONObject = {}, secrets: dict[str, str] = {}):
super().__init__(task_spec)
self.config = EchoTaskSpec.model_validate(task_spec)
@classmethod
def list_splits(cls) -> list[str]:
return ["train"]
@classmethod
def list_tasks(cls, split: str) -> list[JSONObject]:
return [{"target": "hello"}, {"target": "world"}]
def get_prompt(self) -> list[TextBlock]:
return [TextBlock(type="text", text=f"Echo '{self.config.target}' to win.")]
@tool
async def echo(self, params: EchoParams) -> ToolOutput:
"""Submit a string. Reward 1.0 + finished if it matches the target.
Args:
text: The string to echo back.
"""
correct = params.text == self.config.target
return ToolOutput(
blocks=[TextBlock(type="text", text="match" if correct else "no match")],
reward=1.0 if correct else 0.0,
finished=correct,
)
if __name__ == "__main__":
Server([EchoEnvironment]).run(host="0.0.0.0", port=8000)运行它:
pip install openreward fastapi uvicorn pydantic
python server.py # listens on :8000然后让 OpenRewardSpec 指向它(使用上述 URL 覆盖):
import os
URL = "http://127.0.0.1:8000"
os.environ["OPENREWARD_API_URL"] = URL
os.environ["OPENREWARD_SESSION_URL"] = URL
from trl.experimental.openreward import OpenRewardSpec
spec = OpenRewardSpec(URL, env_name="echoenvironment")
print(spec.train_dataset) # 2 rows, task_index + target columns这也是 TRL 自身测试所使用的 fixture 模式——有关已部署的 Space,请参阅 trl-internal-testing/openreward-echo-env。
选择任务
OpenRewardSpec 接受一个计数或一个显式的索引列表:
spec = OpenRewardSpec("Eigent/SETA", num_tasks=10) # first 10 tasks
spec = OpenRewardSpec("Eigent/SETA", indices=[0, 5, 13, 27]) # specific indices
spec = OpenRewardSpec("Eigent/SETA", indices=list(range(50, 100))) # rangenum_tasks 和 indices 互斥,且两者都只获取所需任务(无需扫描完整任务列表)。
工具绑定的工作原理
在构建时,spec 会调用 env 的 /tools 端点以获取工具规范列表(每个规范包含名称、描述以及用于参数的 JSON Schema)。对于每个工具,它会在每个 rollout 的适配器上生成一个具有类型化签名和从 schema 派生的文档字符串的 Python 方法。这些方法被设置在适配器的类上,以便 TRL 的工具收集器能够找到它们,因此 transformers.utils.get_json_schema 和 TRL 的 inspect.getmembers(type(env), isfunction) 都能为模型生成正确的工具 schema,而无需针对每个 env 编写包装代码。
如果工具描述中包含无法安全拼接到 Python 源代码中的字符,binder 会回退到经过清理的形式,以确保在真实环境中绑定不会失败。
奖励函数
spec.reward_funcs 的默认值为仅结果奖励——对于每次 rollout,它返回轨迹中观察到的最后一个非空奖励。这是稀疏奖励环境(例如 SETA,其中只有 submit_solution 返回非空奖励)的合适默认值。
如果没有任何工具返回非空奖励,默认奖励函数将返回 None,而不是 0.0。这包括没有工具调用、仅包含未评分工具调用或仅包含失败工具调用的 rollout。观察到的 0.0 或负奖励仍然是有效的分数。后续未评分或失败的工具调用不会抹去之前的分数。
GRPO 会将 所有 奖励函数均返回 None 的 rollout 从奖励基线中排除,并为其赋予零优势值。这并不会将其 token 从损失归一化中移除,也不会禁用 KL 正则化等其他项。如果另一个奖励函数对该 rollout 进行了评分,它仍可能获得非零优势值。
缺失的分数无法说明回合结束的原因。如果放弃或达到工具调用上限应被视为任务失败,请从您的环境或自定义奖励函数中返回明确的失败奖励。基础设施或评分器故障可以保持为无法评分;请同时监控评分覆盖率和平均奖励。
如果您需要自定义奖励,请编写一个常规的 TRL 奖励函数并直接传入:
def my_reward(environments, **kwargs) -> list[float | None]:
rewards = spec.reward_funcs(environments=environments)
return [2.0 * reward if reward is not None else None for reward in rewards]
trainer = GRPOTrainer(
...,
reward_funcs=my_reward,
)每次 rollout 的适配器会公开 TRL 所需的运行状态——env.reward、env.rewards、env.metadata、env.finished、env.last_output——以便进行任意的事后奖励整形。
OpenRewardSpec
class trl.experimental.openreward.OpenRewardSpec
< source >( target: strnum_tasks: int | None = Nonesplit: str = 'train'indices: list[int] | None = Noneapi_key: str | None = Nonesecrets: dict[str, str] | None = Noneenv_name: str | None = Noneinclude_metadata: bool = Truediscover_task_tools: bool = Truetask_tools_discovery_index: int | None = None )
参数
- target (str) — openreward.ai 目录名称(“Eigent/SETA”)或指向任意 ORS 服务器的 URL (”https://huggingface.co/proxy/you-seta.hf.space”、“http://localhost:8080”)。通过字符串中是否存在 :// 自动检测。
- num_tasks (int, 可选) —
限制拉取到数据集中的任务数量。
None表示使用环境暴露的所有任务。 - split (str,可选,默认为 “train”) — 从中提取任务列表的数据集划分。
- indices (list[int], 可选) —
要训练的具体任务索引。与
num_tasks互斥。适用于调试或课程子集。 - api_key (str, 可选) —
OPENREWARD_API_KEY的覆盖值。仅在target为目录名称时使用。 - secrets (dict[str, str],可选) —
转发到
env.session(secrets=)的每个会话的密钥。 - env_name (str, 可选) — 用于覆盖在服务器上查找的环境名称。很少需要。
- include_metadata (bool,可选,默认为 True) —
将每个任务的元数据(difficulty、category、tags、…)合并到数据集行中,以便奖励函数通过 TRL 的
inputs参数读取它们。 - discover_task_tools (bool,可选,默认为 True) —
如果为
True,将开启一个短生命周期的 ORS 会话并使用session.list_tools(),以便将任务特定的工具(根据 ORS,GET …/task_tools— 例如@tool(shared=False)和list_task_tools())绑定到 GRPO。 如果探测失败,则仅回退到environment.list_tools()。设置为False可跳过额外的会话(仅共享工具 / 离线特殊情况)。 - task_tools_discovery_index (int, 可选) —
仅在设置时用于发现会话的任务索引;覆盖下方的多索引探测。当省略且设置了
indices=时,发现会为indices中的每个 不同 条目(按排序)打开一个探测会话,并按名称 合并 工具规范,从而绑定所有列出任务的特定工具。当省略且使用num_tasks/ 完整列表模式时,仅探测任务0。当discover_task_tools=False时忽略。
单个规范对象,用于将 ORS 环境接入 TRL 训练器。
局限性
- 该集成位于
trl.experimental中 — API 可能会发生变化。设置TRL_EXPERIMENTAL_SILENCE=1可在 CI 日志中静默该警告。 - 目前仅暴露一个涵盖单个环境的
OpenRewardSpec;尚不支持多环境训练(类似于 OpenEnv 的“元环境”模式)。 - 长时间运行的 rollout(每集 >15 分钟)需要 keepalive ping——目前尚未接入。