AMD21/codefixerenv
0
1"""2server.py — FastAPI REST server for CodeFixerEnv.3 4Endpoints5---------6GET /health — liveness check7POST /reset — start a new episode8POST /step — submit an action9GET /state — inspect internal env state10GET /tasks — list all available tasks11 12All responses use Pydantic models for type-safe serialisation.13Hugging Face Spaces expects the server on port 7860.14"""15 16import os17import sys18sys.path.insert(0, os.path.dirname(__file__))19 20from fastapi import FastAPI, HTTPException21from fastapi.middleware.cors import CORSMiddleware22from pydantic import BaseModel23from typing import Optional24import uvicorn25 26from env.environment import CodeFixerEnv27from env.models import Action, Observation, Reward, StepResult28from tasks.tasks import TASKS29 30# ── App setup ─────────────────────────────────────────────────────────────────31app = FastAPI(32 title="CodeFixerEnv",33 description=(34 "OpenEnv environment for code-debugging RL tasks. "35 "An agent receives buggy Python code and must submit corrected solutions."36 ),37 version="1.0.0",38 docs_url="/docs",39 redoc_url="/redoc",40)41 42app.add_middleware(43 CORSMiddleware,44 allow_origins=["*"],45 allow_methods=["*"],46 allow_headers=["*"],47)48 49# Single shared environment instance (one session at a time)50env = CodeFixerEnv()51 52 53# ── Request/response schemas ──────────────────────────────────────────────────54class ResetRequest(BaseModel):55 difficulty: Optional[str] = "easy"56 57 58class StepRequest(BaseModel):59 action_type: str60 action_content: str61 62 63class StepResponse(BaseModel):64 observation: Observation65 reward: Reward66 done: bool67 info: dict68 69 70class TaskInfo(BaseModel):71 id: str72 difficulty: str73 context: str74 75 76# ── Endpoints ─────────────────────────────────────────────────────────────────77@app.get("/health")78def health():79 """Liveness check — returns 200 if the server is up."""80 return {"status": "ok", "environment": "CodeFixerEnv", "version": "1.0.0"}81 82 83@app.post("/reset", response_model=Observation)84def reset(request: ResetRequest):85 """86 Start a new episode.87 88 Body: { "difficulty": "easy" | "medium" | "hard" }89 Returns the initial Observation.90 """91 try:92 obs = env.reset(difficulty=request.difficulty or "easy")93 return obs94 except ValueError as e:95 raise HTTPException(status_code=400, detail=str(e))96 97 98@app.post("/step", response_model=StepResponse)99def step(request: StepRequest):100 """101 Submit one action and advance the episode.102 103 Body: { "action_type": "fix"|"explain"|"give_up", "action_content": "..." }104 Returns observation, structured reward, done flag, and diagnostics.105 """106 try:107 action = Action(type=request.action_type, content=request.action_content)108 obs, reward, done, info = env.step(action)109 return StepResponse(observation=obs, reward=reward, done=done, info=info)110 except RuntimeError as e:111 raise HTTPException(status_code=400, detail=str(e))112 except Exception as e:113 raise HTTPException(status_code=500, detail=str(e))114 115 116@app.get("/state")117def state():118 """Return the current internal state of the environment (for debugging)."""119 return env.state()120 121 122@app.get("/tasks", response_model=list[TaskInfo])123def list_tasks():124 """List all available tasks with their IDs and descriptions."""125 return [126 TaskInfo(id=t["id"], difficulty=t["difficulty"], context=t["context"])127 for _, (t, _) in TASKS.items()128 ]129 130 131# ── Entry point ───────────────────────────────────────────────────────────────132if __name__ == "__main__":133 port = int(os.environ.get("PORT", 7860))134 uvicorn.run(app, host="0.0.0.0", port=port)135 136 137def main():138 import uvicorn139 port = int(os.environ.get("PORT", 7860))140 uvicorn.run(app, host="0.0.0.0", port=port)141 142if __name__ == "__main__":143 main()144 