Spaces:
Sleeping
Sleeping
| from fastapi import FastAPI, HTTPException | |
| from fastapi.middleware.cors import CORSMiddleware | |
| from fastapi.openapi.docs import get_swagger_ui_html | |
| from pydantic import BaseModel, model_validator | |
| from typing import Optional | |
| from environment import CustomerSupportEnv, STEP_ORDER | |
| from graders.base_grader import BaseGrader, HardTaskGrader | |
| from tasks.easy_task import EASY_TASK | |
| from tasks.medium_task import MEDIUM_TASK | |
| from tasks.hard_task import HARD_TASK | |
| app = FastAPI(title="Customer Support AI Environment") | |
| app.add_middleware( | |
| CORSMiddleware, | |
| allow_origins=["*"], | |
| allow_methods=["*"], | |
| allow_headers=["*"], | |
| ) | |
| _env: Optional[CustomerSupportEnv] = None | |
| TASK_MAP = { | |
| "easy": EASY_TASK, | |
| "medium": MEDIUM_TASK, | |
| "hard": HARD_TASK, | |
| } | |
| class ResetRequest(BaseModel): | |
| task: str = "easy" | |
| class StepRequest(BaseModel): | |
| action: Optional[str] = None # accepts {"action": "..."} | |
| response: Optional[str] = None # accepts {"response": "..."} | |
| def resolve_action(self): | |
| self.action = self.action or self.response | |
| if not self.action: | |
| raise ValueError("Provide either 'action' or 'response' field with the agent reply.") | |
| return self | |
| def _build_observation(): | |
| ep = _env.episode | |
| task = _env.task | |
| step_index = _env._step_index | |
| current_step_name = ( | |
| STEP_ORDER[step_index].value if step_index < len(STEP_ORDER) else "done" | |
| ) | |
| return { | |
| "task_id": task.task_id, | |
| "difficulty": task.difficulty.value, | |
| "customer_emotion": task.customer_emotion, | |
| "customer_message": task.customer_message, | |
| "scenario_context": task.scenario_context, | |
| "current_step": current_step_name, | |
| "step_number": step_index + 1, | |
| "total_steps": len(STEP_ORDER), | |
| "episode_status": ep.status.value, | |
| "total_reward": round(ep.total_reward, 3), | |
| "wrong_step_count": ep.wrong_step_count, | |
| "steps_completed": [ | |
| { | |
| "step": s.step.value, | |
| "correct": s.correct, | |
| "detected_action": s.detected_action, | |
| "reward": round(s.reward, 3), | |
| "penalty": round(s.penalty, 3), | |
| "penalty_reasons": s.penalty_reasons, | |
| } | |
| for s in ep.steps | |
| ], | |
| } | |
| def health_check(): | |
| return {"status": "ok", "message": "Customer Support AI Environment"} | |
| def reset_env(body: ResetRequest = None): | |
| global _env | |
| task_key = (body.task if body else "easy").lower() | |
| if task_key not in TASK_MAP: | |
| raise HTTPException( | |
| status_code=400, | |
| detail=f"Unknown task '{task_key}'. Choose from: {list(TASK_MAP.keys())}" | |
| ) | |
| task = TASK_MAP[task_key] | |
| grader = HardTaskGrader() if task_key == "hard" else BaseGrader() | |
| _env = CustomerSupportEnv(task=task, grader=grader) | |
| _env.reset() | |
| return {"observation": _build_observation()} | |
| def step_env(body: StepRequest): | |
| global _env | |
| if _env is None: | |
| raise HTTPException(status_code=400, detail="Environment not initialized. Call /reset first.") | |
| if _env.episode.status.value != "running": | |
| raise HTTPException(status_code=400, detail=f"Episode already ended: {_env.episode.status.value}") | |
| result, done = _env.step(body.action) | |
| return { | |
| "observation": _build_observation(), | |
| "reward": round(result.reward, 3), | |
| "done": done, | |
| "info": { | |
| "step": result.step.value, | |
| "correct": result.correct, | |
| "detected_action": result.detected_action, | |
| "expected_action": result.expected_action, | |
| "penalty": round(result.penalty, 3), | |
| "penalty_reasons": result.penalty_reasons, | |
| "fail_triggered": result.fail_triggered, | |
| "fail_reason": result.fail_reason, | |
| }, | |
| } | |
| def get_state(): | |
| if _env is None: | |
| raise HTTPException(status_code=400, detail="Environment not initialized. Call /reset first.") | |
| return _env.summary() | |
| def observation_space(): | |
| return { | |
| "type": "Dict", | |
| "fields": { | |
| "task_id": "str", | |
| "difficulty": "str (easy|medium|hard)", | |
| "customer_emotion": "str", | |
| "customer_message": "str", | |
| "scenario_context": "str", | |
| "current_step": "str (empathy|collect_info|investigate|resolution|done)", | |
| "step_number": "int", | |
| "total_steps": "int", | |
| "episode_status": "str (running|success|fail)", | |
| "total_reward": "float", | |
| "wrong_step_count": "int", | |
| "steps_completed": "List[Dict]", | |
| } | |
| } | |
| def action_space(): | |
| return { | |
| "type": "Text", | |
| "description": "Agent free-text reply to customer", | |
| "min_words": 6, | |
| } | |
| def custom_docs(): | |
| return get_swagger_ui_html(openapi_url="/openapi.json", title="API Docs") | |
| def main(): | |
| """Entry point for 'server' script defined in pyproject.toml.""" | |
| import uvicorn | |
| uvicorn.run("api:app", host="0.0.0.0", port=7860) | |
| if __name__ == "__main__": | |
| main() | |