Spaces:
Sleeping
Sleeping
| """ | |
| FastAPI server — Support Ticket Triage OpenEnv | |
| ================================================ | |
| Exposes the standard OpenEnv HTTP API: | |
| GET / → healthcheck + metadata | |
| POST /reset → start episode, returns observation | |
| POST /step → apply action, returns (obs, reward, done, info) | |
| GET /state → full internal state (with ground truth) | |
| GET /tasks → list all tasks | |
| """ | |
| from __future__ import annotations | |
| import uuid | |
| from typing import Any, Dict, Optional | |
| from fastapi import FastAPI, HTTPException | |
| from fastapi.responses import HTMLResponse | |
| from fastapi.middleware.cors import CORSMiddleware | |
| from pydantic import BaseModel | |
| import os | |
| from env.environment import TicketTriageEnv | |
| from env.models import ActionType, Department, TicketAction, UrgencyLevel | |
| app = FastAPI( | |
| title="Support Ticket Triage — OpenEnv", | |
| description=( | |
| "An OpenEnv environment where AI agents must triage, route, and resolve " | |
| "customer support tickets. Real-world task with 3 difficulty levels." | |
| ), | |
| version="1.0.0", | |
| ) | |
| app.add_middleware( | |
| CORSMiddleware, | |
| allow_origins=["*"], | |
| allow_methods=["*"], | |
| allow_headers=["*"], | |
| ) | |
| # --------------------------------------------------------------------------- | |
| # In-memory session store (keyed by session_id) | |
| # --------------------------------------------------------------------------- | |
| _sessions: Dict[str, TicketTriageEnv] = {} | |
| def _get_env(session_id: str) -> TicketTriageEnv: | |
| env = _sessions.get(session_id) | |
| if env is None: | |
| raise HTTPException(status_code=404, detail=f"Session '{session_id}' not found.") | |
| return env | |
| # --------------------------------------------------------------------------- | |
| # Request / response schemas | |
| # --------------------------------------------------------------------------- | |
| class ResetRequest(BaseModel): | |
| task_name: str = "route" | |
| ticket_id: Optional[str] = None | |
| seed: Optional[int] = 42 | |
| session_id: Optional[str] = None | |
| class StepRequest(BaseModel): | |
| session_id: str | |
| action_type: str | |
| department: Optional[str] = None | |
| response_text: Optional[str] = None | |
| urgency: Optional[str] = None | |
| tags: Optional[list[str]] = None | |
| escalation_reason: Optional[str] = None | |
| resolution_note: Optional[str] = None | |
| class StepResponse(BaseModel): | |
| observation: Dict[str, Any] | |
| reward: Dict[str, Any] | |
| done: bool | |
| info: Dict[str, Any] | |
| session_id: str | |
| # --------------------------------------------------------------------------- | |
| # Routes | |
| # --------------------------------------------------------------------------- | |
| def healthcheck(): | |
| return { | |
| "status": "ok", | |
| "environment": "Support Ticket Triage", | |
| "version": "1.0.0", | |
| "tasks": TicketTriageEnv.list_tasks(), | |
| } | |
| def list_tasks(): | |
| return {"tasks": TicketTriageEnv.list_tasks()} | |
| def reset(req: ResetRequest): | |
| session_id = req.session_id or str(uuid.uuid4()) | |
| env = TicketTriageEnv( | |
| task_name=req.task_name, | |
| ticket_id=req.ticket_id, | |
| seed=req.seed, | |
| ) | |
| _sessions[session_id] = env | |
| obs = env.reset() | |
| return {"observation": obs.model_dump(), "session_id": session_id} | |
| def step(req: StepRequest): | |
| env = _get_env(req.session_id) | |
| # Parse action | |
| try: | |
| action_type = ActionType(req.action_type) | |
| except ValueError: | |
| raise HTTPException( | |
| status_code=422, | |
| detail=f"Invalid action_type '{req.action_type}'. " | |
| f"Valid: {[a.value for a in ActionType]}", | |
| ) | |
| dept = None | |
| if req.department: | |
| try: | |
| dept = Department(req.department) | |
| except ValueError: | |
| raise HTTPException( | |
| status_code=422, | |
| detail=f"Invalid department '{req.department}'.", | |
| ) | |
| urg = None | |
| if req.urgency: | |
| try: | |
| urg = UrgencyLevel(req.urgency) | |
| except ValueError: | |
| raise HTTPException( | |
| status_code=422, | |
| detail=f"Invalid urgency '{req.urgency}'.", | |
| ) | |
| action = TicketAction( | |
| action_type=action_type, | |
| department=dept, | |
| response_text=req.response_text, | |
| urgency=urg, | |
| tags=req.tags, | |
| escalation_reason=req.escalation_reason, | |
| resolution_note=req.resolution_note, | |
| ) | |
| obs, reward, done, info = env.step(action) | |
| if done: | |
| # Clean up session after episode ends | |
| _sessions.pop(req.session_id, None) | |
| return StepResponse( | |
| observation=obs.model_dump(), | |
| reward=reward.model_dump(), | |
| done=done, | |
| info=info, | |
| session_id=req.session_id, | |
| ) | |
| def state(session_id: str): | |
| env = _get_env(session_id) | |
| s = env.state() | |
| return s.model_dump() | |
| def get_ui(): | |
| html_path = os.path.join(os.path.dirname(__file__), "env", "ui.html") | |
| try: | |
| with open(html_path, "r") as f: | |
| content = f.read() | |
| return HTMLResponse(content=content, status_code=200) | |
| except Exception as e: | |
| raise HTTPException(status_code=500, detail=f"UI template load error: {e}") | |
| # --------------------------------------------------------------------------- | |
| # Entry point (for local dev) | |
| # --------------------------------------------------------------------------- | |
| if __name__ == "__main__": | |
| import uvicorn | |
| uvicorn.run(app, host="0.0.0.0", port=7860) | |