Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 11 additions & 8 deletions docs/source/openenv.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ ENV_URL = "https://openenv-echo-env.hf.space"

class EchoToolEnv:
def __init__(self):
self.env = EchoEnv(base_url=ENV_URL)
self.env = EchoEnv(base_url=ENV_URL).sync()
self.reward = 0.0

def reset(self, **kwargs) -> str | None:
Expand Down Expand Up @@ -248,7 +248,7 @@ from textarena_env import TextArenaAction, TextArenaEnv

class WordleEnv:
def __init__(self):
self.client = TextArenaEnv(base_url="https://openenv-wordle.hf.space")
self.client = TextArenaEnv(base_url="https://openenv-wordle.hf.space").sync()

def reset(self, **kwargs) -> str | None:
result = self.client.reset()
Expand Down Expand Up @@ -401,7 +401,7 @@ class MultiEnv:
self._wordle_client.close()
except Exception:
pass
self._wordle_client = TextArenaEnv(base_url=WORDLE_URL)
self._wordle_client = TextArenaEnv(base_url=WORDLE_URL).sync()
result = self._wordle_client.reset()
self._last_full_feedback = result.observation.messages[0].content
self.reward = 0.0
Expand All @@ -412,7 +412,7 @@ class MultiEnv:
self._catch_client.close()
except Exception:
pass
self._catch_client = OpenSpielEnv(base_url=CATCH_URL)
self._catch_client = OpenSpielEnv(base_url=CATCH_URL).sync()
result = self._catch_client.reset()
self.done = result.observation.done
return _format_catch_obs(result.observation.info_state)
Expand Down Expand Up @@ -492,6 +492,9 @@ python examples/scripts/openenv/multi_env.py \

When using `environment_factory`, the trainer connects to the environment server automatically. You just need the server to be running. There are three ways to run an OpenEnv environment server:

> [!NOTE]
> OpenEnv's client API is async-first: calling `reset()` or `step()` directly returns a coroutine. Appending `.sync()` (as in the examples below) returns a blocking client, which is what the synchronous `environment_factory` and `rollout_func` code expects. The classmethod constructors such as `from_docker_image()` are coroutines too (`await` them). See the OpenEnv [async/sync guide](https://github.com/huggingface/OpenEnv/blob/main/docs/source/guides/async-sync.md).

<hfoptions id="env_mode">

<hfoption id="space">
Expand All @@ -501,7 +504,7 @@ When using `environment_factory`, the trainer connects to the environment server
Most example scripts default to a hosted Space (no setup needed):

```python
env = EchoEnv(base_url="https://openenv-echo-env.hf.space")
env = EchoEnv(base_url="https://openenv-echo-env.hf.space").sync()
```

> [!WARNING]
Expand All @@ -520,7 +523,7 @@ docker run -d -p 8001:8000 --platform linux/amd64 registry.hf.space/openenv-echo
Then connect:

```python
env = EchoEnv(base_url="http://0.0.0.0:8001")
env = EchoEnv(base_url="http://0.0.0.0:8001").sync()
```

We map port 8001 to 8000 to leave port 8000 available for a vLLM server.
Expand Down Expand Up @@ -550,7 +553,7 @@ python -m uvicorn echo_env.src.envs.echo_env.server.app:app --host 0.0.0.0 --por
Then connect:

```python
env = EchoEnv(base_url="http://0.0.0.0:8001")
env = EchoEnv(base_url="http://0.0.0.0:8001").sync()
```

