project_meta / server /app.py
V1ShAl1613's picture
fix(server): allow POST /reset without request body
dc65515
Raw History Blame Contribute Delete
3.9 kB
"""FastAPI server exposing POST /reset, POST /step, GET /state endpoints."""
from __future__ import annotations
from typing import Any, Dict, Optional
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel, Field
from env.environment import CustomerSupportEnv
from env.models import Action, ActionType
# ── App ───────────────────────────────────────────────────────────────
app = FastAPI(
title="OpenEnv Customer Support",
description="AI Customer Support Ticket Resolution Environment",
version="1.0.0",
)
# Single environment instance
_env = CustomerSupportEnv()
# ── Request / Response schemas ────────────────────────────────────────
class ResetRequest(BaseModel):
task_id: str = "easy_classification"
ticket_index: int = 0
class StepRequest(BaseModel):
action_type: str
classification: Optional[str] = None
response_text: Optional[str] = None
refund_amount: Optional[float] = None
escalation_reason: Optional[str] = None
class ResetResponse(BaseModel):
observation: Dict[str, Any]
class StepResponse(BaseModel):
observation: Dict[str, Any]
reward: Dict[str, Any]
done: bool
info: Dict[str, Any]
class StateResponse(BaseModel):
state: Dict[str, Any]
# ── Endpoints ─────────────────────────────────────────────────────────
@app.post("/reset", response_model=ResetResponse)
def reset_endpoint(req: Optional[ResetRequest] = None) -> ResetResponse:
"""Reset the environment and return the initial observation."""
if req is None:
req = ResetRequest()
try:
obs = _env.reset(task_id=req.task_id, ticket_index=req.ticket_index)
return ResetResponse(observation=obs.model_dump())
except ValueError as exc:
raise HTTPException(status_code=400, detail=str(exc))
@app.post("/step", response_model=StepResponse)
def step_endpoint(req: StepRequest) -> StepResponse:
"""Execute one environment step."""
try:
action_type = ActionType(req.action_type)
except ValueError:
raise HTTPException(
status_code=400,
detail=(
f"Invalid action_type '{req.action_type}'. "
f"Valid: {[a.value for a in ActionType]}"
),
)
action = Action(
action_type=action_type,
classification=req.classification,
response_text=req.response_text,
refund_amount=req.refund_amount,
escalation_reason=req.escalation_reason,
)
try:
result = _env.step(action)
except RuntimeError as exc:
raise HTTPException(status_code=400, detail=str(exc))
return StepResponse(
observation=result.observation.model_dump(),
reward=result.reward.model_dump(),
done=result.done,
info=result.info,
)
@app.get("/state", response_model=StateResponse)
def state_endpoint() -> StateResponse:
"""Return the current environment state."""
return StateResponse(state=_env.state())
# ── Health check ──────────────────────────────────────────────────────
@app.get("/health")
def health():
return {"status": "ok"}
# ── Entry point ───────────────────────────────────────────────────────
def main():
import uvicorn
uvicorn.run("server.app:app", host="0.0.0.0", port=7860)
if __name__ == "__main__":
main()