sdipans1's picture
chore: add playground
91634f7
Raw History Blame Contribute Delete
6.34 kB
"""
FastAPI application entry point.
Uses openenv.core.env_server.create_fastapi_app to wire up the standard
OpenEnv endpoints: /reset, /step, /state, /schema, /health, /metadata, /ws
"""
from __future__ import annotations
try:
from openenv.core.env_server import create_fastapi_app # type: ignore
from openenv.core.env_server.types import ConcurrencyConfig # type: ignore
from ..models import ModAction, ModObservation
from .reddit_mod_environment import RedditModEnvironment
app = create_fastapi_app(
RedditModEnvironment,
action_cls=ModAction,
observation_cls=ModObservation,
concurrency_config=ConcurrencyConfig(
max_concurrent_envs=20,
session_timeout=600.0, # 10 min β€” accommodates slow LLM calls between steps
),
)
import uuid as _uuid
from typing import Optional as _Opt
from fastapi import HTTPException, Request, Response
from fastapi.responses import HTMLResponse
from pydantic import BaseModel as _BaseModel
from ._playground import PLAYGROUND_HTML
# ── Playground session store (cookie-keyed) ──────────────────────────────
_pg_sessions: dict = {}
class _PGResetRequest(_BaseModel):
task_level: int = 1
num_posts: _Opt[int] = None
seed: _Opt[int] = None
@app.get("/", include_in_schema=False)
def root() -> HTMLResponse:
return HTMLResponse(PLAYGROUND_HTML)
@app.post("/pg/reset", include_in_schema=False)
def pg_reset(request: _PGResetRequest, response: Response) -> dict:
env = RedditModEnvironment()
kwargs: dict = {"task_level": request.task_level}
if request.num_posts is not None:
kwargs["num_posts"] = request.num_posts
obs = env.reset(seed=request.seed, **kwargs)
sid = str(_uuid.uuid4())
_pg_sessions[sid] = env
response.set_cookie("pg_sid", sid, max_age=3600, httponly=True)
return obs.model_dump()
@app.post("/pg/step", include_in_schema=False)
def pg_step(action: ModAction, request: Request) -> dict:
sid = request.cookies.get("pg_sid")
env = _pg_sessions.get(sid) if sid else None
if env is None:
raise HTTPException(400, "No active session β€” call /pg/reset first")
obs = env.step(action)
return obs.model_dump()
except ImportError:
# ── Fallback: standalone FastAPI server for local dev without openenv-core ──
from typing import Any, Dict, Optional
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from pydantic import BaseModel
from ..models import ModAction, ModObservation, ModState
from .reddit_mod_environment import RedditModEnvironment
app = FastAPI(
title="Reddit Mod Bot RL Environment",
description=(
"Train RL agents to moderate r/science posts. "
"Three difficulty levels from spam detection to context-aware moderation."
),
version="1.0.0",
)
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_methods=["*"],
allow_headers=["*"],
)
# Single shared environment instance (sufficient for dev/demo)
_env = RedditModEnvironment()
class ResetRequest(BaseModel):
task_level: int = 1
num_posts: Optional[int] = None
seed: Optional[int] = None
episode_id: Optional[str] = None
from fastapi import Request, Response
from fastapi.responses import HTMLResponse
from ._playground import PLAYGROUND_HTML
_pg_sessions_fb: dict = {}
@app.get("/", include_in_schema=False)
def root() -> HTMLResponse:
return HTMLResponse(PLAYGROUND_HTML)
@app.post("/pg/reset", include_in_schema=False)
def pg_reset_fb(request: ResetRequest, response: Response) -> dict:
import uuid as _uuid
env = RedditModEnvironment()
kwargs: Dict[str, Any] = {"task_level": request.task_level}
if request.num_posts is not None:
kwargs["num_posts"] = request.num_posts
obs = env.reset(seed=request.seed, **kwargs)
sid = str(_uuid.uuid4())
_pg_sessions_fb[sid] = env
response.set_cookie("pg_sid", sid, max_age=3600, httponly=True)
return obs.model_dump()
@app.post("/pg/step", include_in_schema=False)
def pg_step_fb(action: ModAction, request: Request) -> dict:
from fastapi import HTTPException
sid = request.cookies.get("pg_sid")
env = _pg_sessions_fb.get(sid) if sid else None
if env is None:
raise HTTPException(400, "No active session β€” call /pg/reset first")
return env.step(action).model_dump()
@app.get("/health")
def health() -> Dict[str, str]:
return {"status": "ok"}
@app.get("/metadata")
def metadata() -> Dict[str, Any]:
return {
"name": "reddit_mod_env",
"version": "1.0.0",
"description": "Reddit moderator bot RL environment for r/science",
"task_levels": [1, 2, 3],
"subreddit": "r/science",
}
@app.post("/reset", response_model=ModObservation)
def reset(request: ResetRequest) -> ModObservation:
kwargs: Dict[str, Any] = {"task_level": request.task_level}
if request.num_posts is not None:
kwargs["num_posts"] = request.num_posts
return _env.reset(
seed=request.seed,
episode_id=request.episode_id,
**kwargs,
)
@app.post("/step", response_model=ModObservation)
def step(action: ModAction) -> ModObservation:
return _env.step(action)
@app.get("/state", response_model=ModState)
def state() -> ModState:
return _env.state
@app.get("/schema")
def schema() -> Dict[str, Any]:
return {
"action": ModAction.model_json_schema(),
"observation": ModObservation.model_json_schema(),
"state": ModState.model_json_schema(),
}
def main() -> None:
"""Server entry point β€” callable by the openenv CLI and the installed script."""
import uvicorn
uvicorn.run(app, host="0.0.0.0", port=7860)
if __name__ == "__main__":
main()