Shreya2005-2005's picture
FINAL FIX: indentation fix and hardened scores to [0.1, 0.9]
cc5d66e
Raw History Blame Contribute Delete
4.5 kB
# server/app.py
# FastAPI server that wraps the StudyEnvironment and exposes it as an HTTP API
#
# Run with: uvicorn server.app:app --host 0.0.0.0 --port 8000
# or just: python -m uvicorn server.app:app --reload (for dev)
import sys
import os
# make sure we can import from src/ regardless of how this is run
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from fastapi import FastAPI, HTTPException
from fastapi.middleware.cors import CORSMiddleware
from pydantic import BaseModel
from typing import Optional
import uvicorn
from src.environment import StudyEnvironment
from src.models import Action, StepResult, Observation
app = FastAPI(
title="Smart Study Session Optimizer",
description="An RL environment for optimizing study sessions. Part of OpenEnv hackathon.",
version="1.0.0",
)
# cors - needed for HuggingFace spaces and browser clients
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_methods=["*"],
allow_headers=["*"],
)
# global environment instance - one env per server process
# this is fine for single-agent use, would need sessions for multi-agent
env: Optional[StudyEnvironment] = None
print("[SERVER] Study Optimizer server starting up...")
class ResetRequest(BaseModel):
task_id: Optional[str] = "maximize_productivity"
class StepRequest(BaseModel):
action: int
@app.get("/health")
def health():
"""Health check. Evaluators ping this first."""
return {"status": "ok"}
@app.post("/reset")
def reset(request: ResetRequest = ResetRequest()):
"""
Reset the environment. Call this before starting a new episode.
Returns the first observation.
"""
global env
task = request.task_id or "maximize_productivity"
print(f"[SERVER] /reset called, task_id={task}")
env = StudyEnvironment(task_id=task)
obs = env.reset()
# return as dict so it plays nice with the OpenEnv spec
return {
"observation": obs.model_dump(),
"task_id": task,
"info": {
"score": 0.5,
"task_id": task,
"step_count": 0,
"all_task_scores": {
"survive_8_hours": 0.5,
"maximize_productivity": 0.5,
"smart_break_taker": 0.5,
}
}
}
def _clamp_score(v: float) -> float:
"""Last-resort clamp: force any score into [0.1, 0.9]."""
try:
val = float(v)
return max(0.1, min(0.9, val))
except:
return 0.5
@app.post("/step")
def step(request: StepRequest):
"""
Execute one step in the environment.
Returns observation, reward, done, info.
"""
global env
if env is None:
# auto-init with defaults if reset wasn't called first
print("[SERVER] WARNING: /step called before /reset, auto-initializing")
env = StudyEnvironment()
env.reset()
if not 0 <= request.action <= 2:
raise HTTPException(status_code=400, detail=f"Invalid action {request.action}. Must be 0, 1, or 2.")
print(f"[SERVER] /step called, action={request.action}")
result: StepResult = env.step(request.action)
info = result.info
# SAFETY NET: clamp all scores at the API boundary
if "score" in info:
info["score"] = _clamp_score(info["score"])
if "all_task_scores" in info:
for task_key in info["all_task_scores"]:
info["all_task_scores"][task_key] = _clamp_score(info["all_task_scores"][task_key])
return {
"observation": result.observation.model_dump(),
"reward": result.reward,
"done": result.done,
"info": info,
}
@app.get("/state")
def state():
"""Return the full internal environment state. Useful for debugging."""
global env
if env is None:
return {"status": "not_initialized", "message": "Call /reset first"}
return env.get_state()
@app.get("/")
def root():
"""Basic info page."""
return {
"name": "Smart Study Session Optimizer",
"version": "1.0.0",
"description": "RL environment for optimizing study sessions",
"endpoints": ["/health", "/reset", "/step", "/state"],
"tasks": ["survive_8_hours", "maximize_productivity", "smart_break_taker"],
}
def main():
"""Entry point for the server."""
port = int(os.environ.get("PORT", 7860))
print(f"[SERVER] Starting on port {port}")
uvicorn.run(app, host="0.0.0.0", port=port)
if __name__ == "__main__":
main()