it-support-triage / server.py
kevanthonyP's picture
Create server.py
d860a1c verified
raw
history blame
3.65 kB
"""
server.py β€” FastAPI server exposing the OpenEnv HTTP API.
Endpoints:
POST /reset Reset environment, begin new episode
POST /step Submit a triage action, get StepResult
GET /state Get current environment state
GET /tasks List all available tasks
GET /health Health check
"""
import os
import traceback
from fastapi import FastAPI, HTTPException, Body
from fastapi.middleware.cors import CORSMiddleware
from pydantic import BaseModel, ValidationError
from typing import Optional
from env_models import TriageAction, StepResult, EnvState, TicketObservation
from env_core import ITSupportEnv
app = FastAPI(
title="IT Support Triage β€” OpenEnv",
description=(
"An OpenEnv-compliant RL environment for training and evaluating agents "
"on IT helpdesk ticket triage tasks. Includes 3 tasks (easy β†’ medium β†’ hard) "
"with deterministic graders and safety-aware reward functions."
),
version="1.0.0",
)
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_methods=["*"],
allow_headers=["*"],
)
# One environment instance per server (stateful)
env = ITSupportEnv()
# ─── Request schemas ──────────────────────────────────────────────────────────
class ResetRequest(BaseModel):
task_id: Optional[str] = "task_easy"
class StepRequest(BaseModel):
action: TriageAction
# ─── Endpoints ────────────────────────────────────────────────────────────────
@app.get("/health")
def health():
return {"status": "ok", "environment": "it-support-triage", "version": "1.0.0"}
@app.post("/reset", response_model=TicketObservation)
async def reset(request: Optional[ResetRequest] = Body(default=None)):
"""
Reset the environment and return the initial observation (ticket).
Pass task_id to select difficulty: task_easy | task_medium | task_hard
"""
task_id = (request.task_id if request else None) or "task_easy"
try:
obs = env.reset(task_id)
return obs
except ValueError as e:
raise HTTPException(status_code=400, detail=str(e))
except Exception as e:
raise HTTPException(status_code=500, detail=traceback.format_exc())
@app.post("/step", response_model=StepResult)
def step(request: StepRequest):
"""
Submit a TriageAction and receive a StepResult with reward and grader breakdown.
"""
try:
result = env.step(request.action)
return result
except RuntimeError as e:
raise HTTPException(status_code=400, detail=str(e))
except ValidationError as e:
raise HTTPException(status_code=422, detail=str(e))
except Exception as e:
raise HTTPException(status_code=500, detail=traceback.format_exc())
@app.get("/state", response_model=EnvState)
def state():
"""Return the full current environment state."""
return env.state()
@app.get("/tasks")
def list_tasks():
"""List all available tasks with metadata."""
return {"tasks": env.list_tasks()}
# ─── Entry point ──────────────────────────────────────────────────────────────
if __name__ == "__main__":
import uvicorn
port = int(os.environ.get("PORT", 7860))
uvicorn.run("server:app", host="0.0.0.0", port=port, reload=False)