Spaces:
Sleeping
Sleeping
| """Sandbox for executing OptCoder-submitted optimizer code. | |
| In-process exec with: | |
| - AST strip of module-level demo code (keeps only `class Optimizer`) | |
| - Restricted globals (only `np` and `math` exposed) | |
| - Thread-based timeout on each `step()` call (works on any thread — Gradio | |
| handlers, uvicorn workers, etc. — unlike SIGALRM which requires main thread) | |
| """ | |
| from __future__ import annotations | |
| import ast | |
| import concurrent.futures | |
| import math | |
| from dataclasses import dataclass | |
| from typing import Any | |
| import numpy as np | |
| class SandboxError(Exception): | |
| """Raised for any sandbox-level failure (syntax, timeout, security).""" | |
| class StepTimeout(SandboxError): | |
| pass | |
| # Shared single-worker pool for step() invocations. Using a reusable executor | |
| # avoids the ~50ms-per-call cost of spinning up a fresh thread pool for each | |
| # arena step. Not shared across processes, but that's fine in-process. | |
| _STEP_EXECUTOR = concurrent.futures.ThreadPoolExecutor( | |
| max_workers=1, thread_name_prefix="lf-sandbox-step", | |
| ) | |
| def strip_module_code(source: str) -> str: | |
| """Keep only the `class Optimizer` node. | |
| Drops imports (the sandbox pre-injects np/numpy/math into globals), | |
| hallucinated demo functions, `if __name__ == '__main__'` blocks, and | |
| trailing execution code that frequently appears in LLM output. | |
| """ | |
| try: | |
| tree = ast.parse(source) | |
| except SyntaxError as e: | |
| raise SandboxError(f"SyntaxError: {e}") from e | |
| kept: list[ast.stmt] = [] | |
| found_class = False | |
| for node in tree.body: | |
| if isinstance(node, ast.ClassDef) and node.name == "Optimizer": | |
| kept.append(node) | |
| found_class = True | |
| # Imports are dropped — env provides np/numpy/math via globals. | |
| if not found_class: | |
| raise SandboxError("No `class Optimizer` found in submission") | |
| new_tree = ast.Module(body=kept, type_ignores=[]) | |
| ast.fix_missing_locations(new_tree) | |
| return ast.unparse(new_tree) | |
| def _safe_globals() -> dict: | |
| """Globals exposed to submitted code. Minimal builtins + np/numpy/math.""" | |
| import builtins as _bi | |
| safe_names = [ | |
| # numeric / iteration | |
| "abs", "min", "max", "sum", "len", "range", "zip", "enumerate", | |
| "list", "tuple", "dict", "set", "float", "int", "bool", "str", | |
| "round", "divmod", "pow", "reversed", "sorted", "any", "all", "map", "filter", | |
| # introspection (safe subset) | |
| "isinstance", "issubclass", "hasattr", "getattr", "setattr", | |
| "True", "False", "None", | |
| # class definition machinery (required to define `class Optimizer`) | |
| "__build_class__", "__name__", "object", "super", | |
| "type", "property", "staticmethod", "classmethod", | |
| # errors (so submitted code can raise/catch sanely) | |
| "Exception", "ValueError", "TypeError", "IndexError", "KeyError", | |
| "ZeroDivisionError", "RuntimeError", "ArithmeticError", "OverflowError", | |
| ] | |
| safe_bi = {n: getattr(_bi, n) for n in safe_names if hasattr(_bi, n)} | |
| return { | |
| "__builtins__": safe_bi, | |
| "__name__": "__submission__", | |
| "np": np, | |
| "numpy": np, | |
| "math": math, | |
| } | |
| class CompiledOptimizer: | |
| """Wraps an instantiated Optimizer with bounded `step` execution.""" | |
| instance: Any | |
| step_timeout: float = 0.5 | |
| def step(self, x: np.ndarray, f_val: float, grad: np.ndarray) -> np.ndarray: | |
| # Run step() on a worker thread with a hard deadline. This is | |
| # thread-safe (unlike SIGALRM) so it works from Gradio handlers and | |
| # uvicorn workers. | |
| future = _STEP_EXECUTOR.submit(self.instance.step, x, f_val, grad) | |
| try: | |
| out = future.result(timeout=self.step_timeout) | |
| except concurrent.futures.TimeoutError: | |
| future.cancel() | |
| raise StepTimeout(f"step() exceeded {self.step_timeout}s") | |
| except Exception as e: | |
| raise SandboxError(f"step() raised {type(e).__name__}: {e}") from e | |
| try: | |
| out = np.asarray(out, dtype=float) | |
| except Exception as e: | |
| raise SandboxError(f"step() returned non-array value ({type(e).__name__}: {e})") from e | |
| if out.shape != x.shape: | |
| raise SandboxError(f"step() returned shape {out.shape}, expected {x.shape}") | |
| if not np.all(np.isfinite(out)): | |
| raise SandboxError("step() returned non-finite values") | |
| return out | |
| def compile_optimizer(source: str, dim: int, step_timeout: float = 0.5) -> CompiledOptimizer: | |
| """Strip, exec, and instantiate Optimizer(dim=dim). Returns a wrapper. | |
| exec() and __init__() are NOT timeout-guarded — they should be fast | |
| (microseconds) and any pathological module-level code would be caught | |
| by the AST strip. Timeout protection is applied to `step()` calls. | |
| """ | |
| stripped = strip_module_code(source) | |
| globs = _safe_globals() | |
| locs: dict = {} | |
| try: | |
| exec(compile(stripped, "<submission>", "exec"), globs, locs) | |
| except SandboxError: | |
| raise | |
| except Exception as e: | |
| raise SandboxError(f"exec failed: {type(e).__name__}: {e}") from e | |
| OptimizerCls = locs.get("Optimizer") or globs.get("Optimizer") | |
| if OptimizerCls is None: | |
| raise SandboxError("Optimizer class not defined after exec") | |
| try: | |
| instance = OptimizerCls(dim=dim) | |
| except Exception as e: | |
| raise SandboxError(f"__init__ failed: {type(e).__name__}: {e}") from e | |
| if not hasattr(instance, "step"): | |
| raise SandboxError("Optimizer instance missing `step` method") | |
| return CompiledOptimizer(instance=instance, step_timeout=step_timeout) | |