| """FastAPI server for SurviveCity β OpenEnv-compliant HTTP API. |
| |
| Endpoints: |
| GET /health β {"status": "healthy"} |
| POST /reset β Observation (JSON) |
| POST /step β Observation (JSON) |
| GET /state β episode metadata (JSON) |
| |
| Run with: uvicorn server.app:app --host 0.0.0.0 --port 7860 |
| """ |
|
|
| from __future__ import annotations |
|
|
| import logging |
| from fastapi import FastAPI, HTTPException |
| from fastapi.middleware.cors import CORSMiddleware |
| from pydantic import BaseModel |
| from typing import Optional |
|
|
| from survivecity_env.env import SurviveCityEnv |
|
|
| |
| |
| |
|
|
| logging.basicConfig( |
| level=logging.INFO, |
| format="%(asctime)s %(name)s %(levelname)s %(message)s", |
| ) |
| logger = logging.getLogger("survivecity.server") |
|
|
| |
| |
| |
|
|
| app = FastAPI( |
| title="SurviveCity", |
| description="Multi-Agent Zombie Apocalypse for LLM Failure-Replay Learning", |
| version="1.0.0", |
| ) |
|
|
| app.add_middleware( |
| CORSMiddleware, |
| allow_origins=["*"], |
| allow_methods=["*"], |
| allow_headers=["*"], |
| ) |
|
|
| |
| _env = SurviveCityEnv() |
|
|
|
|
| |
| |
| |
|
|
| class ResetRequest(BaseModel): |
| seed: Optional[int] = None |
|
|
|
|
| class StepRequest(BaseModel): |
| agent_id: int |
| action_type: str |
| vote_target: Optional[int] = None |
| message: Optional[str] = None |
|
|
|
|
| |
| |
| |
|
|
| @app.get("/health") |
| def health(): |
| """Health check β OpenEnv validator requires exactly {"status": "healthy"}.""" |
| return {"status": "healthy"} |
|
|
|
|
| @app.post("/reset") |
| def reset(request: ResetRequest = ResetRequest()): |
| """Reset the environment and start a new episode. |
| |
| Returns the initial observation for agent A0. |
| """ |
| try: |
| obs = _env.reset(seed=request.seed) |
| return obs |
| except Exception as e: |
| logger.error(f"Reset failed: {e}", exc_info=True) |
| raise HTTPException(status_code=500, detail=str(e)) |
|
|
|
|
| @app.post("/step") |
| def step(request: StepRequest): |
| """Execute one agent's action and return observation for the next agent. |
| |
| Args: |
| request: Action with agent_id, action_type, and optional vote_target/message |
| """ |
| try: |
| obs = _env.step(request.model_dump()) |
| return obs |
| except Exception as e: |
| logger.error(f"Step failed: {e}", exc_info=True) |
| raise HTTPException(status_code=500, detail=str(e)) |
|
|
|
|
| @app.get("/state") |
| def get_state(): |
| """Return current episode metadata.""" |
| return _env.state |
|
|
|
|
| |
| |
| |
|
|
| if __name__ == "__main__": |
| import uvicorn |
| uvicorn.run("server.app:app", host="0.0.0.0", port=7860, reload=False) |
|
|