Nitesh321
feat: SQL Analytics OpenEnv environment β€” 10 tasks, dense rewards, RL-ready
798461f
Raw History Blame Contribute Delete
4.58 kB
"""
server/app.py
-------------
FastAPI application for the SQL Analytics Environment.
Exposes the environment over HTTP + WebSocket endpoints that are
consumed by EnvClient / the OpenEnv evaluation harness.
Usage (development):
uvicorn server.app:app --reload --host 0.0.0.0 --port 8000
Usage (production via Docker):
uvicorn server.app:app --host 0.0.0.0 --port 8000 --workers 1
"""
import time
from fastapi import Request
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import JSONResponse
try:
from openenv.core.env_server.http_server import create_app
from .sql_analytics_environment import SQLAnalyticsEnvironment
from ..models import SQLAction, SQLObservation
except ImportError:
from openenv.core.env_server.http_server import create_app
from server.sql_analytics_environment import SQLAnalyticsEnvironment
from models import SQLAction, SQLObservation
# ── Create base app ───────────────────────────────────────────────────────────
# Pass the class (not an instance) so the HTTP server can instantiate
# a fresh environment per WebSocket session.
app = create_app(
SQLAnalyticsEnvironment,
SQLAction,
SQLObservation,
env_name="sql_analytics_env",
)
# ── CORS β€” allow all origins (needed for HuggingFace Spaces) ─────────────────
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
# ── Explicit health check (required by OpenEnv validator + hackathon) ─────────
@app.get("/health", tags=["meta"])
async def health():
"""
Health check endpoint.
Returns HTTP 200 when the server is up and ready.
Required by OpenEnv pre-submission checks.
"""
return {"status": "ok", "environment": "sql_analytics_env", "version": "0.1.0"}
# ── Environment metadata ──────────────────────────────────────────────────────
@app.get("/info", tags=["meta"])
async def info():
"""Return environment metadata for discoverability."""
return {
"name": "sql_analytics_env",
"version": "0.1.0",
"description": (
"SQL Analytics environment where AI agents answer "
"business-intelligence questions by writing SQL queries "
"against a seeded e-commerce database."
),
"tasks": [
{"task_id": "task_001", "difficulty": "easy", "topic": "Count customers by state"},
{"task_id": "task_002", "difficulty": "easy", "topic": "Most expensive product in category"},
{"task_id": "task_003", "difficulty": "medium", "topic": "Top 5 customers by spend"},
{"task_id": "task_004", "difficulty": "medium", "topic": "Category with highest avg rating"},
{"task_id": "task_005", "difficulty": "hard", "topic": "Q1-only buyers (churn signal)"},
{"task_id": "task_006", "difficulty": "hard", "topic": "Monthly revenue + growth rate"},
],
"action_space": "SQLAction { sql: str }",
"observation_space": "SQLObservation { result, error, db_schema, task_description, reward, done }",
"reward_range": [0.0, 1.0],
"max_steps": 10,
}
# ── Request timing middleware (helps with debugging slow queries) ─────────────
@app.middleware("http")
async def add_process_time_header(request: Request, call_next):
start = time.perf_counter()
response = await call_next(request)
elapsed = round(time.perf_counter() - start, 4)
response.headers["X-Process-Time"] = str(elapsed)
return response
# ── Global exception handler ──────────────────────────────────────────────────
@app.exception_handler(Exception)
async def global_exception_handler(request: Request, exc: Exception):
return JSONResponse(
status_code=500,
content={
"error": type(exc).__name__,
"detail": str(exc),
"path": str(request.url),
},
)
def main() -> None:
"""Entry point for direct execution."""
import uvicorn
uvicorn.run(app, host="0.0.0.0", port=8000)
if __name__ == "__main__":
main()