OpenEnv 集成:连接外部环境到 RL 训练
Twinkle 的 envs 模块在异步外部环境(代码沙箱、浏览器、游戏引擎)和同步 RL 训练循环之间架起桥梁。本文介绍 Env 抽象、EnvTool 适配器以及 OpenEnv WebSocket 客户端的设计。
问题背景
使用工具调用的 LLM 进行 RL 训练需要交互式环境:模型生成工具调用,环境执行并返回观测,模型生成下一个动作。但是:
- 外部环境通过 WebSocket(异步)通信
- 训练循环在 torch distributed 中同步运行
- 环境可能定义不同的工具 schema,LLM 需要理解
- 奖励可能是稀疏的(仅在 episode 结束时)或逐步的
Twinkle 的 envs 模块通过三层抽象解决所有这些问题。
第一层:Env 基类
from twinkle_agentic.envs.base import Env, StepResult
@dataclass
class StepResult:
observation: str = ''
reward: float = 0.0
done: bool = False
info: Dict[str, Any] = field(default_factory=dict)
Env 定义了标准接口,支持两种使用模式:
交互模式(多轮 rollout):
env.reset(trajectory)
result = env.step(tool_name, arguments)
# ... 重复直到 result.done
批量评估模式:
rewards = env.evaluate(trajectories)
tools() 方法返回 OpenAI function-call schema,让 LLM 知道有哪些可用动作。
第二层:EnvTool 适配器
EnvTool 将任何 Env 封装为标准 Tool,可注册到 Twinkle 的 ToolManager:
from twinkle_agentic.envs.env_tool import EnvTool
# 封装环境 — 为 env.tools() 的每个条目创建一个 tool
tools = EnvTool.from_env(my_env)
for tool in tools:
tool_manager.register(tool)
当 LLM 生成工具调用时,EnvTool.__call__ 分发到 env.step() 并返回观测字符串。调用方可检查:
tool.done— episode 是否已结束tool.episode_reward— 从info['episode_reward']获取的累积奖励
这种设计将环境实现与 rollout 引擎解耦——任何 Env 无需修改即可接入现有的 MultiTurnRollout。
第三层:OpenEnv WebSocket 客户端
OpenEnv 是面向远程服务环境的具体适配器:
from twinkle_agentic.envs.openenv import OpenEnv
env = OpenEnv(
base_url='http://localhost:8000',
env_cls='coding_env.CodingEnv', # 或 None 使用 GenericEnvClient
env_kwargs={'message_timeout_s': 30},
tool_schema=[...], # 可选的工具定义
action_mapper=my_mapper, # 可选的动作转换
)
懒初始化
WebSocket 客户端在首次 reset() 或 step() 调用时懒创建:
def _ensure_client(self):
if self._sync_client is not None:
return
client = self._env_cls(base_url=self._base_url, **self._env_kwargs)
self._sync_client = client.sync() # async -> sync 封装
self._sync_client.__enter__()
这意味着可以在配置阶段创建 OpenEnv 实例而不建立连接——在环境尚未就绪时非常有用。
动作映射
默认情况下,动作以 {'tool_name': ..., 'arguments': ...} 格式发送。可选的 action_mapper 将 LLM 工具调用转换为环境特定格式:
def code_action_mapper(tool_name, arguments):
if tool_name == 'execute_code':
return {'code': arguments['code'], 'language': 'python'}
return {'tool_name': tool_name, 'arguments': arguments}
env = OpenEnv(base_url=url, action_mapper=code_action_mapper)
观测提取
OpenEnv._format_observation() 处理多种观测格式:
- 字符串 — 直接返回
- 字典 — 尝试常用键(
result、output、content、text、message),回退到 JSON 序列化 - 类型化对象 — 尝试常用属性,然后 JSON
Episode 奖励追踪
奖励按 episode 累积:
self._episode_reward += reward
return StepResult(
observation=obs,
reward=reward,
done=done,
info={'raw_result': result, 'episode_reward': self._episode_reward},
)
这同时支持逐步奖励信号和 episode 结束时的累积奖励。
完整使用示例
典型的多轮 RL 训练配置:
from twinkle_agentic.envs.openenv import OpenEnv
from twinkle_agentic.envs.env_tool import EnvTool
# 1. 创建环境
env = OpenEnv(
base_url='http://sandbox:8000',
tool_schema=[
{'type': 'function', 'function': {
'name': 'execute_code',
'description': '在沙箱中运行 Python 代码',
'parameters': {'type': 'object', 'properties': {
'code': {'type': 'string'}
}}
}}
],
)
# 2. 封装为工具
tools = EnvTool.from_env(env)
# 3. 注册到 ToolManager
for tool in tools:
tool_manager.register(tool)
# 4. 在多轮 rollout 中使用
env.reset()
while True:
action = model.generate(observation) # LLM 生成工具调用
result = env.step(action.tool_name, action.arguments)
if result.done:
break
# 5. 清理
env.close()
支持的环境类型
env_cls 参数支持:
None— 使用GenericEnvClient(适用于任何基于 dict 的环境)'module.ClassName'— 动态导入类型化客户端类- 类对象 — 直接使用
动态导入系统包含对损坏子导入的回退逻辑,对部分安装的 OpenEnv 具有鲁棒性。
核心设计原则
- 同步接口 — RL 训练循环无需管理 async/await
- 懒连接 — 配置时创建环境,运行时建立连接
- Schema 透明 — LLM 看到标准 OpenAI function-call 格式
- 奖励灵活性 — 支持逐步、稀疏或自定义聚合
- 零耦合 —
Env实现不需要了解 Twinkle 的训练基础设施