| """ |
| Meta-SRE FastAPI Server – OpenEnv Standard API. |
| |
| Implements the OpenEnv contract exactly: |
| POST /reset → Observation |
| POST /step → (observation, reward, done, info) |
| GET /state → Observation |
| GET /grade → EpisodeResult |
| GET /tools → tool specs (JSON Schema per tool) |
| GET /tasks → task definitions |
| GET /health → liveness probe |
| |
| The /env/* routes are strict OpenEnv aliases used by openenv_client.connect(). |
| """ |
| from __future__ import annotations |
|
|
| from fastapi import FastAPI, HTTPException |
| from fastapi.middleware.cors import CORSMiddleware |
| from pydantic import BaseModel |
| from typing import Any, Dict, Optional |
|
|
| from app.engine.manager import EpisodeManager, TASK_DEFINITIONS, DifficultyController |
| from app.models import Observation, ActionResult, EpisodeResult |
| from app.tools.definitions import TOOL_SPECS |
|
|
|
|
| |
| |
| |
|
|
| app = FastAPI( |
| title="Meta-SRE", |
| description=( |
| "OpenEnv environment: train LLM agents to act as Senior Site Reliability Engineers " |
| "debugging realistic Meta production incidents." |
| ), |
| version="1.0.0", |
| ) |
|
|
| app.add_middleware( |
| CORSMiddleware, |
| allow_origins=["*"], |
| allow_methods=["*"], |
| allow_headers=["*"], |
| ) |
|
|
| |
| _dc = DifficultyController() |
| _episode = EpisodeManager(difficulty_controller=_dc) |
| _started = False |
|
|
|
|
| |
| |
| |
|
|
| class ResetRequest(BaseModel): |
| task_id: Optional[int] = None |
|
|
|
|
| class StepRequest(BaseModel): |
| tool: str |
| params: Dict[str, Any] = {} |
|
|
|
|
| |
| |
| |
|
|
| @app.get("/health") |
| def health(): |
| return {"status": "ok", "version": "1.0.0"} |
|
|
|
|
| @app.get("/tools") |
| def list_tools(): |
| return {"tools": TOOL_SPECS, "count": len(TOOL_SPECS)} |
|
|
|
|
| @app.get("/tasks") |
| def list_tasks(): |
| return {"tasks": TASK_DEFINITIONS} |
|
|
|
|
| @app.post("/reset", response_model=Observation) |
| def reset(req: ResetRequest = ResetRequest()): |
| global _started |
| _started = True |
| obs = _episode.reset(task_id=req.task_id) |
| return obs |
|
|
|
|
| @app.post("/step", response_model=ActionResult) |
| def step(req: StepRequest): |
| global _started |
| if not _started: |
| raise HTTPException( |
| status_code=400, |
| detail="Episode not started. Call POST /reset first." |
| ) |
| try: |
| result = _episode.step(tool=req.tool, params=req.params) |
| return result |
| except RuntimeError as e: |
| raise HTTPException(status_code=400, detail=str(e)) |
|
|
|
|
| @app.get("/state", response_model=Observation) |
| def get_state(): |
| if not _started: |
| raise HTTPException( |
| status_code=400, |
| detail="Episode not started. Call POST /reset first." |
| ) |
| return _episode._build_observation() |
|
|
|
|
| @app.get("/grade", response_model=EpisodeResult) |
| def grade(): |
| if not _started: |
| raise HTTPException( |
| status_code=400, |
| detail="Episode not started. Call POST /reset first." |
| ) |
| return _episode.get_episode_result() |
|
|
|
|
| |
| |
| |
|
|
| @app.post("/env/reset") |
| def env_reset(req: ResetRequest = ResetRequest()): |
| """OpenEnv spec alias for /reset.""" |
| return reset(req) |
|
|
|
|
| @app.post("/env/step") |
| def env_step(req: StepRequest): |
| """ |
| OpenEnv standard step — returns the canonical 4-tuple: |
| (observation, reward, done, info) |
| This is what openenv_client.connect().step() unpacks. |
| """ |
| result: ActionResult = step(req) |
| return { |
| "observation": result.observation, |
| "reward": result.reward_delta, |
| "done": result.done, |
| "info": { |
| "tool": result.tool, |
| "output": result.output, |
| "episode_id": _episode._incident_id, |
| "step": _episode._step, |
| "budget_remaining": max(0, _episode._build_observation().budget_remaining), |
| }, |
| } |
|
|
|
|
| |
| |
| |
|
|
| if __name__ == "__main__": |
| import uvicorn |
| uvicorn.run("app.main:app", host="0.0.0.0", port=8000, reload=True) |
|
|