Spaces:
Sleeping
Sleeping
| """FastAPI server for TRACE environment.""" | |
| import os | |
| from fastapi import FastAPI, HTTPException, Body | |
| from fastapi.middleware.cors import CORSMiddleware | |
| from fastapi.responses import FileResponse, RedirectResponse | |
| from fastapi.staticfiles import StaticFiles | |
| from pydantic import ValidationError | |
| from trace.env import TraceEnv | |
| from trace.utils import generate_episode_id | |
| from trace.models import ( | |
| Observation, Action, StepResponse, ResetRequest, | |
| StateResponse, HealthResponse | |
| ) | |
| app = FastAPI( | |
| title="TRACE", | |
| version="0.1.0", | |
| description="OpenEnv-compatible incident response environment" | |
| ) | |
| # Add CORS middleware | |
| app.add_middleware( | |
| CORSMiddleware, | |
| allow_origins=["*"], | |
| allow_credentials=True, | |
| allow_methods=["*"], | |
| allow_headers=["*"], | |
| ) | |
| # Mount Gradio UI alongside FastAPI so both share port 7860 on HF Spaces | |
| try: | |
| import gradio as gr | |
| import importlib.util | |
| _ui_path = os.path.join(os.path.dirname(os.path.dirname(__file__)), "ui.py") | |
| if os.path.exists(_ui_path): | |
| spec = importlib.util.spec_from_file_location("ui", _ui_path) | |
| ui_mod = importlib.util.module_from_spec(spec) | |
| spec.loader.exec_module(ui_mod) | |
| app = gr.mount_gradio_app(app, ui_mod.demo, path="/ui") | |
| print("[INFO] Gradio UI mounted at /ui", flush=True) | |
| except Exception as e: | |
| print(f"[INFO] Gradio UI not mounted: {e}", flush=True) | |
| # Global environment instance | |
| env = TraceEnv() | |
| current_task_id: str = None | |
| async def root(): | |
| """Serve the HTML UI.""" | |
| index_path = os.path.join(os.path.dirname(os.path.dirname(__file__)), "index.html") | |
| if os.path.exists(index_path): | |
| return FileResponse(index_path, media_type="text/html") | |
| return {"message": "TRACE v1 API is running. Use /docs for API docs."} | |
| async def index(): | |
| """Serve the HTML UI at /index.html.""" | |
| index_path = os.path.join(os.path.dirname(os.path.dirname(__file__)), "index.html") | |
| if os.path.exists(index_path): | |
| return FileResponse(index_path, media_type="text/html") | |
| raise HTTPException(status_code=404, detail="index.html not found") | |
| async def reset_endpoint(request: dict = Body(default={})) -> dict: | |
| """Reset environment for new episode.""" | |
| global current_task_id | |
| # Use defaults if request is empty | |
| task_id = request.get("task_id", "easy_cpu_spike") | |
| seed = int(request.get("seed", 0)) | |
| try: | |
| obs = env.reset(task_id=task_id, seed=seed) | |
| current_task_id = task_id | |
| return { | |
| "observation": obs.model_dump(), | |
| "info": { | |
| "task_id": task_id, | |
| "episode_id": generate_episode_id(), | |
| "max_steps": { | |
| "easy_cpu_spike": 5, | |
| "medium_cascade": 7, | |
| "hard_mixed": 8, | |
| }[task_id], | |
| } | |
| } | |
| except Exception as e: | |
| raise HTTPException(status_code=400, detail=str(e)) | |
| async def step_endpoint(request: dict) -> dict: | |
| """Execute one step.""" | |
| try: | |
| action_data = request.get("action", {}) | |
| action = Action( | |
| action_type=action_data.get("action_type"), | |
| target=action_data.get("target"), | |
| value=action_data.get("value") | |
| ) | |
| obs, reward, done, info = env.step(action) | |
| return { | |
| "observation": obs.model_dump(), | |
| "reward": float(reward), | |
| "done": done, | |
| "info": { | |
| **info, | |
| "message": f"Action {action.action_type} executed" | |
| } | |
| } | |
| except ValidationError as e: | |
| raise HTTPException(status_code=400, detail=str(e)) | |
| except Exception as e: | |
| raise HTTPException(status_code=400, detail=str(e)) | |
| async def state_endpoint() -> StateResponse: | |
| """Get current state.""" | |
| try: | |
| state = env.state() | |
| return StateResponse( | |
| observation=state["observation"], | |
| episode_reward=state["episode_reward"], | |
| steps=state["steps"], | |
| done=state["done"] | |
| ) | |
| except Exception as e: | |
| raise HTTPException(status_code=400, detail=str(e)) | |
| async def health_endpoint() -> HealthResponse: | |
| """Health check.""" | |
| return HealthResponse( | |
| status="healthy", | |
| version="0.1.0" | |
| ) | |
| def main(): | |
| import uvicorn | |
| port = int(os.getenv("PORT", 7860)) | |
| uvicorn.run(app, host="0.0.0.0", port=port) | |
| if __name__ == "__main__": | |
| main() |