TRL documentation
Integración de OpenEnv para entrenar LLMs con entornos
Integración de OpenEnv para entrenar LLMs con entornos
OpenEnv es un marco de código abierto para definir, desplegar e interactuar con entornos en el aprendizaje por refuerzo (RL) y en flujos de trabajo agénticos. Proporciona APIs estandarizadas para la interacción con entornos y permite ejecutarlos como servidores de backend (mediante WebSocket o ejecución en contenedores). Puedes encontrar una colección de entornos de OpenEnv listos para usar en el Hugging Face Hub.
Esta guía cubre cómo integrar OpenEnv con TRL. Para más información sobre OpenEnv en sí, consulte la documentación de OpenEnv.
Ejemplos de OpenEnv listos para usar:
grpo_echo(mínimo),grpo_catch,grpo_wordle,grpo_sudoku,grpo_multi_env,grpo_browsergym,grpo_carlayasync_grpo_harbor(local y HF Jobs).
Cuándo usar entornos
GRPOTrainer se puede utilizar para entrenar agentes. Para tareas agénticas, admite dos modos: tools, donde el modelo puede llamar a funciones externas, pero cada llamada es sin estado e independiente, y environments, que mantienen el estado entre turnos, lo que permite una interacción genuina de varios turnos en la que las acciones del agente determinan las observaciones futuras. Utilice entornos cuando la continuidad sea importante, por ejemplo, al navegar por un juego, al explorar una página web o en cualquier tarea donde lo que el agente observa a continuación dependa de lo que hizo antes.
Elegir una integración de entorno
OpenEnv es la ruta nativa documentada aquí. Dos integraciones adicionales, OpenReward y Harbor, cumplen con el mismo contrato environment_factory y son intercambiables a nivel de TRL. Consulta la comparación de integraciones de entornos en la guía de GRPO para elegir la cuyo ecosistema se ajuste a tu tarea.
Instalación
Los entornos de OpenEnv se alojan como Hugging Face Spaces, que también son repositorios Git instalables con pip:
# Echo environment
pip install "openenv-echo-env @ git+https://huggingface.co/spaces/openenv/echo_env"
# Wordle (TextArena) environment
pip install "openenv-textarena @ git+https://huggingface.co/spaces/openenv/wordle"
# Catch (OpenSpiel) environment
pip install "openenv-openspiel-env @ git+https://huggingface.co/spaces/openenv/openspiel_env"Esto instala el cliente de entorno (p. ej., EchoEnv) que se comunica con el servidor de entorno remoto a través de WebSocket, junto con los modelos de acción/observación y todas las dependencias necesarias (incluido openenv).
Puedes encontrar el comando de instalación para cualquier entorno en su página de HF Space. Haz clic en el menú ⋮ (tres puntos) y selecciona “Use this Space” para ver las instrucciones de instalación.
También puedes instalar el paquete principal desde PyPI con
pip install "openenv>=0.3.1", pero ten en cuenta que las dependencias específicas del entorno pueden necesitar instalarse por separado.
Para el desarrollo, puedes clonar el repositorio de OpenEnv e instalarlo localmente:
git clone https://github.com/openenv/OpenEnv.git
cd OpenEnv/envs/echo_env
pip install -e .Cada script de entorno en TRL incluye metadatos de dependencias en línea (PEP 723), por lo que también puedes ejecutarlos directamente con uv:
uv run examples/grpo_echo/grpo_echo.pyEsto instala automáticamente el paquete de entorno requerido en un entorno virtual aislado.
Inicio rápido
La forma más rápida de entender la integración es un ejemplo completo. El script echo.py entrena un modelo con el Echo environment, que otorga recompensas a las finalizaciones en función de su longitud de texto:
from datasets import Dataset
from echo_env import EchoEnv
from echo_env.models import EchoAction
from trl import GRPOConfig, GRPOTrainer
ENV_URL = "https://huggingface.co/proxy/openenv-echo-env.hf.space"
class EchoToolEnv:
def __init__(self):
self.env = EchoEnv(base_url=ENV_URL)
self.reward = 0.0
def reset(self, **kwargs) -> str | None:
self.reward = 0.0
return None
def echo(self, message: str) -> str:
"""
Echo the message back from the environment.
Args:
message: The message to echo
Returns:
The echoed message.
"""
observation = self.env.step(EchoAction(message=message))
self.reward = observation.observation.reward
return observation.observation.echoed_message
def reward_func(environments, **kwargs):
return [env.reward for env in environments]
dataset = Dataset.from_dict(
{"prompt": [[{"role": "user", "content": "Try to echo 'Hello World!' in the environment."}]] * 64}
)
trainer = GRPOTrainer(
model="Qwen/Qwen3-0.6B",
train_dataset=dataset,
reward_funcs=reward_func,
args=GRPOConfig(
chat_template_kwargs={"enable_thinking": False},
log_completions=True,
),
environment_factory=EchoToolEnv,
)
trainer.train()Eso es todo. Esto es lo que ocurre por debajo:
environment_factory=EchoToolEnv: El entrenador crea una instancia deEchoToolEnvpor generación (pasa la clase, no una instancia).reset()se llama al inicio de cada episodio para inicializar el estado. Devuelve una cadena de observación (oNone).- Detección de herramientas: El entrenador detecta todos los métodos públicos de la instancia del entorno (en este caso,
echo()) y los expone como herramientas de llamada a funciones. Cada método debe tener una docstring adecuada con argumentos tipados, que el entrenador utiliza para construir el esquema de la herramienta. - Bucle de varios turnos: El entrenador genera una respuesta, analiza las llamadas a herramientas, ejecuta
echo(), añade el resultado y vuelve a generar, hasta que el modelo deja de llamar a herramientas o se alcanzamax_completion_length. - Función de recompensa: Lee
env.rewardde cada instancia de entorno después del episodio (antes de que el entorno se restablezca).
# Run the example
python examples/grpo_echo/grpo_echo.py
# Customize model and environment URL
python examples/grpo_echo/grpo_echo.py --model Qwen/Qwen3-0.6B --env-host https://huggingface.co/proxy/openenv-echo-env.hf.spaceA continuación se muestra la curva de recompensa del entrenamiento:
Puedes explorar más ejemplos de OpenEnv listos para usar en la nota en la parte superior de esta página.
Cómo funciona environment_factory
El GRPOTrainer de TRL admite el entrenamiento en entornos interactivos a través del argumento environment_factory. Cuando se proporciona, el entrenador gestiona automáticamente el bucle de llamadas a herramientas de varios turnos: genera completados, analiza las llamadas a herramientas, las ejecuta contra el entorno y devuelve los resultados al modelo. Todo esto sin código de rollout personalizado.
Requisitos de la clase Environment
Su clase de entorno debe seguir estas reglas:
__init__(self)(opcional): Si se proporciona, no debe aceptar argumentos. Úsalo para inicializar el estado o los clientes. Si necesitas configuración externa (p. ej., una URL), captúrala desde el ámbito envolvente o desde variables a nivel de módulo.reset(self, **kwargs): Se llama al inicio de cada episodio. Recibe todas las columnas del conjunto de datos como argumentos de palabra clave. Devuelve una observación en formato de cadena (oNonesi no hay observación inicial).- Métodos de herramienta: cualquier método público (que no comience por
_) distinto deresetse expone automáticamente como herramienta. Cada método de herramienta debe tener una docstring con descripciones deArgs:, ya que el trainer utiliza estas para generar el esquema de la herramienta para el modelo.
Consejos para las clases de entorno
- Estado para la recompensa: Puede almacenar cualquier estado que desee en la instancia del entorno (p. ej.,
self.reward,self.done, etc.) y acceder a él en su función de recompensa a través del parámetroenvironments. Consulte la guía de inicio rápido para ver un ejemplo de este patrón. - Gestión de errores: si un método de herramienta lanza una excepción (p. ej.,
ValueError("Game over.")), el entrenador la captura y devuelve el mensaje de error al modelo como respuesta de la herramienta. Esta es la forma recomendada de indicar que una acción no es válida o que el episodio ha terminado.
ENV_URL = "https://huggingface.co/proxy/my-env.hf.space"
class MyEnv:
def __init__(self):
self.client = MyClient(base_url=ENV_URL) # captured from enclosing scope
self.reward = 0.0
def reset(self, **kwargs) -> str | None:
self.reward = 0.0
return "Initial observation for the model"
def my_tool(self, arg1: str, arg2: int) -> str:
"""
Description of what this tool does.
Args:
arg1: Description of arg1
arg2: Description of arg2
Returns:
The result message.
"""
self.reward = 1.0
return "Tool result"Las herramientas deben ser métodos individuales con nombres descriptivos y argumentos tipados (p. ej.,
guess(word: str),move(direction: str)). No recomendamos el uso de métodos genéricos comostep(action), ya que el modelo necesita nombres de herramientas y descripciones de argumentos significativos para aprender a llamar herramientas.
Funciones de recompensa
Las funciones de recompensa reciben el parámetro environments (una lista de instancias de entorno), por lo que puedes acceder a cualquier estado almacenado durante el episodio:
def reward_func(environments, **kwargs) -> list[float]:
return [env.reward for env in environments]Para obtener más información sobre las funciones de recompensa, consulte GRPO - Custom Reward Functions.
Consejos para las funciones de recompensa
Algunas cosas que nos han resultado útiles al trabajar con entornos de OpenEnv y GRPO:
- Asegúrate de que los grupos difieran. GRPO compara las finalizaciones dentro de un grupo, por lo que el ranking relativo importa más que los valores absolutos, y un grupo donde todas las finalizaciones reciben la misma recompensa no proporciona ninguna señal. Una recompensa binaria (1.0 para éxito, 0.0 en caso contrario) es clara cuando el modelo tiene éxito a veces. Cuando rara vez lo hace, añade crédito parcial: el ejemplo de Sudoku añade la fracción de celdas completadas a la recompensa por resolverlo, y el ejemplo de Wordle añade los verdes de la mejor conjetura.
- Comprueba el estado final, no el camino. Siempre que sea posible, deja que el entorno juzgue el resultado (p. ej., “¿el modelo resolvió el rompecabezas?”) en lugar de verificar si siguió una secuencia específica de acciones. Esto le da al modelo la libertad de descubrir sus propias estrategias.
- Prueba tu recompensa antes de entrenar. Ejecuta algunos episodios manualmente (consulta el cuaderno de ejemplo de Wordle) para confirmar que el entorno devuelve recompensas coherentes. Si un modelo capaz no logra puntuar más alto que una línea base aleatoria, es posible que la señal de recompensa necesite ajustes.
max_completion_length en episodios de varios turnos
El parámetro max_completion_length limita el número total de tokens en toda la conversación de varios turnos (todas las generaciones del modelo + los resultados de las herramientas combinados), no solo una única generación. Para entornos con muchos turnos (p. ej., Sudoku con decenas de movimientos), es posible que necesites aumentarlo:
args = GRPOConfig(
max_completion_length=4096, # default is usually 256-1024, increase for long episodes
# ...
)Si los episodios se están interrumpiendo (el modelo se detiene a mitad de la partida), esta es probablemente la causa.
Ejecuciones prolongadas contra un Space
Un Space puede cerrar WebSockets que permanecen abiertos durante horas (lo observamos aproximadamente cada 2 horas). Los clientes de OpenEnv hasta la versión 0.11.0 se reconectan entonces a una nueva sesión de entorno, y si una partida estaba en curso, el siguiente step() se ejecuta en una partida que el cliente nunca reinició, sin ningún error. Desde OpenEnv 0.12.0, los clientes lanzan SessionLostError en su lugar, por lo que la partida falla. Para mantener las desconexiones alejadas del entrenamiento, abra un nuevo cliente en reset(), de modo que cada conexión dure un episodio, y establezca MAX_CONCURRENT_ENVS un poco por encima del número de partidas jugadas simultáneamente, ya que las sesiones antiguas se liberan de forma asíncrona:
def reset(self, **kwargs) -> str | None:
self.client.close()
self.client = TextArenaEnv(base_url=env_url).sync()
result = self.client.reset()
...Ejemplo avanzado: Wordle
Entrenemos un modelo para jugar a Wordle utilizando el entorno TextArena. Esto demuestra la interacción de varios turnos, el manejo de retroalimentación acumulativa, una recompensa dividida en varios términos y la terminación del episodio mediante excepciones.
El tutorial de Wordle de OpenEnv cubre todo el flujo de trabajo: los datos de SFT, el calentamiento de SFT, este paso de GRPO y la evaluación, con el hardware y los costos. La versión en cuaderno de este ejemplo es
grpo_wordle.ipynb.
El entorno TextArena
TextArena es una colección de código abierto de juegos competitivos basados en texto, diseñada para evaluar las habilidades de razonamiento de los LLM mediante juegos textuales como Wordle, Snake, Tres en raya y más.

