Spaces:
Sleeping
Sleeping
| """FastAPI app exposing ``CERNCollisionEnvironment`` over the OpenEnv HTTP API. | |
| We delegate the standard OpenEnv routes (``/reset``, ``/step``, ``/state``, | |
| ``/schema``, ``/health``, ``/mcp``) to ``create_fastapi_app`` and add a | |
| human-friendly landing page at ``/`` so the Hugging Face Space preview | |
| shows the project description instead of a 404. | |
| """ | |
| from __future__ import annotations | |
| import os | |
| from typing import Optional | |
| from fastapi.responses import HTMLResponse | |
| from openenv.core.env_server import create_fastapi_app | |
| from models import CollisionObservation, ExperimentAction | |
| from server.environment import CERNCollisionEnvironment | |
| _LANDING_PAGE = """\ | |
| <!doctype html> | |
| <html lang=en> | |
| <head> | |
| <meta charset=utf-8> | |
| <title>CERNenv — LHC Discovery RL Environment</title> | |
| <meta name=viewport content="width=device-width,initial-scale=1"> | |
| <style> | |
| body { font-family: ui-sans-serif, system-ui, -apple-system, sans-serif; | |
| margin: 2rem auto; max-width: 780px; color:#111; padding: 0 1rem; line-height:1.5 } | |
| h1 { margin-bottom: .25rem } | |
| h2 { margin-top: 2rem; border-bottom: 1px solid #eee; padding-bottom: .25rem } | |
| code { background:#f4f4f4; padding:.05rem .35rem; border-radius:4px; font-size:.95em } | |
| pre { background:#0e1116; color:#e6edf3; padding:1rem; border-radius:6px; overflow-x:auto } | |
| pre code { background:transparent; color:inherit; padding:0 } | |
| .pill { display:inline-block; padding:.1rem .55rem; border-radius:999px; | |
| background:#e8f0ff; color:#1d4ed8; font-size:.85em; margin-right:.25rem } | |
| .muted { color:#666 } | |
| a { color:#1d4ed8 } | |
| table { border-collapse:collapse; margin: .5rem 0 } | |
| th,td { text-align:left; padding:.25rem .9rem .25rem 0; vertical-align: top } | |
| th { color:#444; font-weight:600 } | |
| </style> | |
| </head> | |
| <body> | |
| <h1>⚛️ CERNenv</h1> | |
| <p class=muted>An LHC (Large Hadron Collider) particle-discovery RL environment for autonomous physicist agents — built for the Meta OpenEnv Hackathon.</p> | |
| <p> | |
| <span class=pill>OpenEnv</span> | |
| <span class=pill>POMDP</span> | |
| <span class=pill>16 action types</span> | |
| <span class=pill>3 difficulty levels</span> | |
| <span class=pill>HTTP + WebSocket</span> | |
| </p> | |
| <h2>What this is</h2> | |
| <p> | |
| A Large Language Model (LLM) agent plays a high-energy physicist running an | |
| analysis at the LHC. Each step it picks one structured action — configure | |
| the beam, allocate luminosity, set a trigger, collect collisions, fit a | |
| resonance, estimate significance, submit a discovery claim, and so on — | |
| and receives a noisy detector-style observation. The latent particle | |
| (mass, decay channel, branching ratios, width) is hidden ground truth. | |
| Reward decomposes into per-step shaping + a dominant terminal calibration | |
| against the truth particle. | |
| </p> | |
| <h2>API</h2> | |
| <table> | |
| <tr><th><code>GET /health</code></th><td>liveness probe</td></tr> | |
| <tr><th><code>GET /schema</code></th><td>JSON schemas for actions, observations, state</td></tr> | |
| <tr><th><code>POST /reset</code></th><td>start a new episode (e.g. <code>{"seed": 7, "scenario": "easy_diphoton_160"}</code>)</td></tr> | |
| <tr><th><code>POST /step</code></th><td>execute one action (<code>{"action": {"action_type": ..., "parameters": {...}, "justification": "..."}}</code>)</td></tr> | |
| <tr><th><code>GET /state</code></th><td>current public state snapshot</td></tr> | |
| <tr><th><code>GET /docs</code></th><td>interactive Swagger UI</td></tr> | |
| <tr><th><code>GET /metadata</code></th><td>environment metadata</td></tr> | |
| </table> | |
| <h2>Quickstart</h2> | |
| <pre><code># reset | |
| curl -X POST $URL/reset \\ | |
| -H 'Content-Type: application/json' \\ | |
| -d '{"seed": 7, "scenario": "easy_diphoton_160"}' | |
| # step | |
| curl -X POST $URL/step \\ | |
| -H 'Content-Type: application/json' \\ | |
| -d '{"action": {"action_type": "configure_beam", | |
| "parameters": {"sqrt_s_tev": 13.0}, | |
| "justification": "set 13 TeV"}}'</code></pre> | |
| <h2>Companion Spaces</h2> | |
| <ul> | |
| <li>📓 Trainer (Unsloth + LoRA + GRPO on A100): <a href="https://huggingface.co/spaces/anugrah55/cernenv-trainer">anugrah55/cernenv-trainer</a></li> | |
| <li>🎯 Trained adapters (LoRA): <a href="https://huggingface.co/anugrah55/cernenv-grpo-qwen2.5-3b">anugrah55/cernenv-grpo-qwen2.5-3b</a></li> | |
| </ul> | |
| <p class=muted style="margin-top:2rem">CERNenv · OpenEnv-compatible · BSD-3-Clause</p> | |
| </body> | |
| </html> | |
| """ | |
| def make_env_factory( | |
| max_steps: int, | |
| default_difficulty: Optional[str], | |
| ): | |
| def factory() -> CERNCollisionEnvironment: | |
| return CERNCollisionEnvironment( | |
| max_steps=max_steps, | |
| default_difficulty=default_difficulty, | |
| ) | |
| return factory | |
| def build_app( | |
| *, | |
| max_steps: int = 40, | |
| default_difficulty: Optional[str] = None, | |
| ): | |
| """Construct the FastAPI app with a per-session environment factory. | |
| The OpenEnv-provided routes (`/reset`, `/step`, `/state`, `/schema`, | |
| `/health`, `/mcp`) come from ``create_fastapi_app``. We then mount a | |
| friendly landing page at ``/`` so the Space preview is informative. | |
| """ | |
| factory = make_env_factory(max_steps=max_steps, default_difficulty=default_difficulty) | |
| fa_app = create_fastapi_app(factory, ExperimentAction, CollisionObservation) | |
| def landing() -> HTMLResponse: # pragma: no cover - trivial | |
| return HTMLResponse(_LANDING_PAGE) | |
| return fa_app | |
| app = build_app( | |
| max_steps=int(os.getenv("CERNENV_MAX_STEPS", "40")), | |
| default_difficulty=os.getenv("CERNENV_DEFAULT_DIFFICULTY") or None, | |
| ) | |
| def main() -> None: # pragma: no cover - CLI entrypoint | |
| import uvicorn | |
| host = os.getenv("HOST", "0.0.0.0") | |
| port = int(os.getenv("PORT", "8000")) | |
| uvicorn.run("server.app:app", host=host, port=port, log_level="info") | |
| if __name__ == "__main__": # pragma: no cover | |
| main() | |