TRL documentation

用于在环境中训练 LLM 的 OpenReward 集成

You are viewing main version, which requires installation from source. If you'd like regular pip install, checkout the latest stable version (v1.18.0).
Hugging Face's logo
Join the Hugging Face community

and get access to the augmented documentation experience

to get started

用于在环境中训练 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 会在首次访问时延迟执行以下三项操作:

  1. spec.train_dataset:从环境的任务列表派生出一个 datasets.Dataset(通过 SDK 进行一次 HTTP 往返)。至少包含 prompt、task_index,以及合并进来的每个任务的元数据列。
  2. spec.environment_factory:返回一个无参可调用对象,每次调用都会生成一个新的按 rollout 划分的适配器。该适配器为每个 ORS 工具暴露一个 Python 方法,其类型化签名和文档字符串会根据 env 的 JSON Schema 自动生成。TRL 的工具收集器通过 inspect.getmembers 来拾取这些方法。
  3. 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")

openreward SDK 默认期望使用双子域名的平台布局(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)))      # range

num_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

< >

( 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——目前尚未接入。

参考

Update on GitHub