Spaces:
Running
Running
| # server/app.py — FastAPI server for Code Debug Environment | |
| # Port 7860 required for Hugging Face Spaces. | |
| from fastapi import FastAPI, HTTPException | |
| from fastapi.middleware.cors import CORSMiddleware | |
| from fastapi.responses import HTMLResponse | |
| from typing import Optional | |
| from pydantic import BaseModel | |
| import os | |
| from server.environment import CodeDebugEnvironment | |
| from models import DebugAction, DebugObservation, DebugState | |
| app = FastAPI( | |
| title="Code Debug Environment", | |
| description="OpenEnv RL environment where LLM agents fix buggy Python code. 3 difficulty levels: easy, medium, hard.", | |
| version="1.0.0", | |
| ) | |
| app.add_middleware(CORSMiddleware, allow_origins=["*"], allow_methods=["*"], allow_headers=["*"]) | |
| env = CodeDebugEnvironment() | |
| class ResetRequest(BaseModel): | |
| difficulty: Optional[str] = None | |
| class StepRequest(BaseModel): | |
| fixed_code: str | |
| explanation: Optional[str] = None | |
| class StepResponse(BaseModel): | |
| observation: dict | |
| reward: float | |
| done: bool | |
| async def root(): | |
| """Homepage with live interactive tester.""" | |
| html_path = os.path.join(os.path.dirname(__file__), "static", "index.html") | |
| with open(html_path, "r", encoding="utf-8") as f: | |
| return f.read() | |
| async def favicon(): | |
| from fastapi.responses import Response | |
| return Response(content=b"", media_type="image/x-icon") | |
| async def health(): | |
| """Health check — must return 200 for submission validation.""" | |
| return {"status": "ok", "environment": "code-debug-env", "version": "1.0.0"} | |
| async def reset(request: ResetRequest = ResetRequest()) -> dict: | |
| """Reset environment to start a new episode. Pass difficulty: easy | medium | hard""" | |
| try: | |
| obs = env.reset(difficulty=request.difficulty) | |
| return {"observation": obs.model_dump(), "reward": 0.0, "done": False} | |
| except Exception as e: | |
| raise HTTPException(status_code=500, detail=f"Reset failed: {str(e)}") | |
| async def step(request: StepRequest) -> StepResponse: | |
| """Submit fixed code. Returns reward (0.0-1.0), feedback, done flag.""" | |
| if not request.fixed_code or not request.fixed_code.strip(): | |
| raise HTTPException(status_code=400, detail="fixed_code must not be empty.") | |
| try: | |
| action = DebugAction(fixed_code=request.fixed_code, explanation=request.explanation) | |
| obs = env.step(action) | |
| return StepResponse(observation=obs.model_dump(), reward=obs.reward or 0.0, done=obs.done) | |
| except TimeoutError: | |
| return StepResponse( | |
| observation={"task_id": "unknown", "difficulty": "unknown", "buggy_code": "", | |
| "instructions": "", "test_cases_description": "", "reward": 0.0, | |
| "passed_tests": 0, "total_tests": 3, "done": False, | |
| "feedback": "TimeoutError: Infinite loop detected. Add a visited set for graph traversal."}, | |
| reward=0.0, done=False, | |
| ) | |
| except Exception as e: | |
| raise HTTPException(status_code=500, detail=f"Step failed: {str(e)}") | |
| async def state() -> dict: | |
| """Return current episode state.""" | |
| try: | |
| return env.state.model_dump() | |
| except Exception as e: | |
| raise HTTPException(status_code=500, detail=f"State failed: {str(e)}") | |
| async def list_tasks() -> dict: | |
| """List all 45 task IDs across difficulty levels.""" | |
| from server.tasks.task_easy import EASY_TASKS | |
| from server.tasks.task_medium import MEDIUM_TASKS | |
| from server.tasks.task_hard import HARD_TASKS | |
| return { | |
| "easy": [t["task_id"] for t in EASY_TASKS], | |
| "medium": [t["task_id"] for t in MEDIUM_TASKS], | |
| "hard": [t["task_id"] for t in HARD_TASKS], | |
| "total": len(EASY_TASKS) + len(MEDIUM_TASKS) + len(HARD_TASKS), | |
| } | |
| def main(): | |
| import uvicorn | |
| uvicorn.run("server.app:app", host="0.0.0.0", port=7860, reload=False) | |
| if __name__ == "__main__": | |
| main() | |