For more details, see the [OpenEnv catalog](https://huggingface.co/docs/openenv/environments).
Expand Down Expand Up @@ -636,7 +639,7 @@ trainer = GRPOTrainer(..., rollout_func=rollout_func)
```python
class EchoToolEnv:
def __init__(self):
self.env = EchoEnv(base_url=url)
self.env = EchoEnv(base_url=url).sync()
self.reward = 0.0

def reset(self, **kwargs) -> str | None:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,10 @@
"from browsergym_env import BrowserGymEnv\n",
"space_url = \"https://openenv-browsergym-env.hf.space\"\n",
"\n",
"client = BrowserGymEnv(base_url=space_url)"
"# OpenEnv's client API is async-first: `reset()` and `step()` are coroutines.\n",
"# `.sync()` returns a synchronous wrapper so we can call them as plain blocking\n",
"# `reset()`/`step()` throughout this notebook.\n",
"client = BrowserGymEnv(base_url=space_url).sync()"
]
},
{
Expand Down
3 changes: 2 additions & 1 deletion examples/notebooks/openenv_sudoku_grpo.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -325,7 +325,8 @@
"\n",
"class SudokuEnv:\n",
" def __init__(self):\n",
" self.client = TextArenaEnv(base_url=\"https://openenv-sudoku.hf.space\")\n",
" # OpenEnv's client API is async-first; .sync() exposes blocking reset()/step().\n",
" self.client = TextArenaEnv(base_url=\"https://openenv-sudoku.hf.space\").sync()\n",
" self.difficulty = \"easy\"\n",
" self.max_turns = 100\n",
" self._turn = 0\n",
Expand Down
3 changes: 2 additions & 1 deletion examples/notebooks/openenv_wordle_grpo.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,8 @@
"\n",
"class WordleEnv:\n",
" def __init__(self):\n",
" self.client = TextArenaEnv(base_url=\"https://openenv-wordle.hf.space\")\n",
" # OpenEnv's client API is async-first; .sync() exposes blocking reset()/step().\n",
" self.client = TextArenaEnv(base_url=\"https://openenv-wordle.hf.space\").sync()\n",
"\n",
" def reset(self, **kwargs) -> None | str:\n",
" result = self.client.reset()\n",
Expand Down
3 changes: 2 additions & 1 deletion examples/scripts/openenv/browsergym.py
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,8 @@ def main() -> None:

class BrowserGymVLMEnv:
def __init__(self):
self.client = BrowserGymEnv(base_url=space_url)
# OpenEnv's client API is async-first; .sync() exposes blocking reset()/step().
self.client = BrowserGymEnv(base_url=space_url).sync()
self.reward = 0.0
self.done = False
self._step_count = 0
Expand Down
3 changes: 2 additions & 1 deletion examples/scripts/openenv/browsergym_llm.py
Original file line number Diff line number Diff line change
Expand Up @@ -285,7 +285,8 @@ def main() -> None:

class BrowserGymLLMEnv:
def __init__(self):
self.client = BrowserGymEnv(base_url=space_url)
# OpenEnv's client API is async-first; .sync() exposes blocking reset()/step().
self.client = BrowserGymEnv(base_url=space_url).sync()
self.reward = 0.0
self._done = False
self._step_count = 0
Expand Down
3 changes: 2 additions & 1 deletion examples/scripts/openenv/carla.py
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,8 @@ class CarlaGRPOEnv:

def __init__(self):
url = next(CarlaGRPOEnv._env_url_iter)
self.client = CarlaEnv(base_url=url, connect_timeout_s=30, message_timeout_s=120)
# OpenEnv's client API is async-first; .sync() exposes blocking reset()/step().
self.client = CarlaEnv(base_url=url, connect_timeout_s=30, message_timeout_s=120).sync()

@staticmethod
def _describe(obs) -> str:
Expand Down
3 changes: 2 additions & 1 deletion examples/scripts/openenv/carla_vlm.py
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,8 @@ class CarlaGRPOEnv:

def __init__(self):
url = next(CarlaGRPOEnv._env_url_iter)
self.client = CarlaEnv(base_url=url, connect_timeout_s=30, message_timeout_s=120)
# OpenEnv's client API is async-first; .sync() exposes blocking reset()/step().
self.client = CarlaEnv(base_url=url, connect_timeout_s=30, message_timeout_s=120).sync()

@staticmethod
def _describe(obs) -> str:
Expand Down
5 changes: 3 additions & 2 deletions examples/scripts/openenv/carla_vlm_gemma.py
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,8 @@ class CarlaVLMEnv:

def __init__(self):
self.url = next(CarlaVLMEnv._env_url_iter)
self.client = CarlaEnv(base_url=self.url, connect_timeout_s=30, message_timeout_s=120)
# OpenEnv's client API is async-first; .sync() exposes blocking reset()/step().
self.client = CarlaEnv(base_url=self.url, connect_timeout_s=30, message_timeout_s=120).sync()
self.reward = 0.0

@staticmethod
Expand Down Expand Up @@ -166,7 +167,7 @@ def reset(self, **kwargs) -> str | None:
if attempt == 2:
raise
print(f"[WARN] reset failed (attempt {attempt + 1}/3): {e}. Reconnecting...")
self.client = CarlaEnv(base_url=self.url, connect_timeout_s=30, message_timeout_s=120)
self.client = CarlaEnv(base_url=self.url, connect_timeout_s=30, message_timeout_s=120).sync()

def observe(self) -> list:
"""
Expand Down
3 changes: 2 additions & 1 deletion examples/scripts/openenv/catch.py
Original file line number Diff line number Diff line change
Expand Up @@ -237,7 +237,8 @@ class CatchEnv:
COLS = 5

def __init__(self):
self.client = OpenSpielEnv(base_url=env_url)
# OpenEnv's client API is async-first; .sync() exposes blocking reset()/step().
self.client = OpenSpielEnv(base_url=env_url).sync()

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Update docker-mode bootstrap for async OpenEnv constructors

This only wraps clients created from an already resolved URL. In the default --env-mode docker-image path above, _bootstrap = OpenSpielEnv.from_docker_image(...) is now a coroutine under the async-first API, so _bootstrap.base_url fails before this environment is ever constructed; the same pattern exists in the docker-hub path and in the Sudoku script. Please update those bootstrap paths to await/run the async constructor (or otherwise derive a URL from the provider) rather than only adding .sync() here.

Useful? React with 👍 / 👎.

self.reward = 0.0
self.done = False

Expand Down
3 changes: 2 additions & 1 deletion examples/scripts/openenv/echo.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,8 @@ def main():

class EchoToolEnv:
def __init__(self):
self.env = EchoEnv(base_url=args.env_host)
# OpenEnv's client API is async-first; .sync() exposes blocking reset()/step().
self.env = EchoEnv(base_url=args.env_host).sync()
self.reward = 0.0

def reset(self, **kwargs) -> None | str:
Expand Down
5 changes: 3 additions & 2 deletions examples/scripts/openenv/multi_env.py
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,8 @@ def reset(self, **kwargs) -> str | None:
self._wordle_client.close()
except Exception:
pass
self._wordle_client = TextArenaEnv(base_url=MultiEnv.wordle_url)
# OpenEnv's client API is async-first; .sync() exposes blocking reset()/step().
self._wordle_client = TextArenaEnv(base_url=MultiEnv.wordle_url).sync()
result = self._wordle_client.reset()
self._last_full_feedback = result.observation.messages[0].content
self.reward = 0.0
Expand All @@ -146,7 +147,7 @@ def reset(self, **kwargs) -> str | None:
self._catch_client.close()
except Exception:
pass
self._catch_client = OpenSpielEnv(base_url=MultiEnv.catch_url)
self._catch_client = OpenSpielEnv(base_url=MultiEnv.catch_url).sync()
result = self._catch_client.reset()
self.done = result.observation.done
return _format_catch_obs(result.observation.info_state)
Expand Down
3 changes: 2 additions & 1 deletion examples/scripts/openenv/sudoku.py
Original file line number Diff line number Diff line change
Expand Up @@ -410,7 +410,8 @@ def main() -> None:

class SudokuEnv:
def __init__(self):
self.client = TextArenaEnv(base_url=env_url)
# OpenEnv's client API is async-first; .sync() exposes blocking reset()/step().
self.client = TextArenaEnv(base_url=env_url).sync()
self._difficulty = difficulty
self._max_turns = max_turns
self._api_delay = api_delay
Expand Down
3 changes: 2 additions & 1 deletion examples/scripts/openenv/wordle.py
Original file line number Diff line number Diff line change
Expand Up @@ -206,7 +206,8 @@ def main() -> None:

class WordleEnv:
def __init__(self):
self.client = TextArenaEnv(base_url=env_url)
# OpenEnv's client API is async-first; .sync() exposes blocking reset()/step().
self.client = TextArenaEnv(base_url=env_url).sync()

def reset(self, **kwargs) -> str | None:
result = self.client.reset()
Expand Down
Loading