Meta-SRE / app /main.py
Anvit25's picture
Deploy Meta-SRE OpenEnv benchmark FastAPI server
ad6248e
"""
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 setup
# ---------------------------------------------------------------------------
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=["*"],
)
# Single global episode manager (stateful server)
_dc = DifficultyController()
_episode = EpisodeManager(difficulty_controller=_dc)
_started = False
# ---------------------------------------------------------------------------
# Request / response models
# ---------------------------------------------------------------------------
class ResetRequest(BaseModel):
task_id: Optional[int] = None # 1-5; None = difficulty-controller picks
class StepRequest(BaseModel):
tool: str
params: Dict[str, Any] = {}
# ---------------------------------------------------------------------------
# Routes
# ---------------------------------------------------------------------------
@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()
# ---------------------------------------------------------------------------
# OpenEnv compatibility shim (env.reset / env.step / env.grade)
# ---------------------------------------------------------------------------
@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),
},
}
# ---------------------------------------------------------------------------
# Dev entry point
# ---------------------------------------------------------------------------
if __name__ == "__main__":
import uvicorn
uvicorn.run("app.main:app", host="0.0.0.0", port=8000, reload=True)