Spaces:
Sleeping
Sleeping
File size: 6,098 Bytes
7560863 ca526ce 7560863 ca526ce 7560863 ca526ce fa83aac 7560863 fa83aac ca526ce 7560863 ca526ce 7560863 ca526ce 7560863 ca526ce 2c2a501 ca526ce 2c2a501 ca526ce 2c2a501 ca526ce 0b21fa5 ca526ce fa83aac 7560863 8be3fe6 ca526ce 8be3fe6 7560863 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 | """
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()
|