open_env / app.py
Ramachandra Dayal K
Use linear transformation to strictly exclude 0.0 and 1.0 from score
9de2ed4
Raw
History Blame Contribute Delete
4.99 kB
from fastapi import FastAPI, HTTPException
from fastapi.responses import HTMLResponse
from fastapi.staticfiles import StaticFiles
from pydantic import BaseModel
from typing import Dict, Any
import sys
import os
sys.path.append(os.path.dirname(os.path.abspath(__file__)))
from models import Action, Observation, State
from server.llm_env import LLMEnv
app = FastAPI(title="LLM Control OpenEnv")
# Global in-memory store for environments and session state
envs: Dict[str, LLMEnv] = {}
completed_episodes: Dict[str, Dict[str, Any]] = {}
# Default global environment initialized with a reset state
default_env = LLMEnv()
default_env.reset()
class ResetRequest(BaseModel):
task: str = "easy"
class StepRequest(BaseModel):
action: Action
episode_id: str | None = None
class GraderRequest(BaseModel):
episode_id: str
# Default global environment to satisfy simple paths
default_env = LLMEnv()
@app.get("/", response_class=HTMLResponse)
async def serve_gui():
path = os.path.join(os.path.dirname(__file__), "index.html")
try:
with open(path, "r") as f:
return f.read()
except FileNotFoundError:
return "GUI index.html not found. Check the root directory."
@app.post("/reset")
async def reset(req: ResetRequest = ResetRequest()):
if req.task not in ["easy", "medium", "hard"]:
raise HTTPException(status_code=400, detail="Invalid task")
env = LLMEnv(task=req.task)
obs = env.reset()
state = env.state
envs[state.episode_id] = env
# Also set default env to the latest reset for easy single-agent testing
global default_env
default_env = env
return {
"observation": obs.model_dump(),
"state": state.model_dump()
}
@app.post("/step")
async def step(req: StepRequest):
# Retrieve env
env = default_env
if req.episode_id and req.episode_id in envs:
env = envs[req.episode_id]
obs, reward, done, info = env.step(req.action)
if done:
# Save cumulative reward for grading
completed_episodes[env.state.episode_id] = {
"reward": env.state.cumulative_reward,
"bounds": env._reward_bounds()
}
return {
"observation": obs.model_dump(),
"reward": reward,
"done": done,
"info": info
}
@app.get("/state", response_model=State)
async def get_state(episode_id: str | None = None):
env = default_env
if episode_id and episode_id in envs:
env = envs[episode_id]
return env.state
@app.post("/baseline")
async def run_baseline():
import subprocess
try:
# baseline.py should be in the directory above server
baseline_path = os.path.join(os.path.dirname(__file__), "baseline.py")
result = subprocess.run([sys.executable, baseline_path], capture_output=True, text=True, check=True)
# Parse the output to return the dict
# We expect JSON or eval-able output from baseline, or simply look at the final prints
# But this implies we should structure baseline.py to just run the tasks
# Or we can just run the baseline logic directly here if we want API.
# For safety, let's just execute it and return the raw output or parse a standard format.
# We'll just run our logic from baseline script here directly if the subprocess is too complex,
# but the prompt says POST /baseline runs baseline.py, so we will return stdout.
# Actually, let's try to extract JSON from the stdout.
import json
out = result.stdout.strip().splitlines()[-1]
# assume last line is valid JSON dict
scores = json.loads(out)
return scores
except subprocess.CalledProcessError as e:
raise HTTPException(status_code=500, detail=f"Baseline failed: {e.stderr}")
except Exception as e:
raise HTTPException(status_code=500, detail=f"Baseline error: {str(e)}")
@app.post("/grader")
async def grader(req: GraderRequest):
if req.episode_id not in completed_episodes:
# Check active envs
if req.episode_id in envs:
env = envs[req.episode_id]
r = env.state.cumulative_reward
b_min, b_max = env._reward_bounds()
score = (norm * 0.998) + 0.001
return {"score": score}
raise HTTPException(status_code=404, detail="Episode not found or not finished")
data = completed_episodes[req.episode_id]
r = data["reward"]
b_min, b_max = data["bounds"]
norm = (r - b_min) / (b_max - b_min)
# Smoothly scale to strictly (0, 1) to avoid manual edge limits
score = (norm * 0.998) + 0.001
return {"score": score}
@app.get("/tasks")
async def get_tasks():
return {
"tasks": ["easy", "medium", "hard"],
"action_schema": Action.model_json_schema()
}
if __name__ == "__main__":
import uvicorn
uvicorn.run(app, host="0.0.0.0", port=7860)