File size: 3,141 Bytes
60df783
32951ca
60df783
 
32951ca
 
 
 
ab6a3f1
32951ca
 
60df783
d860a1c
 
60df783
32951ca
 
 
 
 
 
 
 
 
60df783
32951ca
 
 
 
 
 
60df783
32951ca
60df783
 
32951ca
 
60df783
8249911
32951ca
 
8249911
60df783
32951ca
60df783
ab6a3f1
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
32951ca
 
 
60df783
 
32951ca
 
 
60df783
32951ca
 
60df783
 
 
32951ca
60df783
 
32951ca
 
60df783
32951ca
 
60df783
 
32951ca
 
60df783
32951ca
60df783
 
32951ca
 
 
60df783
32951ca
 
 
 
60df783
 
 
 
32951ca
d860a1c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
"""
server.py β€” FastAPI server exposing the OpenEnv HTTP API.
"""

import os
import traceback
from fastapi import FastAPI, HTTPException, Body
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import JSONResponse
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=["*"],
)

env = ITSupportEnv()


class ResetRequest(BaseModel):
    task_id: Optional[str] = "task_easy"


class StepRequest(BaseModel):
    action: TriageAction


# ─── Endpoints ────────────────────────────────────────────────────────────────

@app.get("/")
def root():
    return {
        "environment": "it-support-triage",
        "version": "1.0.0",
        "status": "ok",
        "endpoints": {
            "POST /reset": "Start episode. Body: {task_id: task_easy|task_medium|task_hard}",
            "POST /step":  "Submit action. Body: {action: {...}}",
            "GET  /state": "Current environment state",
            "GET  /tasks": "List all tasks",
            "GET  /health": "Health check",
            "GET  /docs":  "Interactive API docs (Swagger UI)",
        }
    }


@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)):
    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):
    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 env.state()


@app.get("/tasks")
def list_tasks():
    return {"tasks": env.list_tasks()}


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)