anugrah55 commited on
Commit
7103888
·
verified ·
1 Parent(s): 2b0bffa

Update CERNenv Space

Browse files
Files changed (2) hide show
  1. server/app.py +105 -3
  2. space/training/app.py +22 -22
server/app.py CHANGED
@@ -1,16 +1,107 @@
1
- """FastAPI app exposing ``CERNCollisionEnvironment`` over the OpenEnv HTTP API."""
 
 
 
 
 
 
2
 
3
  from __future__ import annotations
4
 
5
  import os
6
  from typing import Optional
7
 
 
8
  from openenv.core.env_server import create_fastapi_app
9
 
10
  from models import CollisionObservation, ExperimentAction
11
  from server.environment import CERNCollisionEnvironment
12
 
13
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
14
  def make_env_factory(
15
  max_steps: int,
16
  default_difficulty: Optional[str],
@@ -29,9 +120,20 @@ def build_app(
29
  max_steps: int = 40,
30
  default_difficulty: Optional[str] = None,
31
  ):
32
- """Construct the FastAPI app with a per-session environment factory."""
 
 
 
 
 
33
  factory = make_env_factory(max_steps=max_steps, default_difficulty=default_difficulty)
34
- return create_fastapi_app(factory, ExperimentAction, CollisionObservation)
 
 
 
 
 
 
35
 
36
 
37
  app = build_app(
 
1
+ """FastAPI app exposing ``CERNCollisionEnvironment`` over the OpenEnv HTTP API.
2
+
3
+ We delegate the standard OpenEnv routes (``/reset``, ``/step``, ``/state``,
4
+ ``/schema``, ``/health``, ``/mcp``) to ``create_fastapi_app`` and add a
5
+ human-friendly landing page at ``/`` so the Hugging Face Space preview
6
+ shows the project description instead of a 404.
7
+ """
8
 
9
  from __future__ import annotations
10
 
11
  import os
12
  from typing import Optional
13
 
14
+ from fastapi.responses import HTMLResponse
15
  from openenv.core.env_server import create_fastapi_app
16
 
17
  from models import CollisionObservation, ExperimentAction
18
  from server.environment import CERNCollisionEnvironment
19
 
20
 
21
+ _LANDING_PAGE = """\
22
+ <!doctype html>
23
+ <html lang=en>
24
+ <head>
25
+ <meta charset=utf-8>
26
+ <title>CERNenv — LHC Discovery RL Environment</title>
27
+ <meta name=viewport content="width=device-width,initial-scale=1">
28
+ <style>
29
+ body { font-family: ui-sans-serif, system-ui, -apple-system, sans-serif;
30
+ margin: 2rem auto; max-width: 780px; color:#111; padding: 0 1rem; line-height:1.5 }
31
+ h1 { margin-bottom: .25rem }
32
+ h2 { margin-top: 2rem; border-bottom: 1px solid #eee; padding-bottom: .25rem }
33
+ code { background:#f4f4f4; padding:.05rem .35rem; border-radius:4px; font-size:.95em }
34
+ pre { background:#0e1116; color:#e6edf3; padding:1rem; border-radius:6px; overflow-x:auto }
35
+ pre code { background:transparent; color:inherit; padding:0 }
36
+ .pill { display:inline-block; padding:.1rem .55rem; border-radius:999px;
37
+ background:#e8f0ff; color:#1d4ed8; font-size:.85em; margin-right:.25rem }
38
+ .muted { color:#666 }
39
+ a { color:#1d4ed8 }
40
+ table { border-collapse:collapse; margin: .5rem 0 }
41
+ th,td { text-align:left; padding:.25rem .9rem .25rem 0; vertical-align: top }
42
+ th { color:#444; font-weight:600 }
43
+ </style>
44
+ </head>
45
+ <body>
46
+ <h1>⚛️ CERNenv</h1>
47
+ <p class=muted>An LHC (Large Hadron Collider) particle-discovery RL environment for autonomous physicist agents — built for the Meta OpenEnv Hackathon.</p>
48
+
49
+ <p>
50
+ <span class=pill>OpenEnv</span>
51
+ <span class=pill>POMDP</span>
52
+ <span class=pill>16 action types</span>
53
+ <span class=pill>3 difficulty levels</span>
54
+ <span class=pill>HTTP + WebSocket</span>
55
+ </p>
56
+
57
+ <h2>What this is</h2>
58
+ <p>
59
+ A Large Language Model (LLM) agent plays a high-energy physicist running an
60
+ analysis at the LHC. Each step it picks one structured action — configure
61
+ the beam, allocate luminosity, set a trigger, collect collisions, fit a
62
+ resonance, estimate significance, submit a discovery claim, and so on —
63
+ and receives a noisy detector-style observation. The latent particle
64
+ (mass, decay channel, branching ratios, width) is hidden ground truth.
65
+ Reward decomposes into per-step shaping + a dominant terminal calibration
66
+ against the truth particle.
67
+ </p>
68
+
69
+ <h2>API</h2>
70
+ <table>
71
+ <tr><th><code>GET /health</code></th><td>liveness probe</td></tr>
72
+ <tr><th><code>GET /schema</code></th><td>JSON schemas for actions, observations, state</td></tr>
73
+ <tr><th><code>POST /reset</code></th><td>start a new episode (e.g. <code>{"seed": 7, "scenario": "easy_diphoton_160"}</code>)</td></tr>
74
+ <tr><th><code>POST /step</code></th><td>execute one action (<code>{"action": {"action_type": ..., "parameters": {...}, "justification": "..."}}</code>)</td></tr>
75
+ <tr><th><code>GET /state</code></th><td>current public state snapshot</td></tr>
76
+ <tr><th><code>GET /docs</code></th><td>interactive Swagger UI</td></tr>
77
+ <tr><th><code>GET /metadata</code></th><td>environment metadata</td></tr>
78
+ </table>
79
+
80
+ <h2>Quickstart</h2>
81
+ <pre><code># reset
82
+ curl -X POST $URL/reset \\
83
+ -H 'Content-Type: application/json' \\
84
+ -d '{"seed": 7, "scenario": "easy_diphoton_160"}'
85
+
86
+ # step
87
+ curl -X POST $URL/step \\
88
+ -H 'Content-Type: application/json' \\
89
+ -d '{"action": {"action_type": "configure_beam",
90
+ "parameters": {"sqrt_s_tev": 13.0},
91
+ "justification": "set 13 TeV"}}'</code></pre>
92
+
93
+ <h2>Companion Spaces</h2>
94
+ <ul>
95
+ <li>📓 Trainer (Unsloth + LoRA + GRPO on A100): <a href="https://huggingface.co/spaces/anugrah55/cernenv-trainer">anugrah55/cernenv-trainer</a></li>
96
+ <li>🎯 Trained adapters (LoRA): <a href="https://huggingface.co/anugrah55/cernenv-grpo-qwen2.5-3b">anugrah55/cernenv-grpo-qwen2.5-3b</a></li>
97
+ </ul>
98
+
99
+ <p class=muted style="margin-top:2rem">CERNenv · OpenEnv-compatible · BSD-3-Clause</p>
100
+ </body>
101
+ </html>
102
+ """
103
+
104
+
105
  def make_env_factory(
106
  max_steps: int,
107
  default_difficulty: Optional[str],
 
120
  max_steps: int = 40,
121
  default_difficulty: Optional[str] = None,
122
  ):
123
+ """Construct the FastAPI app with a per-session environment factory.
124
+
125
+ The OpenEnv-provided routes (`/reset`, `/step`, `/state`, `/schema`,
126
+ `/health`, `/mcp`) come from ``create_fastapi_app``. We then mount a
127
+ friendly landing page at ``/`` so the Space preview is informative.
128
+ """
129
  factory = make_env_factory(max_steps=max_steps, default_difficulty=default_difficulty)
130
+ fa_app = create_fastapi_app(factory, ExperimentAction, CollisionObservation)
131
+
132
+ @fa_app.get("/", response_class=HTMLResponse, include_in_schema=False)
133
+ def landing() -> HTMLResponse: # pragma: no cover - trivial
134
+ return HTMLResponse(_LANDING_PAGE)
135
+
136
+ return fa_app
137
 
138
 
139
  app = build_app(
space/training/app.py CHANGED
@@ -297,17 +297,17 @@ _HTML = """\
297
  <meta charset=utf-8>
298
  <title>CERNenv Trainer</title>
299
  <style>
300
- body {{ font-family: ui-sans-serif, system-ui, sans-serif; margin: 2rem auto; max-width: 760px; color:#111 }}
301
- h1 {{ margin-bottom: 0 }}
302
- .muted {{ color:#666 }}
303
- pre {{ background:#0e1116; color:#e6edf3; padding:1rem; border-radius:6px; overflow-x:auto; max-height:50vh }}
304
- button {{ font-size:1rem; padding:.6rem 1rem; border-radius:6px; border:1px solid #888; background:#fff; cursor:pointer }}
305
- .pill {{ display:inline-block; padding:.1rem .5rem; border-radius:999px; background:#eef; color:#225 }}
306
- .ok {{ background:#dfd; color:#272 }}
307
- .fail {{ background:#fdd; color:#822 }}
308
- .run {{ background:#fdf6d8; color:#774 }}
309
- table {{ border-collapse:collapse; }}
310
- td {{ padding:.2rem .8rem .2rem 0; }}
311
  </style>
312
  </head>
313
  <body>
@@ -326,31 +326,31 @@ _HTML = """\
326
  <pre id=logs>loading…</pre>
327
 
328
  <script>
329
- async function refresh() {{
330
  const s = await fetch('/status').then(r => r.json());
331
  const pill = document.getElementById('status');
332
  pill.textContent = s.status;
333
- pill.className = 'pill ' + ({{idle:'',running:'run',finished:'ok',failed:'fail'}}[s.status] || '');
334
 
335
  const meta = document.getElementById('meta');
336
  meta.innerHTML = '';
337
- for (const [k, v] of Object.entries({{
338
  started_at: s.started_at, finished_at: s.finished_at, error: s.last_error,
339
- ...(s.last_config || {{}}),
340
- }})) {{
341
  if (v == null || v === '') continue;
342
  const tr = document.createElement('tr');
343
- tr.innerHTML = `<td><b>${{k}}</b></td><td><code>${{v}}</code></td>`;
344
  meta.appendChild(tr);
345
- }}
346
 
347
  const logs = await fetch('/logs?tail=200').then(r => r.text());
348
  document.getElementById('logs').textContent = logs || '(no logs yet)';
349
- }}
350
- async function startRun() {{
351
- await fetch('/train', {{method:'POST'}});
352
  setTimeout(refresh, 500);
353
- }}
354
  refresh();
355
  setInterval(refresh, 5000);
356
  </script>
 
297
  <meta charset=utf-8>
298
  <title>CERNenv Trainer</title>
299
  <style>
300
+ body { font-family: ui-sans-serif, system-ui, sans-serif; margin: 2rem auto; max-width: 760px; color:#111 }
301
+ h1 { margin-bottom: 0 }
302
+ .muted { color:#666 }
303
+ pre { background:#0e1116; color:#e6edf3; padding:1rem; border-radius:6px; overflow-x:auto; max-height:50vh }
304
+ button { font-size:1rem; padding:.6rem 1rem; border-radius:6px; border:1px solid #888; background:#fff; cursor:pointer }
305
+ .pill { display:inline-block; padding:.1rem .5rem; border-radius:999px; background:#eef; color:#225 }
306
+ .ok { background:#dfd; color:#272 }
307
+ .fail { background:#fdd; color:#822 }
308
+ .run { background:#fdf6d8; color:#774 }
309
+ table { border-collapse:collapse; }
310
+ td { padding:.2rem .8rem .2rem 0; }
311
  </style>
312
  </head>
313
  <body>
 
326
  <pre id=logs>loading…</pre>
327
 
328
  <script>
329
+ async function refresh() {
330
  const s = await fetch('/status').then(r => r.json());
331
  const pill = document.getElementById('status');
332
  pill.textContent = s.status;
333
+ pill.className = 'pill ' + ({idle:'',running:'run',finished:'ok',failed:'fail'}[s.status] || '');
334
 
335
  const meta = document.getElementById('meta');
336
  meta.innerHTML = '';
337
+ for (const [k, v] of Object.entries({
338
  started_at: s.started_at, finished_at: s.finished_at, error: s.last_error,
339
+ ...(s.last_config || {}),
340
+ })) {
341
  if (v == null || v === '') continue;
342
  const tr = document.createElement('tr');
343
+ tr.innerHTML = `<td><b>${k}</b></td><td><code>${v}</code></td>`;
344
  meta.appendChild(tr);
345
+ }
346
 
347
  const logs = await fetch('/logs?tail=200').then(r => r.text());
348
  document.getElementById('logs').textContent = logs || '(no logs yet)';
349
+ }
350
+ async function startRun() {
351
+ await fetch('/train', {method:'POST'});
352
  setTimeout(refresh, 500);
353
+ }
354
  refresh();
355
  setInterval(refresh, 5000);
356
  </script>