File size: 4,088 Bytes
8485798
2785b89
 
2ce1061
 
ceba2ab
2ce1061
 
ceba2ab
2ce1061
 
2785b89
2ce1061
 
 
8485798
2ce1061
 
 
8485798
2ce1061
 
ceba2ab
 
2ce1061
8485798
2ce1061
 
 
 
 
 
 
 
 
 
 
2785b89
 
8485798
2785b89
8485798
2785b89
 
 
8485798
 
 
 
 
 
2785b89
 
8485798
2785b89
 
 
2ce1061
 
8485798
2ce1061
8485798
 
2ce1061
 
 
 
 
 
8485798
2ce1061
 
 
8485798
 
 
d298b6d
2785b89
8485798
 
 
 
 
2785b89
2ce1061
 
 
 
 
 
8485798
2ce1061
8485798
2ce1061
 
 
 
 
 
8485798
2ce1061
 
 
 
8485798
2ce1061
8485798
 
2ce1061
0944271
8485798
e688c16
ceba2ab
e688c16
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
# 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


@app.get("/", response_class=HTMLResponse)
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()


@app.get("/favicon.ico", include_in_schema=False)
async def favicon():
    from fastapi.responses import Response
    return Response(content=b"", media_type="image/x-icon")


@app.get("/health")
async def health():
    """Health check — must return 200 for submission validation."""
    return {"status": "ok", "environment": "code-debug-env", "version": "1.0.0"}


@app.post("/reset")
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)}")


@app.post("/step")
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)}")


@app.get("/state")
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)}")


@app.get("/tasks")
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()