¿Por qué Wordle?
Wordle es un buen benchmark para el RL basado en entornos porque requiere razonar sobre la retroalimentación, es puramente textual y los modelos de 1B parámetros pueden mejorar en él.
¿Cómo funciona Wordle? Wordle es un juego de adivinanzas de palabras en el que el jugador debe adivinar una palabra de 5 letras en 6 intentos. Después de cada intento, el entorno proporciona retroalimentación letra por letra:
G U E S S X G Y X XX = no está en la palabra, G = posición correcta (verde), Y = posición incorrecta (amarillo). Aquí, “U” es correcta y está en su lugar, “E” está en la palabra pero en una posición equivocada.
Comenzar con un calentamiento de SFT
Qwen3-1.7B casi nunca gana una partida, por lo que GRPO a partir del modelo base tiene poco de lo que aprender: un grupo de partidas en las que ninguna se gana no proporciona ninguna señal. El ejemplo parte de openenv/Qwen3-1.7B-Wordle-SFT, entrenado durante una época con 1.000 partidas jugadas por un solver en el mismo formato de tool-calling, con un razonamiento breve antes de cada intento.
Clase Environment
La clase WordleEnv encapsula el cliente de TextArena, expone guess() como herramienta y conserva lo que necesita la recompensa:
import re
from textarena_env import TextArenaAction, TextArenaEnv
FEEDBACK = re.compile(r"Feedback:\n([A-Z ]+)\n([GYX ]+)")
class WordleEnv:
def __init__(self):
self.client = TextArenaEnv(base_url="https://<user>-wordle.hf.space")
def reset(self, **kwargs) -> str | None:
result = self.client.reset()
self._last_full_feedback = result.observation.messages[0].content
self.done = False
self.won = False
self.num_guesses = 0
self.best_greens = 0
self.invalid_moves = 0
return self._last_full_feedback
def guess(self, guess: str) -> str:
"""
Make a guess in the Wordle environment.
Args:
guess: The guessed word, formatted as '[abcde]'
Returns:
The feedback message from the environment.
"""
if self.done:
raise ValueError("Game over.")
result = self.client.step(TextArenaAction(message=guess))
_full_feedback = result.observation.messages[0].content
feedback = _full_feedback[len(self._last_full_feedback):]
self._last_full_feedback = _full_feedback
if "invalid move" in feedback.lower():
self.invalid_moves += 1
else:
self.num_guesses += 1
match = FEEDBACK.search(feedback)
if match:
self.best_greens = max(self.best_greens, match.group(2).count("G"))
self.won = result.done and result.reward >= 1.0
self.done = result.done
return feedbackDecisiones de diseño clave:
reset()devuelve el mensaje inicial del juego como la primera observación que ve el modelo.guess()es la única herramienta. El modelo la llama en cada turno con una palabra de 5 letras.- Segmentación de retroalimentación acumulativa: TextArena devuelve el historial completo del juego en cada turno. Extraemos solo la parte nueva para evitar repetir el contexto.
- Excepción en done: Si el modelo intenta adivinar después de que el juego termina,
guess()lanza unValueError. El entrenador captura esto y devuelve"Game over."al modelo como respuesta de la herramienta, y lo cuenta entools/failure_frequency.
Funciones de recompensa y entrenamiento
La recompensa propia del entorno paga una victoria de la misma manera ya sea tras 1 o 6 intentos, y solo otorga crédito parcial al final de una partida. Con el razonamiento activado, una política puede aprender a razonar menos y usar los 6 intentos para llegar al final. Tres términos de recompensa evitan que eso ocurra:
from datasets import Dataset
from trl import GRPOConfig, GRPOTrainer
def reward_win(environments, **kwargs) -> list[float]:
return [1.0 + 0.5 * (1 - env.num_guesses / 6) if env.won else 0.0 for env in environments]
def reward_partial(environments, **kwargs) -> list[float]:
return [0.0 if env.won else 0.1 * env.best_greens for env in environments]
def reward_invalid(environments, **kwargs) -> list[float]:
return [-0.1 * env.invalid_moves for env in environments]
prompt = """You are an expert Wordle solver with deep knowledge of English vocabulary...
Use the tool `guess` to make a guess."""
dataset = Dataset.from_dict({"prompt": [[{"role": "user", "content": prompt}]] * 600})
trainer = GRPOTrainer(
model="openenv/Qwen3-1.7B-Wordle-SFT",
reward_funcs=[reward_win, reward_partial, reward_invalid],
train_dataset=dataset,
args=GRPOConfig(
use_vllm=True,
vllm_mode="colocate",
chat_template_kwargs={"enable_thinking": True},
max_completion_length=4096,
max_tool_calling_iterations=12,
vllm_importance_sampling_mode="token_truncate",
num_generations=16,
per_device_train_batch_size=8,
gradient_accumulation_steps=8,
learning_rate=1e-6,
lr_scheduler_type="constant",
max_steps=150,
),
environment_factory=WordleEnv,
)
trainer.train()Un resultado ganador otorga una recompensa mayor cuanto antes se produzca (1.42 en un solo intento, 1.0 en seis), una derrota paga según la mejor suposición del juego, independientemente de si este terminó o no, y cada movimiento inválido cuesta 0.1. TRL registra cada término por separado, por lo que rewards/reward_win/mean aumenta con la tasa de victorias.
Ejecutar el ejemplo
Cada paso juega 64 partidas, una sesión de entorno cada una. Duplica el Space openenv/wordle y establece la variable MAX_CONCURRENT_ENVS=64 en tu copia.
Modo Colocate (1 GPU, recomendado)
python examples/grpo_wordle/grpo_wordle.py --vllm-mode colocate --env-url https://<user>-wordle.hf.space
Esto ejecuta vLLM en el mismo proceso que el entrenamiento, lo que requiere solo una GPU. En nuestra ejecución, 150 pasos tomaron aproximadamente 2 horas y 15 minutos en una A100 de 80 GB.
Modo servidor (2+ GPUs, escalable)
# Terminal 1: Start vLLM inference server
CUDA_VISIBLE_DEVICES=0 VLLM_SERVER_DEV_MODE=1 vllm serve openenv/Qwen3-1.7B-Wordle-SFT --host 0.0.0.0 --port 8000 \
--weight-transfer-config '{"backend": "nccl"}' \
--logprobs-mode processed_logprobs \
--max-logprobs -1
# Terminal 2: Run GRPO training with OpenEnv
CUDA_VISIBLE_DEVICES=1 python examples/grpo_wordle/grpo_wordle.py --vllm-mode server --vllm-server-url http://localhost:8000 \
--env-url https://<user>-wordle.hf.spaceResultados
En nuestra ejecución, con una ejecución de entrenamiento por modelo, con las mismas 30 palabras reproducidas de forma codiciosa:
| Modelo | Victorias | Aciertos por victoria | Conjeturas incorrectas |
|---|---|---|---|
| Qwen3-1.7B | 0/30 | - | 24 |
| + SFT | 10/30 | 3.2 | 41 |
| + SFT + GRPO (modelo) | 20/30 | 4.1 | 3 |
GRPO eliminó en gran medida las palabras repetidas e inventadas y ganó más partidas. La mayoría de las nuevas victorias requirieron de 4 a 6 intentos, por lo que el número de intentos por victoria aumentó. Las 30 palabras provienen de los 156 sustantivos de los que TextArena extrae sus secretos, que los modelos también vieron durante el entrenamiento, y una nueva ejecución se situará a unas pocas victorias de distancia (dos ejecuciones de SFT con la misma receta ganaron 10 y 13 partidas). Observa a los tres modelos jugar en la Wordle Arena y consulta las curvas de entrenamiento en Trackio.
Entrenamiento en múltiples entornos
Puedes entrenar un modelo único en varios entornos simultáneamente. Esto es útil cuando quieres que un modelo aprenda diferentes habilidades en paralelo. Por ejemplo, jugar a Wordle (razonamiento lingüístico) y Catch (razonamiento espacial) en la misma ejecución de entrenamiento.
La idea clave es crear una clase de meta-entorno que envuelva múltiples entornos y enrute cada muestra al correcto utilizando una columna del conjunto de datos.
Cómo funciona
- Agrega una columna
"env"(o similar) a tu conjunto de datos que identifique a qué entorno pertenece cada muestra. - En
reset(**kwargs), leakwargs["env"]para seleccionar el entorno activo para ese episodio. - Exponer herramientas de todos los entornos; el trainer descubre todos los métodos públicos.
- Utilice funciones de recompensa separadas por entorno, devolviendo
Nonepara las muestras que no pertenecen a ese entorno. TRL maneja los valoresNoneconnansum/nanmean.
Ejemplo: Wordle + Catch
El script grpo_multi_env.py entrena simultáneamente en Wordle y Catch:
class MultiEnv:
def __init__(self):
self._wordle_client = None
self._catch_client = None
self.active = None
self.reward = 0.0
self.done = False
def reset(self, **kwargs) -> str | None:
self.active = kwargs.get("env", "wordle")
self.reward = 0.0
self.done = False
if self.active == "wordle":
if self._wordle_client is not None:
try:
self._wordle_client.close()
except Exception:
pass
self._wordle_client = TextArenaEnv(base_url=WORDLE_URL)
result = self._wordle_client.reset()
self._last_full_feedback = result.observation.messages[0].content
self.reward = 0.0
return self._last_full_feedback
elif self.active == "catch":
if self._catch_client is not None:
try:
self._catch_client.close()
except Exception:
pass
self._catch_client = OpenSpielEnv(base_url=CATCH_URL)
result = self._catch_client.reset()
self.done = result.observation.done
return _format_catch_obs(result.observation.info_state)
# Wordle tool
def guess(self, guess: str) -> str:
"""Make a guess in the Wordle environment. ..."""
...
# Catch tools
def move(self, direction: str) -> str:
"""Move the paddle left or right. ..."""
...
def stay(self) -> str:
"""Do nothing and let the ball fall one step. ..."""
...Patrones clave:
- Inicialización perezosa de clientes: Crea los clientes en
reset(), no en__init__(), para evitar conexiones WebSocket innecesarias. - Cerrar antes de reabrir: Cierre el cliente anterior antes de crear uno nuevo para evitar errores de capacidad del servidor.
- Enrutamiento de
kwargs: La columna"env"del conjunto de datos se pasa areset()como un argumento de palabra clave. - Todas las herramientas se exponen simultáneamente: el modelo ve
guess,moveystaycomo herramientas disponibles, independientemente del entorno activo. Si llama a la herramienta incorrecta (por ejemplo,movedurante Wordle), el método lanza unValueErrorque el entrenador captura de forma adecuada. En la práctica, los modelos aprenden a usar las herramientas correctas en función del system prompt.
Funciones de recompensa por entorno
Cada función de recompensa devuelve None para las muestras de otros entornos:
def wordle_reward(environments, **kwargs) -> list[float | None]:
return [env.reward if env.active == "wordle" else None for env in environments]
def catch_reward(environments, **kwargs) -> list[float | None]:
rewards = []
for env in environments:
if env.active != "catch":
rewards.append(None)
elif env.done:
rewards.append(max(env.reward, 0.0))
else:
rewards.append(0.0)
return rewardsTRL convierte None en nan internamente y utiliza nansum/nanmean para la agregación, de modo que cada muestra solo se puntúa con su función de recompensa relevante.
Dataset con enrutamiento de entorno
n = 500
dataset = Dataset.from_dict({
"prompt": (
[[{"role": "user", "content": wordle_prompt}]] * n
+ [[{"role": "user", "content": catch_prompt}]] * n
),
"env": ["wordle"] * n + ["catch"] * n,
})Ejecutar el ejemplo de multi-entorno
python examples/grpo_multi_env/grpo_multi_env.py \
--wordle-url https://huggingface.co/proxy/openenv-wordle.hf.space \
--catch-url https://huggingface.co/proxy/openenv-openspiel-env.hf.space \
--vllm-mode colocate \
--gradient-accumulation-steps 4 \
--num-generations 8Al entrenar en varios entornos, monitorea las métricas por función de recompensa (
train/reward_func_0,train/reward_func_1, etc.) en lugar de la métrica combinadatrain/reward. La métrica combinada alterna entre entornos y puede parecer ruidosa.
Ejecución de los entornos
Al usar environment_factory, el trainer se conecta automáticamente al servidor de entorno. Solo es necesario que el servidor esté en ejecución. Hay tres formas de ejecutar un servidor de entorno de OpenEnv:
Conectar a un Space remoto de Hugging Face (el más simple)
La mayoría de los scripts de ejemplo usan un Space alojado por defecto (no requiere configuración):
env = EchoEnv(base_url="https://huggingface.co/proxy/openenv-echo-env.hf.space")Para el entrenamiento, duplica el Space en tu propia cuenta para evitar problemas de concurrencia. El trainer abre N conexiones WebSocket simultáneas (una por generación) y los Spaces compartidos pueden no admitir esto. Consulta Server concurrency para más detalles.
Contenedor de Docker (recomendado para producción)
docker run -d -p 8001:8000 --platform linux/amd64 registry.hf.space/openenv-echo-env:latest
Luego, conecta:
env = EchoEnv(base_url="http://0.0.0.0:8001")Mapeamos el puerto 8001 al 8000 para dejar el puerto 8000 disponible para un servidor de vLLM.
También puede iniciar el contenedor de forma programática:
env = EchoEnv.from_docker_image("registry.hf.space/openenv-echo-env:latest")Puedes encontrar la imagen de Docker de cualquier Space en el Hub: abre la página del Space → ⋮ (tres puntos) → “Run locally.”
Proceso de Python local (para desarrollo)
hf download openenv/echo_env --repo-type=space --local-dir=echo_env python -m uvicorn echo_env.src.envs.echo_env.server.app:app --host 0.0.0.0 --port 8001
Luego, conecta:
env = EchoEnv(base_url="http://0.0.0.0:8001")Para más detalles, consulte el catálogo de OpenEnv.
Catálogo de entornos
La mejor manera de explorar el catálogo actual de entornos mantenidos es visitar el catálogo oficial de OpenEnv.
Para crear tu propio entorno, consulta la guía sobre Building Your Own Environment with OpenEnv. Los entornos están estrechamente integrados con el Hub, por lo que puedes publicar nuevos entornos para que la comunidad los reutilice.
Concurrencia del servidor
Al usar environment_factory, el trainer crea N instancias de entorno (una por generación), cada una abre una conexión WebSocket con el servidor. De forma predeterminada, los servidores de OpenEnv solo permiten 1 sesión concurrente, lo que provocará fallos durante el entrenamiento.
Para admitir el entrenamiento paralelo, configure el servidor para la concurrencia:
- En su archivo de entorno, declare el soporte para sesiones concurrentes:
SUPPORTS_CONCURRENT_SESSIONS: bool = True- En tu aplicación de servidor, establece el límite de concurrencia:
app = create_app(
create_my_environment,
MyAction,
MyObservation,
max_concurrent_envs=64, # match or exceed generation_batch_size
)
max_concurrent_envsdebe ser ≥generation_batch_size(que por defecto esper_device_train_batch_size × gradient_accumulation_steps). Por ejemplo, congradient_accumulation_steps=64y un tamaño de lote de 1, se necesitan al menos 64 sesiones concurrentes.
environment_factory vs rollout_func
GRPOTrainer admite dos enfoques para el entrenamiento basado en entornos:
environment_factory(recomendado): define una clase de entorno con métodos de herramientas, y el entrenador se encarga automáticamente de la generación, el análisis de llamadas a herramientas y el bucle de varios turnos. Este es el enfoque utilizado en toda esta guía.rollout_func: Escribes tú mismo todo el bucle de generación e interacción con el entorno. Esto te da un control total sobre cómo se producen las finalizaciones, cómo se ejecutan las herramientas y cómo se calculan las recompensas.
Use rollout_func cuando environment_factory no se adapta a su caso de uso. Por ejemplo, servidores de agentes externos donde un servidor externo controla el bucle de generación y gestiona su propio protocolo de interacción entre el agente y el entorno.
Entrenamiento en arneses: entrenamiento de agentes de codificación reales (Harbor)
Las integraciones anteriores son de caja blanca: TRL controla el bucle de varios turnos por sí mismo. Muestrea cada turno, analiza las llamadas a herramientas, las ejecuta y devuelve los resultados.
Algunos agentes no pueden controlarse de esta manera porque gestionan su propio bucle. Un marco de agente de codificación de producción como opencode o claude-code tiene su propio planificador, conjunto de herramientas, gestión de contexto y condición de parada. Lo que se desea es entrenar ese agente exacto, no una reimplementación de él.
Para ello, TRL ofrece una ruta experimental de caja negra (con control del bucle) basada en experimental.async_grpo.AsyncGRPOTrainer: HarnessAsyncGRPOTrainer controla una fábrica de sesiones de OpenEnv. Los agentes y sus entornos de ejecución provienen de Harbor, servidos a través de OpenEnv con openenv harbor serve, de modo que cada agente que instala Harbor se puede entrenar de la misma manera: se selecciona uno con --harness. Consulte examples/async_grpo_harbor para un ejemplo completo, incluido un lanzador de Hugging Face Jobs.
Esta ruta requiere
openenv>=0.7.0, la primera versión con la API de capturaTrainingTrace:pip install "openenv>=0.7.0".
Cómo funciona
TRL no muestrea cada turno aquí. El agente se ejecuta hasta su finalización por sí mismo, y TRL lee lo que hizo:
- El servidor OpenEnv Harbor inicia el agente en una tarea de Harbor dentro de un sandbox (por ejemplo, E2B). Cada llamada al modelo que realiza el agente pasa a través del proxy de captura del servidor hacia tu servidor vLLM, que registra los ids de tokens de prompt y completion del propio motor, los logprobs muestreados y una máscara de pérdida por token.
- Cuando el agente se detiene, el verificador propio de la tarea puntúa el resultado, y TRL recibe el
TrainingTracevalidado de OpenEnv y construye las filas de entrenamiento a partir de sus ids de tokens y máscaras. TRL no vuelve a tokenizar los prompts capturados. - GRPO entrena en esas filas. La recompensa se propaga a cada token entrenado a través de la ventaja relativa al grupo, y los nuevos pesos se sincronizan de vuelta en el mismo servidor de vLLM.
Cada rollout se ejecuta en su propia sesión aislada, con su propio sandbox y su propia sesión de captura. El aislamiento es importante por dos razones: el proxy debe capturar exactamente los tokens de ese rollout, y un rollout no debe interferir con otro. La configuración max_concurrent_rollouts controla cuántos rollouts se ejecutan al mismo tiempo, y el MAX_CONCURRENT_ENVS del servidor debe ser al menos max_concurrent_rollouts + 1 (la fábrica mantiene una conexión extra para los metadatos de la tarea).
Configuración
HarnessAsyncGRPOTrainer recibe el modelo, la configuración y el conjunto de datos de la misma manera que experimental.async_grpo.AsyncGRPOTrainer, pero con una fábrica de sesiones en lugar de reward_funcs, tools y environment_factory. Sin un harness_adapter, el agente controla su propio bucle. El entrenador construye la fábrica de sesiones con su propia política de muestreo, por lo que se le pasa una función invocable, como partial(HarborSessionFactory, ...), en lugar de una fábrica ya construida. Cada sesión devuelve un TrainingTrace de OpenEnv que contiene los tokens exactos del motor, los logprobs de comportamiento y las máscaras prellenadas. rollout_reward_fn es opcional; si no se proporciona, TRL utiliza la recompensa del verificador de la sesión.
from functools import partial
from harbor_env.harness import HarborSessionFactory
from trl.experimental.async_grpo import AsyncGRPOConfig
from trl.experimental.async_grpo.openenv_harness import HarnessAsyncGRPOTrainer
factory = partial(
HarborSessionFactory,
"http://localhost:8200", # the OpenEnv Harbor server
split="<hf-dataset>", # a Harbor task dataset the server serves
harness="mini-swe-agent", # or "opencode", "claude-code", ...
sandbox="e2b",
llm_url=vllm_url,
model=model,
)
config = AsyncGRPOConfig(num_generations=8, max_concurrent_rollouts=8, vllm_server_base_url=vllm_url)
trainer = HarnessAsyncGRPOTrainer(
model=model,
harness_session_factory=factory, # called by the trainer with its sampling policy
args=config,
train_dataset=dataset,
rollout_reward_fn=my_reward, # outcome -> float | None
)
trainer.train()Lo que necesitas definir
OpenEnv es responsable de la validación de la captura y la selección de tokens. TRL convierte la máscara de secuencia completa del productor en su máscara de finalización interna, empaqueta las filas resultantes y aplica GRPO. No reconstruye tokens ni infiere qué llamadas pertenecen al agente.
rollout_reward_fn : cómo puntuar un rollout
Callable[[HarnessRolloutOutcome], float | None]
TRL no sabe qué significa el éxito para tu tarea, por lo que conviertes cada rollout finalizado en una recompensa escalar. La función recibe un HarnessRolloutOutcome que describe lo que hizo el agente:
env_reward(float | None): la recompensa del verificador de la tarea, oNonecuando el rollout no pudo puntuarse.completion(list[dict]): el transcurso final del mensaje.trace(list[TraceEntry]): los registros de captura de OpenEnv seleccionados, incluyendo los turnos con máscara cero.tool_call_countytool_failure_count(int): cuántas llamadas a herramientas realizó el agente y cuántas parecían ser fallos.tool_calls_by_name(dict[str, int]): llamadas por herramienta, por ejemplo{"bash": 3, "edit": 2}.timed_out(bool): si el agente agotó su presupuesto de tiempo.
Devuelve un float o devuelve None para marcar el rollout como no puntuable, de modo que se descarte de la línea base del grupo en lugar de contarse como un cero. Si no pasas esta función, se utiliza el env_reward crudo tal cual. El ejemplo de Harbor mantiene la corrección del verificador como señal principal y añade un pequeño bono de eficiencia de herramientas solo cuando la tarea se resuelve.
¿Por qué no usar directamente el verificador? El verificador es responsabilidad del entorno y responde a una sola pregunta, “¿cuán correcto fue el resultado?”, lo que lo mantiene limpio y reutilizable para la evaluación. La recompensa sobre la que se entrena es una decisión separada, propia del momento de entrenamiento (binarizar la puntuación, penalizar el comportamiento degenerado, descartar los *rollouts* que no se pueden puntuar). Además, necesita señales que el verificador nunca ve, ya que el verificador solo inspecciona el resultado final, mientras que
rollout_reward_fntambién recibe la trayectoria (número de herramientas,timed_out, la traza). Por ejemplo, un *rollout* puede pasar algunas pruebas pero nunca ejecutarbash; solorollout_reward_fnpuede ver eso y penalizarlo.
La elegibilidad de los tokens proviene de OpenEnv
session.fetch_training_trace() devuelve un TrainingTrace validado. Cada turno tiene un node_id, prompt_token_ids, completion_token_ids, per_token_logps y un loss_mask obligatorio sobre el prompt y la completion. Las posiciones del prompt son cero. Se conservan las máscaras de completion parciales, incluidos los ceros dentro de una respuesta del asistente.
Por ejemplo, el prompt [10, 11], la completion [20, 21, 22] y la mask [0, 0, 1, 0, 1] entrenan en los tokens 20 y 22. El token 21 permanece como contexto. Una completion completamente enmascarada se conserva para el contexto y la contabilidad de uso.
El productor excluye las llamadas auxiliares y los reintentos descartados. Esta API no tiene parámetros train_turn_fn ni agent_turn_fn. Las integraciones existentes deben mover la selección a sus máscaras de OpenEnv. El verificador de la tarea permanece independiente de la elegibilidad de los tokens.
Las capturas malformadas fallan en el límite de OpenEnv y detienen el entrenamiento. Las fallas de transporte siguen siendo no puntuables. Los historiales reescritos inician filas separadas, preservando los tokens y logprobs muestreados. Múltiples filas aún comparten la recompensa del rollout; esta API no cambia el ponderado de GRPO.
Requisitos
El servidor OpenEnv Harbor es el propietario del conjunto de datos y de los entornos de pruebas. Necesita la credencial del backend de entornos de pruebas (por ejemplo, E2B_API_KEY):
MAX_CONCURRENT_ENVS=9 openenv harbor serve --dataset <hf-dataset> --port 8200 --capture-port 8300 --expose gradio
El servidor de vLLM debe exponer la invocación de herramientas y los ids de tokens reales, y habilitar la sincronización de pesos de NCCL para que el agente siempre acceda a la política actual. Los rollouts y las actualizaciones de pesos deben usar el mismo servidor de vLLM:
vllm serve <model> \
--enable-auto-tool-choice --tool-call-parser <parser> \
--logprobs-mode processed_logprobs \
--return-tokens-as-token-ids \
--weight-transfer-config '{"backend":"nccl"}'Update on GitHubEl entrenamiento con propiedad del bucle se encuentra en
trl.experimentaly su API puede cambiar. El ejemplo fija la revisión de OpenEnv con la que se probó; consulte su README para la configuración exacta.
