{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Module 1: Why OpenEnv? — Your First Environments\n", "\n", "In this notebook you'll connect to three real hosted OpenEnv environments and interact with each using the same 3-method interface: `reset()`, `step()`, `state()`.\n", "\n", "**Time:** ~15 min · **Difficulty:** Beginner · **GPU:** Not required" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": "!pip install -q openenv-core fastmcp\n!git clone --depth=1 -q https://github.com/meta-pytorch/OpenEnv.git 2>/dev/null || true\n\nimport sys, os\nrepo = os.path.abspath('OpenEnv')\nfor p in [repo, os.path.join(repo, 'src')]:\n if p not in sys.path:\n sys.path.insert(0, p)\nprint(\"Setup complete!\")" }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 1. The Echo Environment\n", "\n", "The simplest possible OpenEnv environment — it echoes back whatever you send. Perfect for learning the interface.\n", "\n", "Hosted at: `https://openenv-echo-env.hf.space`" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from envs.echo_env import EchoEnv\n", "\n", "# EchoEnv extends MCPToolClient — it exposes tools, not raw reset/step actions.\n", "# MCP methods (list_tools, call_tool) are async; .sync() wraps them automatically\n", "# via SyncEnvClient.__getattr__, so the same .sync() pattern works here.\n", "with EchoEnv(base_url='https://openenv-echo-env.hf.space').sync() as env:\n", " # reset() starts a new episode\n", " result = env.reset()\n", " print('After reset:')\n", " print(f' Observation: {result.observation}')\n", " print(f' Done: {result.done}')\n", " print()\n", "\n", " # Discover available tools\n", " tools = env.list_tools()\n", " print('Available tools:')\n", " for tool in tools:\n", " print(f' - {tool.name}: {tool.description}')\n", " print()\n", "\n", " # call_tool() sends a message and returns the result\n", " response = env.call_tool('echo_message', message='Hello, OpenEnv!')\n", " print(f'echo_message(\"Hello, OpenEnv!\") -> {response}')\n", "\n", " response = env.call_tool('echo_with_length', message='OpenEnv')\n", " print(f'echo_with_length(\"OpenEnv\") -> {response}')\n", "\n", " # state() returns episode metadata\n", " state = env.state()\n", " print(f'\\nState: step_count={state.step_count}')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Three methods. That's the entire API. Every OpenEnv environment works exactly like this." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 2. OpenSpiel Catch\n", "\n", "Now let's connect to a real game. Catch is a simple single-player game from DeepMind's OpenSpiel:\n", "\n", "- A ball falls from the top of a 10×5 grid\n", "- You move a paddle left/right to catch it\n", "- Actions: `0` = left, `1` = stay, `2` = right\n", "- Reward: `+1` if caught, `0` if missed\n", "\n", "Same 3 methods, completely different game." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from envs.openspiel_env import OpenSpielEnv\n", "from envs.openspiel_env.models import OpenSpielAction\n", "\n", "OPENSPIEL_URL = 'https://openenv-openspiel-catch.hf.space'\n", "\n", "with OpenSpielEnv(base_url=OPENSPIEL_URL).sync() as env:\n", " result = env.reset()\n", " print('Game: Catch')\n", " print(f'Legal actions: {result.observation.legal_actions}')\n", " print(f'Info state length: {len(result.observation.info_state)}')\n", " print()\n", "\n", " # Play a few steps with a random policy\n", " import random\n", " step = 0\n", " while not result.done:\n", " action_id = random.choice(result.observation.legal_actions)\n", " action_name = {0: 'LEFT', 1: 'STAY', 2: 'RIGHT'}[action_id]\n", " result = env.step(OpenSpielAction(\n", " action_id=action_id,\n", " game_name='catch'\n", " ))\n", " step += 1\n", " print(f'Step {step}: {action_name} -> reward={result.reward}, done={result.done}')\n", "\n", " print(f'\\nFinal reward: {result.reward}')\n", " state = env.state()\n", " print(f'State: step_count={state.step_count}')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Same pattern: `reset()` → `step()` → check `done`. The observation type is different (`OpenSpielObservation` vs `EchoObservation`), but the interface is identical." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 3. TextArena Wordle\n", "\n", "TextArena is a text-based game environment. Wordle gives you 6 attempts to guess a 5-letter word, with color-coded feedback after each guess.\n", "\n", "Hosted at: `https://burtenshaw-textarena.hf.space`" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from envs.textarena_env import TextArenaEnv\n", "from envs.textarena_env.models import TextArenaAction\n", "\n", "TEXTARENA_URL = 'https://burtenshaw-textarena.hf.space'\n", "\n", "with TextArenaEnv(base_url=TEXTARENA_URL).sync() as env:\n", " result = env.reset()\n", " print('Wordle prompt:')\n", " print(result.observation.prompt)\n", " print()\n", "\n", " # Make a few guesses\n", " guesses = ['crane', 'slate', 'blind']\n", " for guess in guesses:\n", " if result.done:\n", " break\n", " result = env.step(TextArenaAction(message=f'[{guess}]'))\n", " print(f'Guess: {guess}')\n", " for msg in result.observation.messages:\n", " print(f' [{msg.category}] {msg.content}')\n", " print(f' Reward: {result.reward}, Done: {result.done}')\n", " print()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 4. Async vs Sync\n", "\n", "OpenEnv clients are async by default. For notebooks and simple scripts, use the `.sync()` wrapper:\n", "\n", "```python\n", "# Sync (notebooks, simple scripts)\n", "with EchoEnv(base_url=url).sync() as env:\n", " result = env.reset()\n", "\n", "# Async (production, training loops)\n", "async with EchoEnv(base_url=url) as env:\n", " result = await env.reset()\n", "```\n", "\n", "For this course, we'll use `.sync()` everywhere for simplicity." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Summary\n", "\n", "You connected to three completely different environments — Echo, Catch, Wordle — using the same interface:\n", "\n", "| Method | What it does |\n", "|--------|--------------|\n", "| `reset()` | Start a new episode |\n", "| `step(action)` | Take an action, get observation + reward |\n", "| `state()` | Get episode metadata |\n", "\n", "The action and observation types change per environment, but the pattern never does.\n", "\n", "**Next:** [Module 2](../module-2/README.md) — Using existing environments to build and compare policies." ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "name": "python", "version": "3.11.0" } }, "nbformat": 4, "nbformat_minor": 4 }