openenv_hack / minimal_openenv_app.py
thomasm6m6's picture
Restore minimal OpenEnv app with logs
de55468 verified
from __future__ import annotations
import os
import time
from pathlib import Path
from typing import Literal
from uuid import uuid4
from fastapi import Request
from fastapi.responses import PlainTextResponse, RedirectResponse
from openenv.core.env_server import create_app
from openenv.core.env_server.interfaces import Environment
from openenv.core.env_server.types import Action, Observation, State
from pydantic import Field
LOG_PATH = Path("/tmp/minimal-space.log")
START_TS = time.strftime("%Y-%m-%dT%H:%M:%S%z")
def log(message: str) -> None:
line = f"[{time.strftime('%Y-%m-%dT%H:%M:%S%z')}] {message}"
print(line, flush=True)
try:
with LOG_PATH.open("a", encoding="utf-8") as f:
f.write(line + "\n")
except Exception:
pass
class MinimalAction(Action):
action_type: Literal["noop", "increment", "finish"] = "noop"
amount: int = Field(default=1, ge=1, le=3)
class MinimalObservation(Observation):
status: str
counter: int
summary: str
reward: float = 0.0
done: bool = False
class MinimalState(State):
counter: int = 0
class MinimalEnvironment(Environment[MinimalAction, MinimalObservation, MinimalState]):
SUPPORTS_CONCURRENT_SESSIONS = False
def __init__(self):
super().__init__()
log("MinimalEnvironment.__init__ begin")
self._done = False
self._state = MinimalState(episode_id=str(uuid4()), step_count=0, counter=0)
log("MinimalEnvironment.__init__ end")
def reset(self, seed: int | None = None, episode_id: str | None = None, **kwargs) -> MinimalObservation:
log(f"reset begin seed={seed} episode_id={episode_id} kwargs={kwargs}")
self._done = False
self._state = MinimalState(
episode_id=episode_id or str(uuid4()),
step_count=0,
counter=0,
)
obs = self._observation(status="ready", reward=0.0, done=False)
log(f"reset end episode_id={self._state.episode_id}")
return obs
def step(self, action: MinimalAction, timeout_s: float | None = None, **kwargs) -> MinimalObservation:
log(f"step begin action={action.model_dump()} timeout_s={timeout_s} kwargs={kwargs}")
if self._done:
obs = self._observation(status="done", reward=0.0, done=True)
log("step end already done")
return obs
self._state.step_count += 1
reward = 0.0
status = "ok"
if action.action_type == "increment":
self._state.counter += action.amount
reward = float(action.amount)
elif action.action_type == "finish":
self._done = True
status = "finished"
if self._state.step_count >= 8:
self._done = True
status = "finished"
obs = self._observation(status=status, reward=reward, done=self._done)
log(f"step end counter={self._state.counter} step_count={self._state.step_count} done={self._done}")
return obs
@property
def state(self) -> MinimalState:
log("state property")
return self._state
def close(self) -> None:
log("close")
def _observation(self, *, status: str, reward: float, done: bool) -> MinimalObservation:
return MinimalObservation(
status=status,
counter=self._state.counter,
summary=(
f"Minimal OpenEnv demo. Counter={self._state.counter}. "
f"Step={self._state.step_count}. "
f"Choose noop, increment, or finish."
),
reward=reward,
done=done,
)
log("minimal_openenv_app module import begin")
app = create_app(MinimalEnvironment, MinimalAction, MinimalObservation, env_name="minimal_openenv")
log("openenv app created")
@app.on_event("startup")
async def startup() -> None:
log("startup event begin")
log(f"python={os.sys.version.split()[0]}")
log(f"cwd={os.getcwd()}")
log(f"enable_web={os.getenv('ENABLE_WEB_INTERFACE')}")
log("startup event end")
@app.middleware("http")
async def request_logger(request: Request, call_next):
log(f"request start method={request.method} path={request.url.path}")
response = await call_next(request)
log(f"request end method={request.method} path={request.url.path} status={response.status_code}")
return response
@app.get("/", include_in_schema=False)
def root() -> RedirectResponse:
log("root redirect")
return RedirectResponse(url="/web")
@app.get("/logs", include_in_schema=False)
def logs() -> PlainTextResponse:
log("logs handler")
try:
return PlainTextResponse(LOG_PATH.read_text(encoding="utf-8"))
except FileNotFoundError:
return PlainTextResponse("no log file yet\n")
log("minimal_openenv_app module import end")