Vaish6's picture
Upload app.py
0b21fa5 verified
Raw History Blame Contribute Delete
6.1 kB
"""
MetaOCT: OpenEnv-Compliant REST API Server
Implements POST /reset, POST /step, GET /validate per OpenEnv specification.
Hackathon automated checker compatible.
"""
import json
import os
import asyncio
import threading
from http.server import HTTPServer, BaseHTTPRequestHandler
from dotenv import load_dotenv
from env import MetaOCTEnv, Action
load_dotenv()
# Global environment instance and state
_env = None
_env_lock = threading.Lock()
_loop = None
def get_loop():
global _loop
if _loop is None or _loop.is_closed():
_loop = asyncio.new_event_loop()
return _loop
def run_async(coro):
loop = get_loop()
return loop.run_until_complete(coro)
def get_env(difficulty="easy"):
global _env
if _env is None:
_env = MetaOCTEnv(difficulty=difficulty)
return _env
class OpenEnvHandler(BaseHTTPRequestHandler):
def log_message(self, format, *args): pass # Suppress access logs
def send_json(self, code, data):
body = json.dumps(data).encode()
self.send_response(code)
self.send_header("Content-Type", "application/json")
self.send_header("Content-Length", str(len(body)))
self.send_header("Access-Control-Allow-Origin", "*")
self.end_headers()
self.wfile.write(body)
def read_body(self):
length = int(self.headers.get("Content-Length", 0))
if length:
return json.loads(self.rfile.read(length))
return {}
def do_OPTIONS(self):
self.send_response(200)
self.send_header("Access-Control-Allow-Origin", "*")
self.send_header("Access-Control-Allow-Methods", "GET, POST, OPTIONS")
self.send_header("Access-Control-Allow-Headers", "Content-Type, Authorization")
self.end_headers()
def do_GET(self):
if self.path == "/validate" or self.path == "/openenv/validate" or self.path == "/openenv/info":
task_list = [
{"id": "easy", "name": "Easy POMDP Diagnosis", "difficulty": "easy", "budget": 1000, "description": "Basic POMDP traversal", "grader": "tasks:grade_easy"},
{"id": "medium", "name": "Medium Accuracy POMDP", "difficulty": "medium", "budget": 400, "description": "Precision optimization", "grader": "tasks:grade_medium"},
{"id": "hard", "name": "Hard Zero-Shot POMDP", "difficulty": "hard", "budget": 200, "description": "Extreme resource constraints", "grader": "tasks:grade_hard"}
]
self.send_json(200, {
"status": "ok",
"name": "MetaOCT",
"version": "1.0.0",
"description": "Multi-step POMDP clinical OCT diagnostic environment",
"tasks": task_list,
"action_space": ["request_oct_scan", "enhance_contrast", "measure_fluid_thickness", "submit_diagnosis"],
"observation_space": ["acquired_scans", "available_budget", "tool_outputs", "step_count"]
})
elif self.path == "/" or self.path == "/health":
self.send_json(200, {"status": "ok", "message": "MetaOCT API Server is running. Use /validate, /reset, /step endpoints."})
def do_POST(self):
try:
body = self.read_body()
if self.path in ["/reset", "/openenv/reset"]:
difficulty = body.get("task", body.get("difficulty", "easy"))
with _env_lock:
global _env
_env = MetaOCTEnv(difficulty=difficulty)
obs = run_async(_env.reset())
self.send_json(200, {
"observation": {
"acquired_scans": obs.acquired_scans,
"available_budget": obs.available_budget,
"tool_outputs": obs.tool_outputs,
"step_count": obs.step_count
},
"info": {"difficulty": difficulty, "task": difficulty}
})
elif self.path in ["/step", "/openenv/step"]:
action_data = body.get("action", body)
tool_name = action_data.get("tool_name", action_data.get("name", "submit_diagnosis"))
parameters = action_data.get("parameters", action_data.get("args", {}))
action = Action(tool_name=tool_name, parameters=parameters)
with _env_lock:
env = get_env()
result = run_async(env.step(action))
obs = result.observation
obs_dict = {}
if obs:
obs_dict = {
"acquired_scans": obs.acquired_scans,
"available_budget": obs.available_budget,
"tool_outputs": obs.tool_outputs,
"step_count": obs.step_count
}
self.send_json(200, {
"observation": obs_dict,
"reward": result.reward,
"done": result.done,
"info": result.info or {}
})
else:
self.send_json(404, {"error": "Unknown endpoint"})
except Exception as e:
print(f"[ERROR] {self.path}: {e}", flush=True)
self.send_json(500, {"error": str(e)})
if __name__ == "__main__":
port = int(os.getenv("GRADIO_SERVER_PORT", "7860"))
# Start HTTP server IMMEDIATELY so checker can connect
server = HTTPServer(("0.0.0.0", port), OpenEnvHandler)
print(f"[INFO] MetaOCT OpenEnv API running on port {port}", flush=True)
print(f"[INFO] Endpoints: POST /reset, POST /step, GET /validate", flush=True)
# Pre-warm environment in background thread
def prewarm():
print("[INFO] Pre-warming MetaOCT environment...", flush=True)
get_env("easy")
print("[INFO] Environment ready.", flush=True)
t = threading.Thread(target=prewarm, daemon=True)
t.start()
server.serve_forever()