Spaces:
Sleeping
Sleeping
| # server/app.py | |
| import sys | |
| import os | |
| sys.path.insert(0, "/app") | |
| sys.path.insert(0, "/app/server") | |
| from openenv.core.env_server import create_app | |
| from model import TriageAction, TriageObservation | |
| from environment import BugTriageEnvironment, SessionManager, TASKS_META | |
| from task import sample_bug, grade_action, TASKS | |
| from fastapi import Response, Request, HTTPException | |
| from fastapi.responses import FileResponse, JSONResponse | |
| from fastapi.staticfiles import StaticFiles | |
| from typing import Optional, Dict, Any | |
| app = create_app( | |
| BugTriageEnvironment, | |
| TriageAction, | |
| TriageObservation, | |
| env_name="bug-triage-env", | |
| ) | |
| # Session manager replaces the broken global state | |
| sessions = SessionManager(max_sessions=500, ttl_seconds=600) | |
| # Fallback env for backward-compatible (non-session) requests | |
| _fallback_env = BugTriageEnvironment() | |
| _fallback_answer = None | |
| # Remove default routes from create_app — we override them | |
| routes_to_remove = [] | |
| for route in app.routes: | |
| if hasattr(route, "path") and route.path in ("/reset", "/step", "/state"): | |
| routes_to_remove.append(route) | |
| for route in routes_to_remove: | |
| app.routes.remove(route) | |
| # --------------------------------------------------------------------------- | |
| # CORE ENDPOINTS | |
| # --------------------------------------------------------------------------- | |
| def health(): | |
| return { | |
| "status": "ok", | |
| "env": "bug-triage-env", | |
| "version": "2.0.0", | |
| "active_sessions": sessions.active_count, | |
| } | |
| def root(): | |
| """Serve the interactive demo frontend.""" | |
| static_dir = os.path.join(os.path.dirname(__file__), "static") | |
| index_path = os.path.join(static_dir, "index.html") | |
| if os.path.exists(index_path): | |
| return FileResponse(index_path) | |
| return {"message": "Bug Triage Environment v2.0.0", "docs": "/docs"} | |
| def web_ui(): | |
| """Alias for the frontend.""" | |
| return root() | |
| def list_tasks(): | |
| return TASKS_META | |
| def get_task(task_id: str): | |
| for t in TASKS_META: | |
| if t["id"] == task_id: | |
| return t | |
| raise HTTPException(404, detail={ | |
| "error": "task_not_found", | |
| "message": f"Task '{task_id}' not found. Valid: easy, medium, hard", | |
| }) | |
| # --------------------------------------------------------------------------- | |
| # SESSION-BASED RESET / STEP / STATE | |
| # --------------------------------------------------------------------------- | |
| async def custom_reset(request: Request): | |
| """Start a new episode. Returns a session_id for subsequent step() calls.""" | |
| global _fallback_env, _fallback_answer | |
| body = {} | |
| try: | |
| body = await request.json() | |
| except Exception: | |
| pass | |
| task_id = body.get("task_id", "easy") | |
| seed = body.get("seed", None) | |
| episode_id = body.get("episode_id", None) | |
| session_id = body.get("session_id", None) | |
| # If session_id provided, reuse that session | |
| if session_id: | |
| env = sessions.get_session(session_id) | |
| if env is None: | |
| session_id, env = sessions.create_session() | |
| else: | |
| session_id, env = sessions.create_session() | |
| obs = env.reset(task_id=task_id, seed=seed, episode_id=episode_id) | |
| # Also update fallback for backward compatibility | |
| _fallback_env = env | |
| try: | |
| obs_dict = obs.model_dump(exclude={"reward", "done", "metadata"}) | |
| except AttributeError: | |
| obs_dict = obs.dict() | |
| obs_dict.pop("reward", None) | |
| obs_dict.pop("done", None) | |
| obs_dict.pop("metadata", None) | |
| return { | |
| "session_id": session_id, | |
| "observation": obs_dict, | |
| "reward": 0.0, | |
| "done": False, | |
| } | |
| async def custom_step(request: Request): | |
| """Process an action — either investigation or final triage submission.""" | |
| global _fallback_env | |
| body = await request.json() | |
| action_data = body.get("action", body) | |
| session_id = body.get("session_id", None) | |
| # Find the right environment | |
| env = None | |
| if session_id: | |
| env = sessions.get_session(session_id) | |
| if env is None: | |
| env = _fallback_env | |
| action = TriageAction( | |
| action_type=action_data.get("action_type", "submit"), | |
| priority=action_data.get("priority", "P2"), | |
| labels=action_data.get("labels", ["bug"]), | |
| assigned_team=action_data.get("assigned_team", "backend"), | |
| milestone=action_data.get("milestone", "backlog"), | |
| reasoning=action_data.get("reasoning", ""), | |
| ) | |
| obs = env.step(action) | |
| try: | |
| obs_dict = obs.model_dump(exclude={"reward", "done", "metadata"}) | |
| except AttributeError: | |
| obs_dict = obs.dict() | |
| obs_dict.pop("reward", None) | |
| obs_dict.pop("done", None) | |
| obs_dict.pop("metadata", None) | |
| reward = float(obs.reward) if obs.reward is not None else 0.0 | |
| reward = max(0.01, min(0.99, reward)) if obs.done else 0.0 | |
| response_data = { | |
| "observation": obs_dict, | |
| "reward": reward, | |
| "done": obs.done, | |
| } | |
| if session_id: | |
| response_data["session_id"] = session_id | |
| # Cleanup session when episode is done | |
| if obs.done and session_id: | |
| sessions.remove_session(session_id) | |
| return response_data | |
| def custom_state(session_id: Optional[str] = None): | |
| """Return current environment state.""" | |
| env = None | |
| if session_id: | |
| env = sessions.get_session(session_id) | |
| if env is None: | |
| env = _fallback_env | |
| state = env.get_state() | |
| try: | |
| return state.model_dump() | |
| except AttributeError: | |
| return state.dict() | |
| # --------------------------------------------------------------------------- | |
| # PER-TASK SHORTCUT ENDPOINTS | |
| # --------------------------------------------------------------------------- | |
| async def reset_easy(): | |
| session_id, env = sessions.create_session() | |
| obs = env.reset(task_id="easy") | |
| return { | |
| "session_id": session_id, | |
| "task_id": "easy", | |
| "bug_report": obs.bug_report.model_dump(), | |
| "done": False, | |
| "reward": 0.0, | |
| } | |
| async def reset_medium(): | |
| session_id, env = sessions.create_session() | |
| obs = env.reset(task_id="medium") | |
| return { | |
| "session_id": session_id, | |
| "task_id": "medium", | |
| "bug_report": obs.bug_report.model_dump(), | |
| "done": False, | |
| "reward": 0.0, | |
| } | |
| async def reset_hard(): | |
| session_id, env = sessions.create_session() | |
| obs = env.reset(task_id="hard") | |
| return { | |
| "session_id": session_id, | |
| "task_id": "hard", | |
| "bug_report": obs.bug_report.model_dump(), | |
| "done": False, | |
| "reward": 0.0, | |
| } | |
| # --------------------------------------------------------------------------- | |
| # LEADERBOARD | |
| # --------------------------------------------------------------------------- | |
| _leaderboard = [] | |
| def get_leaderboard(): | |
| """Return top 50 agent scores.""" | |
| return sorted(_leaderboard, key=lambda x: x.get("avg_score", 0), reverse=True)[:50] | |
| async def submit_to_leaderboard(request: Request): | |
| """Submit agent scores to the leaderboard.""" | |
| body = await request.json() | |
| entry = { | |
| "agent_name": body.get("agent_name", "anonymous"), | |
| "model": body.get("model", "unknown"), | |
| "scores": body.get("scores", {}), | |
| "avg_score": body.get("avg_score", 0.0), | |
| } | |
| _leaderboard.append(entry) | |
| rank = sorted( | |
| _leaderboard, key=lambda x: x.get("avg_score", 0), reverse=True | |
| ).index(entry) + 1 | |
| return {"status": "submitted", "rank": rank, "total_entries": len(_leaderboard)} | |
| # --------------------------------------------------------------------------- | |
| # ENTRYPOINT | |
| # --------------------------------------------------------------------------- | |
| def main(): | |
| import uvicorn | |
| uvicorn.run(app, host="0.0.0.0", port=7860) | |
| if __name__ == "__main__": | |
| main() |