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
69 changes: 36 additions & 33 deletions docs/environment-builder.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,33 +58,26 @@ my_env/
└── Dockerfile
```

Python classes are generated for the action, observation, and state, and a client is generated for the environment. For example, you will find `MyEnvironment`, `MyAction`, `MyObservation`, and `MyState` in the `my_env` directory based on the name of the environment you provided.
Python classes are generated for the action, observation, environment, and client. For example, you will find `MyEnvironment`, `MyAction`, `MyObservation`, and `MyEnv` (client) in the `my_env` directory based on the name you provided. The environment uses the core `State` class from `openenv.core.env_server.types`.

### 2. Define Models

Edit `models.py` to describe your action, observation, and state dataclasses:
Edit `models.py` to describe your action and observation using Pydantic:

```python
# models.py
from dataclasses import dataclass
from openenv.core.env_server import Action, Observation, State
from pydantic import Field
from openenv.core.env_server.types import Action, Observation

@dataclass
class MyAction(Action):
"""Your custom action."""
command: str
parameters: dict
command: str = Field(..., description="Command to execute")
parameters: dict = Field(default_factory=dict, description="Command parameters")

@dataclass
class MyObservation(Observation):
"""Your custom observation."""
result: str
success: bool

@dataclass
class MyState(State):
"""Custom state fields."""
custom_field: int = 0
result: str = Field(..., description="Result of the action")
success: bool = Field(..., description="Whether the action succeeded")
```

### 3. Implement Environment Logic
Expand All @@ -93,42 +86,42 @@ Customize `server/my_environment.py` by extending `Environment`:

```python
# server/my_environment.py
import uuid
from openenv.core.env_server import Environment
from ..models import MyAction, MyObservation, MyState
from uuid import uuid4
from openenv.core.env_server.interfaces import Environment
from openenv.core.env_server.types import State
from models import MyAction, MyObservation

class MyEnvironment(Environment):
def __init__(self):
super().__init__()
self._state = MyState()
self._state = State(episode_id=str(uuid4()), step_count=0)

def reset(self) -> MyObservation:
self._state = MyState(episode_id=str(uuid.uuid4()))
return MyObservation(result="Ready", success=True)
self._state = State(episode_id=str(uuid4()), step_count=0)
return MyObservation(result="Ready", success=True, done=False, reward=0.0)

def step(self, action: MyAction) -> MyObservation:
# Implement your logic here
self._state.step_count += 1
result = self._execute_command(action.command)
return MyObservation(result=result, success=True)
return MyObservation(result=result, success=True, done=False, reward=1.0)

@property
def state(self) -> MyState:
def state(self) -> State:
return self._state
```

### 4. Create the FastAPI Server

`server/app.py` should expose the environment through `create_fastapi_app`:
`server/app.py` should expose the environment through `create_app`:

```python
# server/app.py
from openenv.core.env_server import create_fastapi_app
from ..models import MyAction, MyObservation
from openenv.core.env_server.http_server import create_app
from my_env.models import MyAction, MyObservation
from .my_environment import MyEnvironment

env = MyEnvironment()
app = create_fastapi_app(env, MyAction, MyObservation)
app = create_app(env, MyAction, MyObservation, env_name="my_env")
```

### 5. Implement the Client
Expand All @@ -138,23 +131,33 @@ app = create_fastapi_app(env, MyAction, MyObservation)
```python
# client.py
from openenv.core.http_env_client import HTTPEnvClient
from openenv.core.types import StepResult
from .models import MyAction, MyObservation, MyState
from openenv.core.client_types import StepResult
from openenv.core.env_server.types import State
from .models import MyAction, MyObservation

class MyEnv(HTTPEnvClient[MyAction, MyObservation]):
def _step_payload(self, action: MyAction) -> dict:
return {"command": action.command, "parameters": action.parameters}

def _parse_result(self, payload: dict) -> StepResult[MyObservation]:
obs = MyObservation(**payload["observation"])
obs_data = payload.get("observation", {})
obs = MyObservation(
result=obs_data.get("result", ""),
success=obs_data.get("success", False),
done=payload.get("done", False),
reward=payload.get("reward"),
)
return StepResult(
observation=obs,
reward=payload.get("reward"),
done=payload.get("done", False),
)

def _parse_state(self, payload: dict) -> MyState:
return MyState(**payload)
def _parse_state(self, payload: dict) -> State:
return State(
episode_id=payload.get("episode_id"),
step_count=payload.get("step_count", 0),
)
```

### 6. Configure Dependencies & Dockerfile
Expand Down
10 changes: 4 additions & 6 deletions envs/echo_env/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
The Echo environment is a simple test environment that echoes back messages.
"""

from dataclasses import dataclass
from pydantic import Field

# Support both in-repo and standalone imports
try:
Expand All @@ -21,16 +21,14 @@
from openenv.core.env_server.types import Action, Observation


@dataclass(kw_only=True)
class EchoAction(Action):
"""Action for the Echo environment - just a message to echo."""

message: str
message: str = Field(..., min_length=1, description="Message to echo back")


@dataclass(kw_only=True)
class EchoObservation(Observation):
"""Observation from the Echo environment - the echoed message."""

echoed_message: str
message_length: int = 0
echoed_message: str = Field(..., description="The echoed message from the environment")
message_length: int = Field(default=0, ge=0, description="Length of the echoed message")
10 changes: 4 additions & 6 deletions src/openenv/cli/templates/openenv_env/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,22 +10,20 @@
The __ENV_NAME__ environment is a simple test environment that echoes back messages.
"""

from dataclasses import dataclass
from pydantic import Field

from openenv.core.env_server.types import Action, Observation


@dataclass(kw_only=True)
class __ENV_CLASS_NAME__Action(Action):
"""Action for the __ENV_TITLE_NAME__ environment - just a message to echo."""

message: str
message: str = Field(..., description="Message to echo back")


@dataclass(kw_only=True)
class __ENV_CLASS_NAME__Observation(Observation):
"""Observation from the __ENV_TITLE_NAME__ environment - the echoed message."""

echoed_message: str
message_length: int = 0
echoed_message: str = Field(..., description="The echoed message")
message_length: int = Field(default=0, description="Length of the echoed message")