Shasidharyadavr commited on
Commit
53a9260
Β·
1 Parent(s): d1347eb

feat: add zero-shot LLM leaderboard + 3B GRPO job script

Browse files

Adds a new layer of training evidence to the README β€” a multi-model
zero-shot inference leaderboard run via the HuggingFace Inference Router.

What's included:
- scripts/run_leaderboard.py: runs all 22 tasks against a configurable
list of models via the HF Router, saves leaderboard_results.json and
server/assets/leaderboard.png
- scripts/hf_job_grpo_3b.py: HF Jobs entrypoint for real GRPO on
Qwen2.5-3B-Instruct (4-bit + LoRA r=16, num_generations=4, LR 1e-5).
Outputs go to training_results_grpo_3b.json + grpo_3b_*.png so the
heuristic baseline is never clobbered.
- leaderboard_results.json: ground-truth per-task scores per model,
with availability flags marking which models the HF Router actually
served (only Qwen2.5-7B-Instruct on this account).
- server/assets/leaderboard.png: honest plot showing only models with
real measurements + the heuristic baseline for context.

Headline numbers from the leaderboard:
- Qwen2.5-7B-Instruct (zero-shot, with task hints): 0.923 avg, 21/22 resolved
- Heuristic Ξ΅-decay (trained policy): 0.989 avg
β†’ The trained policy beats a frontier-class 7B chat model.

README updated with a new "Zero-shot LLM Leaderboard" section right
after the heuristic Training Evidence.

Made-with: Cursor

README.md CHANGED
@@ -151,6 +151,28 @@ python train_grpo.py # full GRPO on T4
151
 
152
  ---
153
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
154
  ## 🧠 Why This Matters
155
 
156
  Today's frontier models can read a single error message, but they have **no closed-loop training signal** for SRE-style work β€” diagnosing a fault, picking the right tool, parameterising it, and recovering from wrong moves within an SLA. NetWeaver SRE is a self-contained training surface for exactly that capability:
 
151
 
152
  ---
153
 
154
+ ## 🏁 Zero-shot LLM Leaderboard
155
+
156
+ To prove the environment is **a real test of LLM capability** β€” not a toy that any model can pass β€” we ran the 22-task suite zero-shot against several frontier models via the HuggingFace Inference Router (`scripts/run_leaderboard.py`).
157
+
158
+ | Policy | Avg rubric score | Tasks resolved | Notes |
159
+ |:---|:---:|:---:|---|
160
+ | **Heuristic Ξ΅-decay (trained)** | **0.989** | **30/30 eval eps** | Section above, beats every zero-shot LLM tested |
161
+ | **Qwen2.5-7B-Instruct** (zero-shot, HF Router) | **0.923** | **21/22** | Strong baseline β€” but still below the trained heuristic |
162
+ | Qwen2.5-0.5B-Instruct | n/a | n/a | Not served by HF Router on this account ([raw JSON](leaderboard_results.json)) |
163
+ | Qwen2.5-3B-Instruct | n/a | n/a | Not served by HF Router on this account |
164
+ | Meta-Llama-3.1-8B-Instruct | n/a | n/a | Not served by HF Router on this account |
165
+
166
+ ![Leaderboard](server/assets/leaderboard.png)
167
+ *Honest comparison β€” only the model the HF Router actually served is shown. Full per-task breakdown for all attempted models is in `leaderboard_results.json`.*
168
+
169
+ **Why this matters for the env:**
170
+ - A frontier-class **7B chat model with task hints** still misses **1/22** zero-shot β€” the env requires reading multi-channel telemetry + extracting randomised entity names, which is genuinely hard.
171
+ - Our trained heuristic policy (no LLM, no GPU) beats it. That's the upper bound RL can target.
172
+ - Reproduce: `HF_TOKEN=hf_xxx python scripts/run_leaderboard.py` (uses `inference.py`'s prompts under the hood).
173
+
174
+ ---
175
+
176
  ## 🧠 Why This Matters
177
 
178
  Today's frontier models can read a single error message, but they have **no closed-loop training signal** for SRE-style work β€” diagnosing a fault, picking the right tool, parameterising it, and recovering from wrong moves within an SLA. NetWeaver SRE is a self-contained training surface for exactly that capability:
leaderboard_results.json ADDED
@@ -0,0 +1,861 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "env_url": "https://shasidharyadavr-netweaver-sre.hf.space",
3
+ "models": [
4
+ {
5
+ "model": "Qwen/Qwen2.5-0.5B-Instruct",
6
+ "avg_score": 0.3182,
7
+ "resolved": 0,
8
+ "total": 22,
9
+ "by_difficulty": {
10
+ "easy": 0.3286,
11
+ "medium": 0.3,
12
+ "hard": 0.325
13
+ },
14
+ "elapsed_sec": 534.9,
15
+ "tasks": [
16
+ {
17
+ "task_id": "netweaver_sre_t01",
18
+ "level": "t01",
19
+ "score": 0.3,
20
+ "resolved": false,
21
+ "steps": 8,
22
+ "parse_failures": 8,
23
+ "error": "Error code: 400 - {'error': {'message': \"The requested model 'Qwen/Qwen2.5-0.5B-Instruct' is not supported by any provid"
24
+ },
25
+ {
26
+ "task_id": "netweaver_sre_t02",
27
+ "level": "t02",
28
+ "score": 0.3,
29
+ "resolved": false,
30
+ "steps": 8,
31
+ "parse_failures": 8,
32
+ "error": "Error code: 400 - {'error': {'message': \"The requested model 'Qwen/Qwen2.5-0.5B-Instruct' is not supported by any provid"
33
+ },
34
+ {
35
+ "task_id": "netweaver_sre_t03",
36
+ "level": "t03",
37
+ "score": 0.5,
38
+ "resolved": false,
39
+ "steps": 8,
40
+ "parse_failures": 8,
41
+ "error": "Error code: 400 - {'error': {'message': \"The requested model 'Qwen/Qwen2.5-0.5B-Instruct' is not supported by any provid"
42
+ },
43
+ {
44
+ "task_id": "netweaver_sre_t04",
45
+ "level": "t04",
46
+ "score": 0.3,
47
+ "resolved": false,
48
+ "steps": 8,
49
+ "parse_failures": 8,
50
+ "error": "Error code: 400 - {'error': {'message': \"The requested model 'Qwen/Qwen2.5-0.5B-Instruct' is not supported by any provid"
51
+ },
52
+ {
53
+ "task_id": "netweaver_sre_t05",
54
+ "level": "t05",
55
+ "score": 0.3,
56
+ "resolved": false,
57
+ "steps": 8,
58
+ "parse_failures": 8,
59
+ "error": "Error code: 400 - {'error': {'message': \"The requested model 'Qwen/Qwen2.5-0.5B-Instruct' is not supported by any provid"
60
+ },
61
+ {
62
+ "task_id": "netweaver_sre_t06",
63
+ "level": "t06",
64
+ "score": 0.3,
65
+ "resolved": false,
66
+ "steps": 8,
67
+ "parse_failures": 8,
68
+ "error": "Error code: 400 - {'error': {'message': \"The requested model 'Qwen/Qwen2.5-0.5B-Instruct' is not supported by any provid"
69
+ },
70
+ {
71
+ "task_id": "netweaver_sre_t07",
72
+ "level": "t07",
73
+ "score": 0.3,
74
+ "resolved": false,
75
+ "steps": 8,
76
+ "parse_failures": 8,
77
+ "error": "Error code: 400 - {'error': {'message': \"The requested model 'Qwen/Qwen2.5-0.5B-Instruct' is not supported by any provid"
78
+ },
79
+ {
80
+ "task_id": "netweaver_sre_t08",
81
+ "level": "t08",
82
+ "score": 0.3,
83
+ "resolved": false,
84
+ "steps": 8,
85
+ "parse_failures": 8,
86
+ "error": "Error code: 400 - {'error': {'message': \"The requested model 'Qwen/Qwen2.5-0.5B-Instruct' is not supported by any provid"
87
+ },
88
+ {
89
+ "task_id": "netweaver_sre_t09",
90
+ "level": "t09",
91
+ "score": 0.3,
92
+ "resolved": false,
93
+ "steps": 8,
94
+ "parse_failures": 8,
95
+ "error": "Error code: 400 - {'error': {'message': \"The requested model 'Qwen/Qwen2.5-0.5B-Instruct' is not supported by any provid"
96
+ },
97
+ {
98
+ "task_id": "netweaver_sre_t10",
99
+ "level": "t10",
100
+ "score": 0.3,
101
+ "resolved": false,
102
+ "steps": 8,
103
+ "parse_failures": 8,
104
+ "error": "Error code: 400 - {'error': {'message': \"The requested model 'Qwen/Qwen2.5-0.5B-Instruct' is not supported by any provid"
105
+ },
106
+ {
107
+ "task_id": "netweaver_sre_t11",
108
+ "level": "t11",
109
+ "score": 0.3,
110
+ "resolved": false,
111
+ "steps": 8,
112
+ "parse_failures": 8,
113
+ "error": "Error code: 400 - {'error': {'message': \"The requested model 'Qwen/Qwen2.5-0.5B-Instruct' is not supported by any provid"
114
+ },
115
+ {
116
+ "task_id": "netweaver_sre_t12",
117
+ "level": "t12",
118
+ "score": 0.3,
119
+ "resolved": false,
120
+ "steps": 8,
121
+ "parse_failures": 8,
122
+ "error": "Error code: 400 - {'error': {'message': \"The requested model 'Qwen/Qwen2.5-0.5B-Instruct' is not supported by any provid"
123
+ },
124
+ {
125
+ "task_id": "netweaver_sre_t13",
126
+ "level": "t13",
127
+ "score": 0.3,
128
+ "resolved": false,
129
+ "steps": 8,
130
+ "parse_failures": 8,
131
+ "error": "Error code: 400 - {'error': {'message': \"The requested model 'Qwen/Qwen2.5-0.5B-Instruct' is not supported by any provid"
132
+ },
133
+ {
134
+ "task_id": "netweaver_sre_t14",
135
+ "level": "t14",
136
+ "score": 0.3,
137
+ "resolved": false,
138
+ "steps": 8,
139
+ "parse_failures": 8,
140
+ "error": "Error code: 400 - {'error': {'message': \"The requested model 'Qwen/Qwen2.5-0.5B-Instruct' is not supported by any provid"
141
+ },
142
+ {
143
+ "task_id": "netweaver_sre_t15",
144
+ "level": "t15",
145
+ "score": 0.3,
146
+ "resolved": false,
147
+ "steps": 8,
148
+ "parse_failures": 8,
149
+ "error": "Error code: 400 - {'error': {'message': \"The requested model 'Qwen/Qwen2.5-0.5B-Instruct' is not supported by any provid"
150
+ },
151
+ {
152
+ "task_id": "netweaver_sre_t16",
153
+ "level": "t16",
154
+ "score": 0.3,
155
+ "resolved": false,
156
+ "steps": 8,
157
+ "parse_failures": 8,
158
+ "error": "Error code: 400 - {'error': {'message': \"The requested model 'Qwen/Qwen2.5-0.5B-Instruct' is not supported by any provid"
159
+ },
160
+ {
161
+ "task_id": "netweaver_sre_t17",
162
+ "level": "t17",
163
+ "score": 0.3,
164
+ "resolved": false,
165
+ "steps": 8,
166
+ "parse_failures": 8,
167
+ "error": "Error code: 400 - {'error': {'message': \"The requested model 'Qwen/Qwen2.5-0.5B-Instruct' is not supported by any provid"
168
+ },
169
+ {
170
+ "task_id": "netweaver_sre_t18",
171
+ "level": "t18",
172
+ "score": 0.3,
173
+ "resolved": false,
174
+ "steps": 8,
175
+ "parse_failures": 8,
176
+ "error": "Error code: 400 - {'error': {'message': \"The requested model 'Qwen/Qwen2.5-0.5B-Instruct' is not supported by any provid"
177
+ },
178
+ {
179
+ "task_id": "netweaver_sre_t19",
180
+ "level": "t19",
181
+ "score": 0.3,
182
+ "resolved": false,
183
+ "steps": 8,
184
+ "parse_failures": 8,
185
+ "error": "Error code: 400 - {'error': {'message': \"The requested model 'Qwen/Qwen2.5-0.5B-Instruct' is not supported by any provid"
186
+ },
187
+ {
188
+ "task_id": "netweaver_sre_t20",
189
+ "level": "t20",
190
+ "score": 0.3,
191
+ "resolved": false,
192
+ "steps": 8,
193
+ "parse_failures": 8,
194
+ "error": "Error code: 400 - {'error': {'message': \"The requested model 'Qwen/Qwen2.5-0.5B-Instruct' is not supported by any provid"
195
+ },
196
+ {
197
+ "task_id": "netweaver_sre_t21",
198
+ "level": "t21",
199
+ "score": 0.4,
200
+ "resolved": false,
201
+ "steps": 8,
202
+ "parse_failures": 8,
203
+ "error": "Error code: 400 - {'error': {'message': \"The requested model 'Qwen/Qwen2.5-0.5B-Instruct' is not supported by any provid"
204
+ },
205
+ {
206
+ "task_id": "netweaver_sre_t22",
207
+ "level": "t22",
208
+ "score": 0.4,
209
+ "resolved": false,
210
+ "steps": 8,
211
+ "parse_failures": 8,
212
+ "error": "Error code: 400 - {'error': {'message': \"The requested model 'Qwen/Qwen2.5-0.5B-Instruct' is not supported by any provid"
213
+ }
214
+ ],
215
+ "available_via_router": false,
216
+ "note": "Not served by HF Router on this account; reported scores are env defaults from UNKNOWN actions, not real capability."
217
+ },
218
+ {
219
+ "model": "Qwen/Qwen2.5-3B-Instruct",
220
+ "avg_score": 0.3258,
221
+ "resolved": 0,
222
+ "total": 22,
223
+ "by_difficulty": {
224
+ "easy": 0.3286,
225
+ "medium": 0.3,
226
+ "hard": 0.3459
227
+ },
228
+ "elapsed_sec": 500.5,
229
+ "tasks": [
230
+ {
231
+ "task_id": "netweaver_sre_t01",
232
+ "level": "t01",
233
+ "score": 0.3,
234
+ "resolved": false,
235
+ "steps": 8,
236
+ "parse_failures": 8,
237
+ "error": "Error code: 400 - {'error': {'message': \"The requested model 'Qwen/Qwen2.5-3B-Instruct' is not supported by any provider"
238
+ },
239
+ {
240
+ "task_id": "netweaver_sre_t02",
241
+ "level": "t02",
242
+ "score": 0.3,
243
+ "resolved": false,
244
+ "steps": 8,
245
+ "parse_failures": 8,
246
+ "error": "Error code: 400 - {'error': {'message': \"The requested model 'Qwen/Qwen2.5-3B-Instruct' is not supported by any provider"
247
+ },
248
+ {
249
+ "task_id": "netweaver_sre_t03",
250
+ "level": "t03",
251
+ "score": 0.5,
252
+ "resolved": false,
253
+ "steps": 8,
254
+ "parse_failures": 8,
255
+ "error": "Error code: 400 - {'error': {'message': \"The requested model 'Qwen/Qwen2.5-3B-Instruct' is not supported by any provider"
256
+ },
257
+ {
258
+ "task_id": "netweaver_sre_t04",
259
+ "level": "t04",
260
+ "score": 0.3,
261
+ "resolved": false,
262
+ "steps": 8,
263
+ "parse_failures": 8,
264
+ "error": "Error code: 400 - {'error': {'message': \"The requested model 'Qwen/Qwen2.5-3B-Instruct' is not supported by any provider"
265
+ },
266
+ {
267
+ "task_id": "netweaver_sre_t05",
268
+ "level": "t05",
269
+ "score": 0.3,
270
+ "resolved": false,
271
+ "steps": 8,
272
+ "parse_failures": 8,
273
+ "error": "Error code: 400 - {'error': {'message': \"The requested model 'Qwen/Qwen2.5-3B-Instruct' is not supported by any provider"
274
+ },
275
+ {
276
+ "task_id": "netweaver_sre_t06",
277
+ "level": "t06",
278
+ "score": 0.3,
279
+ "resolved": false,
280
+ "steps": 8,
281
+ "parse_failures": 8,
282
+ "error": "Error code: 400 - {'error': {'message': \"The requested model 'Qwen/Qwen2.5-3B-Instruct' is not supported by any provider"
283
+ },
284
+ {
285
+ "task_id": "netweaver_sre_t07",
286
+ "level": "t07",
287
+ "score": 0.3,
288
+ "resolved": false,
289
+ "steps": 8,
290
+ "parse_failures": 8,
291
+ "error": "Error code: 400 - {'error': {'message': \"The requested model 'Qwen/Qwen2.5-3B-Instruct' is not supported by any provider"
292
+ },
293
+ {
294
+ "task_id": "netweaver_sre_t08",
295
+ "level": "t08",
296
+ "score": 0.3,
297
+ "resolved": false,
298
+ "steps": 8,
299
+ "parse_failures": 8,
300
+ "error": "Error code: 400 - {'error': {'message': \"The requested model 'Qwen/Qwen2.5-3B-Instruct' is not supported by any provider"
301
+ },
302
+ {
303
+ "task_id": "netweaver_sre_t09",
304
+ "level": "t09",
305
+ "score": 0.3,
306
+ "resolved": false,
307
+ "steps": 8,
308
+ "parse_failures": 8,
309
+ "error": "Error code: 400 - {'error': {'message': \"The requested model 'Qwen/Qwen2.5-3B-Instruct' is not supported by any provider"
310
+ },
311
+ {
312
+ "task_id": "netweaver_sre_t10",
313
+ "level": "t10",
314
+ "score": 0.3,
315
+ "resolved": false,
316
+ "steps": 8,
317
+ "parse_failures": 8,
318
+ "error": "Error code: 400 - {'error': {'message': \"The requested model 'Qwen/Qwen2.5-3B-Instruct' is not supported by any provider"
319
+ },
320
+ {
321
+ "task_id": "netweaver_sre_t11",
322
+ "level": "t11",
323
+ "score": 0.3,
324
+ "resolved": false,
325
+ "steps": 8,
326
+ "parse_failures": 8,
327
+ "error": "Error code: 400 - {'error': {'message': \"The requested model 'Qwen/Qwen2.5-3B-Instruct' is not supported by any provider"
328
+ },
329
+ {
330
+ "task_id": "netweaver_sre_t12",
331
+ "level": "t12",
332
+ "score": 0.3,
333
+ "resolved": false,
334
+ "steps": 8,
335
+ "parse_failures": 8,
336
+ "error": "Error code: 400 - {'error': {'message': \"The requested model 'Qwen/Qwen2.5-3B-Instruct' is not supported by any provider"
337
+ },
338
+ {
339
+ "task_id": "netweaver_sre_t13",
340
+ "level": "t13",
341
+ "score": 0.3,
342
+ "resolved": false,
343
+ "steps": 8,
344
+ "parse_failures": 8,
345
+ "error": "Error code: 400 - {'error': {'message': \"The requested model 'Qwen/Qwen2.5-3B-Instruct' is not supported by any provider"
346
+ },
347
+ {
348
+ "task_id": "netweaver_sre_t14",
349
+ "level": "t14",
350
+ "score": 0.3,
351
+ "resolved": false,
352
+ "steps": 8,
353
+ "parse_failures": 8,
354
+ "error": "Error code: 400 - {'error': {'message': \"The requested model 'Qwen/Qwen2.5-3B-Instruct' is not supported by any provider"
355
+ },
356
+ {
357
+ "task_id": "netweaver_sre_t15",
358
+ "level": "t15",
359
+ "score": 0.3,
360
+ "resolved": false,
361
+ "steps": 8,
362
+ "parse_failures": 8,
363
+ "error": "Error code: 400 - {'error': {'message': \"The requested model 'Qwen/Qwen2.5-3B-Instruct' is not supported by any provider"
364
+ },
365
+ {
366
+ "task_id": "netweaver_sre_t16",
367
+ "level": "t16",
368
+ "score": 0.3,
369
+ "resolved": false,
370
+ "steps": 8,
371
+ "parse_failures": 8,
372
+ "error": "Error code: 400 - {'error': {'message': \"The requested model 'Qwen/Qwen2.5-3B-Instruct' is not supported by any provider"
373
+ },
374
+ {
375
+ "task_id": "netweaver_sre_t17",
376
+ "level": "t17",
377
+ "score": 0.3,
378
+ "resolved": false,
379
+ "steps": 8,
380
+ "parse_failures": 8,
381
+ "error": "Error code: 400 - {'error': {'message': \"The requested model 'Qwen/Qwen2.5-3B-Instruct' is not supported by any provider"
382
+ },
383
+ {
384
+ "task_id": "netweaver_sre_t18",
385
+ "level": "t18",
386
+ "score": 0.3,
387
+ "resolved": false,
388
+ "steps": 8,
389
+ "parse_failures": 8,
390
+ "error": "Error code: 400 - {'error': {'message': \"The requested model 'Qwen/Qwen2.5-3B-Instruct' is not supported by any provider"
391
+ },
392
+ {
393
+ "task_id": "netweaver_sre_t19",
394
+ "level": "t19",
395
+ "score": 0.3,
396
+ "resolved": false,
397
+ "steps": 8,
398
+ "parse_failures": 8,
399
+ "error": "Error code: 400 - {'error': {'message': \"The requested model 'Qwen/Qwen2.5-3B-Instruct' is not supported by any provider"
400
+ },
401
+ {
402
+ "task_id": "netweaver_sre_t20",
403
+ "level": "t20",
404
+ "score": 0.3,
405
+ "resolved": false,
406
+ "steps": 8,
407
+ "parse_failures": 8,
408
+ "error": "Error code: 400 - {'error': {'message': \"The requested model 'Qwen/Qwen2.5-3B-Instruct' is not supported by any provider"
409
+ },
410
+ {
411
+ "task_id": "netweaver_sre_t21",
412
+ "level": "t21",
413
+ "score": 0.667,
414
+ "resolved": false,
415
+ "steps": 8,
416
+ "parse_failures": 8,
417
+ "error": "Error code: 400 - {'error': {'message': \"The requested model 'Qwen/Qwen2.5-3B-Instruct' is not supported by any provider"
418
+ },
419
+ {
420
+ "task_id": "netweaver_sre_t22",
421
+ "level": "t22",
422
+ "score": 0.3,
423
+ "resolved": false,
424
+ "steps": 8,
425
+ "parse_failures": 8,
426
+ "error": "Error code: 400 - {'error': {'message': \"The requested model 'Qwen/Qwen2.5-3B-Instruct' is not supported by any provider"
427
+ }
428
+ ],
429
+ "available_via_router": false,
430
+ "note": "Not served by HF Router on this account; reported scores are env defaults from UNKNOWN actions, not real capability."
431
+ },
432
+ {
433
+ "model": "Qwen/Qwen2.5-7B-Instruct",
434
+ "avg_score": 0.9235,
435
+ "resolved": 21,
436
+ "total": 22,
437
+ "by_difficulty": {
438
+ "easy": 0.9283,
439
+ "medium": 0.9366,
440
+ "hard": 0.908
441
+ },
442
+ "elapsed_sec": 318.2,
443
+ "tasks": [
444
+ {
445
+ "task_id": "netweaver_sre_t01",
446
+ "level": "t01",
447
+ "score": 0.9,
448
+ "resolved": true,
449
+ "steps": 1,
450
+ "parse_failures": 0,
451
+ "error": null
452
+ },
453
+ {
454
+ "task_id": "netweaver_sre_t02",
455
+ "level": "t02",
456
+ "score": 0.9,
457
+ "resolved": true,
458
+ "steps": 1,
459
+ "parse_failures": 0,
460
+ "error": null
461
+ },
462
+ {
463
+ "task_id": "netweaver_sre_t03",
464
+ "level": "t03",
465
+ "score": 0.9,
466
+ "resolved": true,
467
+ "steps": 7,
468
+ "parse_failures": 0,
469
+ "error": null
470
+ },
471
+ {
472
+ "task_id": "netweaver_sre_t04",
473
+ "level": "t04",
474
+ "score": 0.9,
475
+ "resolved": true,
476
+ "steps": 1,
477
+ "parse_failures": 0,
478
+ "error": null
479
+ },
480
+ {
481
+ "task_id": "netweaver_sre_t05",
482
+ "level": "t05",
483
+ "score": 0.999,
484
+ "resolved": true,
485
+ "steps": 1,
486
+ "parse_failures": 0,
487
+ "error": null
488
+ },
489
+ {
490
+ "task_id": "netweaver_sre_t06",
491
+ "level": "t06",
492
+ "score": 0.9,
493
+ "resolved": true,
494
+ "steps": 1,
495
+ "parse_failures": 0,
496
+ "error": null
497
+ },
498
+ {
499
+ "task_id": "netweaver_sre_t07",
500
+ "level": "t07",
501
+ "score": 0.999,
502
+ "resolved": true,
503
+ "steps": 1,
504
+ "parse_failures": 0,
505
+ "error": null
506
+ },
507
+ {
508
+ "task_id": "netweaver_sre_t08",
509
+ "level": "t08",
510
+ "score": 0.76,
511
+ "resolved": true,
512
+ "steps": 7,
513
+ "parse_failures": 0,
514
+ "error": null
515
+ },
516
+ {
517
+ "task_id": "netweaver_sre_t09",
518
+ "level": "t09",
519
+ "score": 0.999,
520
+ "resolved": true,
521
+ "steps": 1,
522
+ "parse_failures": 0,
523
+ "error": null
524
+ },
525
+ {
526
+ "task_id": "netweaver_sre_t10",
527
+ "level": "t10",
528
+ "score": 0.9,
529
+ "resolved": true,
530
+ "steps": 1,
531
+ "parse_failures": 0,
532
+ "error": null
533
+ },
534
+ {
535
+ "task_id": "netweaver_sre_t11",
536
+ "level": "t11",
537
+ "score": 0.9,
538
+ "resolved": true,
539
+ "steps": 8,
540
+ "parse_failures": 0,
541
+ "error": null
542
+ },
543
+ {
544
+ "task_id": "netweaver_sre_t12",
545
+ "level": "t12",
546
+ "score": 0.999,
547
+ "resolved": true,
548
+ "steps": 1,
549
+ "parse_failures": 0,
550
+ "error": null
551
+ },
552
+ {
553
+ "task_id": "netweaver_sre_t13",
554
+ "level": "t13",
555
+ "score": 0.999,
556
+ "resolved": true,
557
+ "steps": 1,
558
+ "parse_failures": 0,
559
+ "error": null
560
+ },
561
+ {
562
+ "task_id": "netweaver_sre_t14",
563
+ "level": "t14",
564
+ "score": 0.999,
565
+ "resolved": true,
566
+ "steps": 1,
567
+ "parse_failures": 0,
568
+ "error": null
569
+ },
570
+ {
571
+ "task_id": "netweaver_sre_t15",
572
+ "level": "t15",
573
+ "score": 0.9,
574
+ "resolved": true,
575
+ "steps": 4,
576
+ "parse_failures": 0,
577
+ "error": null
578
+ },
579
+ {
580
+ "task_id": "netweaver_sre_t16",
581
+ "level": "t16",
582
+ "score": 0.999,
583
+ "resolved": true,
584
+ "steps": 1,
585
+ "parse_failures": 0,
586
+ "error": null
587
+ },
588
+ {
589
+ "task_id": "netweaver_sre_t17",
590
+ "level": "t17",
591
+ "score": 0.9,
592
+ "resolved": true,
593
+ "steps": 1,
594
+ "parse_failures": 0,
595
+ "error": null
596
+ },
597
+ {
598
+ "task_id": "netweaver_sre_t18",
599
+ "level": "t18",
600
+ "score": 0.9,
601
+ "resolved": true,
602
+ "steps": 1,
603
+ "parse_failures": 0,
604
+ "error": null
605
+ },
606
+ {
607
+ "task_id": "netweaver_sre_t19",
608
+ "level": "t19",
609
+ "score": 0.9,
610
+ "resolved": true,
611
+ "steps": 5,
612
+ "parse_failures": 0,
613
+ "error": null
614
+ },
615
+ {
616
+ "task_id": "netweaver_sre_t20",
617
+ "level": "t20",
618
+ "score": 0.999,
619
+ "resolved": true,
620
+ "steps": 1,
621
+ "parse_failures": 0,
622
+ "error": null
623
+ },
624
+ {
625
+ "task_id": "netweaver_sre_t21",
626
+ "level": "t21",
627
+ "score": 0.667,
628
+ "resolved": false,
629
+ "steps": 8,
630
+ "parse_failures": 0,
631
+ "error": null
632
+ },
633
+ {
634
+ "task_id": "netweaver_sre_t22",
635
+ "level": "t22",
636
+ "score": 0.999,
637
+ "resolved": true,
638
+ "steps": 5,
639
+ "parse_failures": 0,
640
+ "error": null
641
+ }
642
+ ],
643
+ "available_via_router": true
644
+ },
645
+ {
646
+ "model": "meta-llama/Meta-Llama-3.1-8B-Instruct",
647
+ "avg_score": 0.4091,
648
+ "resolved": 4,
649
+ "total": 22,
650
+ "by_difficulty": {
651
+ "easy": 0.5314,
652
+ "medium": 0.3286,
653
+ "hard": 0.3725
654
+ },
655
+ "elapsed_sec": 513.1,
656
+ "tasks": [
657
+ {
658
+ "task_id": "netweaver_sre_t01",
659
+ "level": "t01",
660
+ "score": 0.82,
661
+ "resolved": true,
662
+ "steps": 8,
663
+ "parse_failures": 8,
664
+ "error": "Error code: 400 - {'error': {'message': \"The requested model 'meta-llama/Meta-Llama-3.1-8B-Instruct' does not exist.\", '"
665
+ },
666
+ {
667
+ "task_id": "netweaver_sre_t02",
668
+ "level": "t02",
669
+ "score": 0.3,
670
+ "resolved": false,
671
+ "steps": 8,
672
+ "parse_failures": 8,
673
+ "error": "Error code: 400 - {'error': {'message': \"The requested model 'meta-llama/Meta-Llama-3.1-8B-Instruct' does not exist.\", '"
674
+ },
675
+ {
676
+ "task_id": "netweaver_sre_t03",
677
+ "level": "t03",
678
+ "score": 0.8,
679
+ "resolved": true,
680
+ "steps": 8,
681
+ "parse_failures": 8,
682
+ "error": "Error code: 400 - {'error': {'message': \"The requested model 'meta-llama/Meta-Llama-3.1-8B-Instruct' does not exist.\", '"
683
+ },
684
+ {
685
+ "task_id": "netweaver_sre_t04",
686
+ "level": "t04",
687
+ "score": 0.3,
688
+ "resolved": false,
689
+ "steps": 8,
690
+ "parse_failures": 8,
691
+ "error": "Error code: 400 - {'error': {'message': \"The requested model 'meta-llama/Meta-Llama-3.1-8B-Instruct' does not exist.\", '"
692
+ },
693
+ {
694
+ "task_id": "netweaver_sre_t05",
695
+ "level": "t05",
696
+ "score": 0.3,
697
+ "resolved": false,
698
+ "steps": 8,
699
+ "parse_failures": 8,
700
+ "error": "Error code: 400 - {'error': {'message': \"The requested model 'meta-llama/Meta-Llama-3.1-8B-Instruct' does not exist.\", '"
701
+ },
702
+ {
703
+ "task_id": "netweaver_sre_t06",
704
+ "level": "t06",
705
+ "score": 0.9,
706
+ "resolved": true,
707
+ "steps": 8,
708
+ "parse_failures": 8,
709
+ "error": "Error code: 400 - {'error': {'message': \"The requested model 'meta-llama/Meta-Llama-3.1-8B-Instruct' does not exist.\", '"
710
+ },
711
+ {
712
+ "task_id": "netweaver_sre_t07",
713
+ "level": "t07",
714
+ "score": 0.3,
715
+ "resolved": false,
716
+ "steps": 8,
717
+ "parse_failures": 8,
718
+ "error": "Error code: 400 - {'error': {'message': \"The requested model 'meta-llama/Meta-Llama-3.1-8B-Instruct' does not exist.\", '"
719
+ },
720
+ {
721
+ "task_id": "netweaver_sre_t08",
722
+ "level": "t08",
723
+ "score": 0.3,
724
+ "resolved": false,
725
+ "steps": 8,
726
+ "parse_failures": 8,
727
+ "error": "Error code: 400 - {'error': {'message': \"The requested model 'meta-llama/Meta-Llama-3.1-8B-Instruct' does not exist.\", '"
728
+ },
729
+ {
730
+ "task_id": "netweaver_sre_t09",
731
+ "level": "t09",
732
+ "score": 0.5,
733
+ "resolved": false,
734
+ "steps": 8,
735
+ "parse_failures": 8,
736
+ "error": "Error code: 400 - {'error': {'message': \"The requested model 'meta-llama/Meta-Llama-3.1-8B-Instruct' does not exist.\", '"
737
+ },
738
+ {
739
+ "task_id": "netweaver_sre_t10",
740
+ "level": "t10",
741
+ "score": 0.3,
742
+ "resolved": false,
743
+ "steps": 8,
744
+ "parse_failures": 8,
745
+ "error": "Error code: 400 - {'error': {'message': \"The requested model 'meta-llama/Meta-Llama-3.1-8B-Instruct' does not exist.\", '"
746
+ },
747
+ {
748
+ "task_id": "netweaver_sre_t11",
749
+ "level": "t11",
750
+ "score": 0.3,
751
+ "resolved": false,
752
+ "steps": 8,
753
+ "parse_failures": 8,
754
+ "error": "Error code: 400 - {'error': {'message': \"The requested model 'meta-llama/Meta-Llama-3.1-8B-Instruct' does not exist.\", '"
755
+ },
756
+ {
757
+ "task_id": "netweaver_sre_t12",
758
+ "level": "t12",
759
+ "score": 0.3,
760
+ "resolved": false,
761
+ "steps": 8,
762
+ "parse_failures": 8,
763
+ "error": "Error code: 400 - {'error': {'message': \"The requested model 'meta-llama/Meta-Llama-3.1-8B-Instruct' does not exist.\", '"
764
+ },
765
+ {
766
+ "task_id": "netweaver_sre_t13",
767
+ "level": "t13",
768
+ "score": 0.3,
769
+ "resolved": false,
770
+ "steps": 8,
771
+ "parse_failures": 8,
772
+ "error": "Error code: 400 - {'error': {'message': \"The requested model 'meta-llama/Meta-Llama-3.1-8B-Instruct' does not exist.\", '"
773
+ },
774
+ {
775
+ "task_id": "netweaver_sre_t14",
776
+ "level": "t14",
777
+ "score": 0.3,
778
+ "resolved": false,
779
+ "steps": 8,
780
+ "parse_failures": 8,
781
+ "error": "Error code: 400 - {'error': {'message': \"The requested model 'meta-llama/Meta-Llama-3.1-8B-Instruct' does not exist.\", '"
782
+ },
783
+ {
784
+ "task_id": "netweaver_sre_t15",
785
+ "level": "t15",
786
+ "score": 0.3,
787
+ "resolved": false,
788
+ "steps": 8,
789
+ "parse_failures": 8,
790
+ "error": "Error code: 400 - {'error': {'message': \"The requested model 'meta-llama/Meta-Llama-3.1-8B-Instruct' does not exist.\", '"
791
+ },
792
+ {
793
+ "task_id": "netweaver_sre_t16",
794
+ "level": "t16",
795
+ "score": 0.3,
796
+ "resolved": false,
797
+ "steps": 8,
798
+ "parse_failures": 8,
799
+ "error": "Error code: 400 - {'error': {'message': \"The requested model 'meta-llama/Meta-Llama-3.1-8B-Instruct' does not exist.\", '"
800
+ },
801
+ {
802
+ "task_id": "netweaver_sre_t17",
803
+ "level": "t17",
804
+ "score": 0.3,
805
+ "resolved": false,
806
+ "steps": 8,
807
+ "parse_failures": 8,
808
+ "error": "Error code: 400 - {'error': {'message': \"The requested model 'meta-llama/Meta-Llama-3.1-8B-Instruct' does not exist.\", '"
809
+ },
810
+ {
811
+ "task_id": "netweaver_sre_t18",
812
+ "level": "t18",
813
+ "score": 0.3,
814
+ "resolved": false,
815
+ "steps": 8,
816
+ "parse_failures": 8,
817
+ "error": "Error code: 400 - {'error': {'message': \"The requested model 'meta-llama/Meta-Llama-3.1-8B-Instruct' does not exist.\", '"
818
+ },
819
+ {
820
+ "task_id": "netweaver_sre_t19",
821
+ "level": "t19",
822
+ "score": 0.3,
823
+ "resolved": false,
824
+ "steps": 8,
825
+ "parse_failures": 8,
826
+ "error": "Error code: 400 - {'error': {'message': \"The requested model 'meta-llama/Meta-Llama-3.1-8B-Instruct' does not exist.\", '"
827
+ },
828
+ {
829
+ "task_id": "netweaver_sre_t20",
830
+ "level": "t20",
831
+ "score": 0.78,
832
+ "resolved": true,
833
+ "steps": 8,
834
+ "parse_failures": 8,
835
+ "error": "Error code: 400 - {'error': {'message': \"The requested model 'meta-llama/Meta-Llama-3.1-8B-Instruct' does not exist.\", '"
836
+ },
837
+ {
838
+ "task_id": "netweaver_sre_t21",
839
+ "level": "t21",
840
+ "score": 0.4,
841
+ "resolved": false,
842
+ "steps": 8,
843
+ "parse_failures": 8,
844
+ "error": "Error code: 400 - {'error': {'message': \"The requested model 'meta-llama/Meta-Llama-3.1-8B-Instruct' does not exist.\", '"
845
+ },
846
+ {
847
+ "task_id": "netweaver_sre_t22",
848
+ "level": "t22",
849
+ "score": 0.3,
850
+ "resolved": false,
851
+ "steps": 8,
852
+ "parse_failures": 8,
853
+ "error": "Error code: 400 - {'error': {'message': \"The requested model 'meta-llama/Meta-Llama-3.1-8B-Instruct' does not exist.\", '"
854
+ }
855
+ ],
856
+ "available_via_router": false,
857
+ "note": "Not served by HF Router on this account; reported scores are env defaults from UNKNOWN actions, not real capability."
858
+ }
859
+ ],
860
+ "timestamp": "2026-04-25 23:50:48"
861
+ }
scripts/hf_job_grpo_3b.py ADDED
@@ -0,0 +1,363 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # /// script
2
+ # requires-python = ">=3.10"
3
+ # dependencies = [
4
+ # "torch>=2.4.0",
5
+ # "transformers>=4.44.0",
6
+ # "trl>=0.11.0",
7
+ # "peft>=0.12.0",
8
+ # "bitsandbytes>=0.43.0",
9
+ # "datasets>=2.18.0",
10
+ # "huggingface_hub>=0.24.0",
11
+ # "openenv-core>=0.2.3",
12
+ # "requests>=2.31.0",
13
+ # "matplotlib>=3.8.0",
14
+ # "pillow>=10.0.0",
15
+ # "fastapi",
16
+ # "uvicorn",
17
+ # "pydantic>=2.0.0",
18
+ # "accelerate>=0.31.0",
19
+ # ]
20
+ # ///
21
+ """Real GRPO on Qwen2.5-3B-Instruct (4-bit + LoRA) against the live NetWeaver SRE env.
22
+
23
+ Why this is the recipe that should actually move the needle:
24
+ - 3B already gets ~0.92 zero-shot on the env (verified). Capacity is there.
25
+ - GRPO (not SFT) generates k=4 completions per prompt, scores them with the
26
+ rubric, and updates toward the better ones. Even if 3 of 4 fail, the 1
27
+ that succeeds gives a clear positive signal.
28
+ - 4-bit + LoRA (r=16, alpha=32, q+v) keeps VRAM ~6 GB on a10g-small (24 GB).
29
+ - LR 1e-5 + cosine: gentle enough that we don't corrupt the base.
30
+
31
+ Outputs (do NOT clobber the heuristic_training_demo baseline):
32
+ - training_results_grpo_3b.json (source: "grpo_3b_real_run")
33
+ - server/assets/grpo_3b_reward_curve.png
34
+ - server/assets/grpo_3b_before_after.png
35
+ - server/assets/grpo_3b_difficulty_breakdown.png
36
+ """
37
+ from __future__ import annotations
38
+
39
+ import json
40
+ import os
41
+ import shutil
42
+ import subprocess
43
+ import sys
44
+ import time
45
+ from pathlib import Path
46
+
47
+ REPO_ID = "Shasidharyadavr/netweaver_sre"
48
+ WORKDIR = Path("/tmp/netweaver-sre-grpo-3b")
49
+ ENV_URL = os.environ.get("ENV_URL", "https://shasidharyadavr-netweaver-sre.hf.space")
50
+
51
+ # Knobs (override via env)
52
+ os.environ.setdefault("MODEL_NAME", "Qwen/Qwen2.5-3B-Instruct")
53
+ os.environ.setdefault("MAX_TRAIN_STEPS", "30")
54
+ os.environ.setdefault("EVAL_EPISODES", "10")
55
+ os.environ.setdefault("MAX_STEPS_PER_EPISODE", "5")
56
+ os.environ["ENV_URL"] = ENV_URL
57
+
58
+
59
+ def _run(cmd, **kw):
60
+ print(f"\n$ {' '.join(cmd) if isinstance(cmd, list) else cmd}", flush=True)
61
+ return subprocess.run(cmd, check=True, **kw)
62
+
63
+
64
+ # ── 1. Clone the Space repo ──────────────────────────────────────────────────
65
+ if WORKDIR.exists():
66
+ shutil.rmtree(WORKDIR)
67
+ print(f"[1/7] Cloning {REPO_ID} into {WORKDIR}...", flush=True)
68
+ _run(["git", "clone", "--depth", "1",
69
+ f"https://huggingface.co/spaces/{REPO_ID}", str(WORKDIR)])
70
+ os.chdir(WORKDIR)
71
+ sys.path.insert(0, str(WORKDIR))
72
+
73
+
74
+ # ── 2. Sanity-check the live env ─────────────────────────────────────────────
75
+ print(f"\n[2/7] Probing live env at {ENV_URL}...", flush=True)
76
+ import requests # noqa: E402
77
+
78
+ for i in range(15):
79
+ try:
80
+ r = requests.get(f"{ENV_URL}/health", timeout=10)
81
+ if r.status_code == 200:
82
+ print(" health:", r.json(), flush=True)
83
+ break
84
+ except Exception as e:
85
+ print(f" attempt {i+1}/15: {e}", flush=True)
86
+ time.sleep(2)
87
+ else:
88
+ raise RuntimeError(f"Live env at {ENV_URL} unreachable; aborting.")
89
+
90
+
91
+ # ── 3. Reuse the env adapter + reward funcs from train_grpo.py ───────────────
92
+ print(f"\n[3/7] Importing reward functions and EnvAdapter from train_grpo.py...", flush=True)
93
+ import train_grpo # noqa: E402
94
+ from train_grpo import ( # noqa: E402
95
+ EnvAdapter,
96
+ TASK_LEVELS,
97
+ build_prompt,
98
+ clamp_score,
99
+ evaluate_model,
100
+ generate_action,
101
+ parse_action,
102
+ reward_action_parses,
103
+ reward_correct_command,
104
+ reward_episode_resolution,
105
+ run_episode,
106
+ )
107
+
108
+
109
+ # ── 4. Load 3B + 4-bit + LoRA ────────────────────────────────────────────────
110
+ print(f"\n[4/7] Loading {os.environ['MODEL_NAME']} in 4-bit + attaching LoRA...", flush=True)
111
+
112
+ import torch # noqa: E402
113
+ from transformers import ( # noqa: E402
114
+ AutoModelForCausalLM,
115
+ AutoTokenizer,
116
+ BitsAndBytesConfig,
117
+ )
118
+ from peft import ( # noqa: E402
119
+ LoraConfig,
120
+ get_peft_model,
121
+ prepare_model_for_kbit_training,
122
+ )
123
+
124
+ bnb = BitsAndBytesConfig(
125
+ load_in_4bit=True,
126
+ bnb_4bit_compute_dtype=torch.bfloat16,
127
+ bnb_4bit_quant_type="nf4",
128
+ bnb_4bit_use_double_quant=True,
129
+ )
130
+
131
+ MODEL_NAME = os.environ["MODEL_NAME"]
132
+ tokenizer = AutoTokenizer.from_pretrained(MODEL_NAME, trust_remote_code=True)
133
+ if tokenizer.pad_token is None:
134
+ tokenizer.pad_token = tokenizer.eos_token
135
+
136
+ model = AutoModelForCausalLM.from_pretrained(
137
+ MODEL_NAME,
138
+ quantization_config=bnb,
139
+ device_map="auto",
140
+ trust_remote_code=True,
141
+ )
142
+ model = prepare_model_for_kbit_training(model)
143
+ lora_cfg = LoraConfig(
144
+ r=16,
145
+ lora_alpha=32,
146
+ target_modules=["q_proj", "v_proj"],
147
+ lora_dropout=0.05,
148
+ bias="none",
149
+ task_type="CAUSAL_LM",
150
+ )
151
+ model = get_peft_model(model, lora_cfg)
152
+ trainable, total = 0, 0
153
+ for p in model.parameters():
154
+ n = p.numel()
155
+ total += n
156
+ if p.requires_grad:
157
+ trainable += n
158
+ print(f" trainable params: {trainable:,} ({100 * trainable / total:.3f}% of {total:,})",
159
+ flush=True)
160
+
161
+
162
+ # ── 5. BEFORE evaluation ─────────────────────────────────────────────────────
163
+ print(f"\n[5/7] Evaluating BEFORE training ({os.environ['EVAL_EPISODES']} episodes)...",
164
+ flush=True)
165
+ EVAL_EPS = int(os.environ["EVAL_EPISODES"])
166
+ env = EnvAdapter(ENV_URL)
167
+ before_rewards, before_diff = evaluate_model(env, model, tokenizer, episodes=EVAL_EPS)
168
+ before_avg = sum(before_rewards) / max(1, len(before_rewards))
169
+ print(f" before_avg = {before_avg:.3f}", flush=True)
170
+
171
+
172
+ # ── 6. GRPO training ─────────────────────────────────────────────────────────
173
+ print(f"\n[6/7] Starting GRPO training "
174
+ f"({os.environ['MAX_TRAIN_STEPS']} steps, num_generations=4, LR 1e-5)...",
175
+ flush=True)
176
+
177
+ from datasets import Dataset # noqa: E402
178
+ from trl import GRPOConfig, GRPOTrainer # noqa: E402
179
+ import random # noqa: E402
180
+
181
+ # Build a small training dataset of prompts (the env generates fresh state every reset)
182
+ random.seed(7)
183
+ DATASET_SIZE = int(os.environ.get("DATASET_SIZE", "128"))
184
+ rows = []
185
+ for _ in range(DATASET_SIZE):
186
+ level = random.choice(TASK_LEVELS)
187
+ env.set_level(level)
188
+ obs = (env.reset() or {}).get("observation", {}) or {"alert": f"[fallback {level}]"}
189
+ rows.append({"prompt": build_prompt(obs, level)})
190
+ train_dataset = Dataset.from_list(rows)
191
+ print(f" built {len(train_dataset)} prompts (one per task selection)", flush=True)
192
+
193
+ # Inject runtime context onto reward funcs (for the rollout-based reward)
194
+ reward_episode_resolution.env = env # type: ignore[attr-defined]
195
+ reward_episode_resolution.model = model # type: ignore[attr-defined]
196
+ reward_episode_resolution.tokenizer = tokenizer # type: ignore[attr-defined]
197
+
198
+ config = GRPOConfig(
199
+ output_dir="grpo_3b_qwen",
200
+ max_steps=int(os.environ["MAX_TRAIN_STEPS"]),
201
+ per_device_train_batch_size=4, # matches num_generations
202
+ num_generations=4, # 3B is diverse enough for k=4
203
+ gradient_accumulation_steps=1,
204
+ learning_rate=1e-5, # gentle; LoRA + base preservation
205
+ warmup_ratio=0.05,
206
+ weight_decay=0.01,
207
+ lr_scheduler_type="cosine",
208
+ optim="adamw_torch", # avoid bitsandbytes optim with 4-bit base
209
+ logging_steps=1,
210
+ save_steps=max(50, int(os.environ["MAX_TRAIN_STEPS"])), # don't checkpoint mid-run
211
+ report_to=[],
212
+ max_completion_length=128,
213
+ bf16=True,
214
+ )
215
+ trainer = GRPOTrainer(
216
+ model=model,
217
+ reward_funcs=[
218
+ reward_action_parses,
219
+ reward_correct_command,
220
+ reward_episode_resolution,
221
+ ],
222
+ args=config,
223
+ train_dataset=train_dataset,
224
+ processing_class=tokenizer,
225
+ )
226
+
227
+ t0 = time.time()
228
+ trainer.train()
229
+ print(f"\n GRPO training done in {time.time() - t0:.1f}s", flush=True)
230
+
231
+
232
+ # ── 7. AFTER eval + save + upload ────────────────────────────────────────────
233
+ print(f"\n[7/7] Evaluating AFTER training ({EVAL_EPS} episodes)...", flush=True)
234
+ after_rewards, after_diff = evaluate_model(env, model, tokenizer, episodes=EVAL_EPS)
235
+ after_avg = sum(after_rewards) / max(1, len(after_rewards))
236
+ delta = after_avg - before_avg
237
+
238
+ print(f"\n=== GRPO 3B TRAINING SUMMARY ===")
239
+ print(f" source : grpo_3b_real_run")
240
+ print(f" model : {MODEL_NAME}")
241
+ print(f" trainable : {trainable:,} ({100 * trainable / total:.3f}%)")
242
+ print(f" before avg : {before_avg:.3f}")
243
+ print(f" after avg : {after_avg:.3f}")
244
+ print(f" delta : {delta:+.3f}")
245
+
246
+ results = {
247
+ "env_url": ENV_URL,
248
+ "model_name": MODEL_NAME,
249
+ "training_method": "GRPO with 4-bit + LoRA r=16 (q_proj, v_proj)",
250
+ "max_train_steps": int(os.environ["MAX_TRAIN_STEPS"]),
251
+ "num_generations": 4,
252
+ "learning_rate": 1e-5,
253
+ "trainable_params": trainable,
254
+ "total_params": total,
255
+ "training_rewards": train_grpo.TRAIN_REWARDS[:int(os.environ["MAX_TRAIN_STEPS"])],
256
+ "before_rewards": before_rewards,
257
+ "after_rewards": after_rewards,
258
+ "difficulty_breakdown": {"before": before_diff, "after": after_diff},
259
+ "timestamp": time.strftime("%Y-%m-%d %H:%M:%S"),
260
+ "source": "grpo_3b_real_run",
261
+ "job_flavor": os.environ.get("HF_JOB_FLAVOR", "a10g-small"),
262
+ "notes": (
263
+ "Real GRPO run on Qwen2.5-3B-Instruct (4-bit + LoRA r=16) against the "
264
+ "live NetWeaver SRE env. 3 composed reward functions: parse + correct "
265
+ "command + rollout-based grader score. Heuristic_training_demo data on "
266
+ "the Space is preserved (separate filenames)."
267
+ ),
268
+ }
269
+ with open("training_results_grpo_3b.json", "w") as f:
270
+ json.dump(results, f, indent=2)
271
+ print(f" saved: training_results_grpo_3b.json", flush=True)
272
+
273
+ # Render plots (reuse the Pillow renderer from run_training_demo.py β€” works
274
+ # without matplotlib if its DLLs are missing, and is also fine with matplotlib
275
+ # already installed).
276
+ try:
277
+ import matplotlib
278
+ matplotlib.use("Agg")
279
+ import matplotlib.pyplot as plt
280
+
281
+ tr = results["training_rewards"]
282
+ plt.figure(figsize=(10, 5))
283
+ if tr:
284
+ plt.plot(range(1, len(tr) + 1), tr, linewidth=1.6, color="#2563eb",
285
+ alpha=0.55, label="Per-episode reward")
286
+ if len(tr) >= 5:
287
+ ma = [sum(tr[max(0, i - 4):i + 1]) / min(5, i + 1) for i in range(len(tr))]
288
+ plt.plot(range(1, len(ma) + 1), ma, linewidth=2.6, color="#dc2626",
289
+ label="5-step moving average")
290
+ plt.xlabel("GRPO training step")
291
+ plt.ylabel("Reward (rubric score)")
292
+ plt.title(f"GRPO 3B Reward Curve β€” NetWeaver SRE ({MODEL_NAME})")
293
+ plt.grid(alpha=0.3)
294
+ plt.legend()
295
+ plt.tight_layout()
296
+ plt.savefig("server/assets/grpo_3b_reward_curve.png", dpi=160)
297
+ plt.close()
298
+
299
+ plt.figure(figsize=(6, 5))
300
+ bars = plt.bar(["Before (zero-shot)", "After (GRPO)"], [before_avg, after_avg],
301
+ color=["#7c3aed", "#16a34a"])
302
+ plt.ylim(0.0, 1.0)
303
+ plt.ylabel("Average reward")
304
+ plt.title(f"Qwen2.5-3B: Zero-shot vs GRPO-trained")
305
+ for b, v in zip(bars, [before_avg, after_avg]):
306
+ plt.text(b.get_x() + b.get_width() / 2, v + 0.02, f"{v:.3f}",
307
+ ha="center", va="bottom", fontsize=11, fontweight="bold")
308
+ plt.tight_layout()
309
+ plt.savefig("server/assets/grpo_3b_before_after.png", dpi=160)
310
+ plt.close()
311
+
312
+ labels = ["easy", "medium", "hard"]
313
+ bvals = [before_diff.get(k, 0.001) for k in labels]
314
+ avals = [after_diff.get(k, 0.001) for k in labels]
315
+ x = list(range(len(labels)))
316
+ width = 0.36
317
+ plt.figure(figsize=(8, 5))
318
+ plt.bar([i - width / 2 for i in x], bvals, width=width, label="Before (zero-shot)",
319
+ color="#9333ea")
320
+ plt.bar([i + width / 2 for i in x], avals, width=width, label="After (GRPO)",
321
+ color="#16a34a")
322
+ plt.xticks(x, [s.title() for s in labels])
323
+ plt.ylim(0.0, 1.0)
324
+ plt.ylabel("Average reward")
325
+ plt.title("3B Reward by Difficulty β€” Zero-shot vs GRPO")
326
+ plt.legend()
327
+ for i, (b, a) in enumerate(zip(bvals, avals)):
328
+ plt.text(i - width / 2, b + 0.02, f"{b:.2f}", ha="center", fontsize=9)
329
+ plt.text(i + width / 2, a + 0.02, f"{a:.2f}", ha="center", fontsize=9)
330
+ plt.tight_layout()
331
+ plt.savefig("server/assets/grpo_3b_difficulty_breakdown.png", dpi=160)
332
+ plt.close()
333
+ print(f" saved 3 plots to server/assets/grpo_3b_*.png", flush=True)
334
+ except Exception as e:
335
+ print(f"[WARN] plotting failed: {e}", flush=True)
336
+
337
+
338
+ # ── 8. Upload to HF Space (separate filenames, won't clobber heuristic) ─────
339
+ print(f"\nUploading 3B GRPO results to {REPO_ID}...", flush=True)
340
+ from huggingface_hub import HfApi # noqa: E402
341
+
342
+ api = HfApi(token=os.environ["HF_TOKEN"])
343
+ uploads = [
344
+ "training_results_grpo_3b.json",
345
+ "server/assets/grpo_3b_reward_curve.png",
346
+ "server/assets/grpo_3b_before_after.png",
347
+ "server/assets/grpo_3b_difficulty_breakdown.png",
348
+ ]
349
+ commit_msg = (f"GRPO 3B run: {MODEL_NAME} {os.environ['MAX_TRAIN_STEPS']}steps "
350
+ f"{before_avg:.3f}->{after_avg:.3f} delta={delta:+.3f}")
351
+ for rel in uploads:
352
+ p = WORKDIR / rel
353
+ if not p.exists():
354
+ print(f" SKIP (not found): {rel}", flush=True)
355
+ continue
356
+ api.upload_file(
357
+ path_or_fileobj=str(p), path_in_repo=rel,
358
+ repo_id=REPO_ID, repo_type="space",
359
+ commit_message=commit_msg,
360
+ )
361
+ print(f" uploaded: {rel} ({p.stat().st_size} bytes)", flush=True)
362
+
363
+ print(f"\nDone. heuristic_training_demo baseline on Space is preserved.")
scripts/run_leaderboard.py ADDED
@@ -0,0 +1,301 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Multi-model inference leaderboard for NetWeaver SRE.
2
+
3
+ Runs the 22 tasks against the live env via the HuggingFace Router API for
4
+ each model in MODELS, collecting per-model rubric scores. Produces:
5
+ - leaderboard_results.json
6
+ - server/assets/leaderboard.png
7
+
8
+ This is the "scaling curve" evidence for the README: bigger models score
9
+ better on this env, which proves the env actually differentiates capability.
10
+
11
+ Usage:
12
+ HF_TOKEN=hf_xxx ENV_URL=https://shasidharyadavr-netweaver-sre.hf.space \
13
+ python scripts/run_leaderboard.py
14
+
15
+ Cost: depends on HF Router pricing per model. Roughly $1-3 total for the
16
+ default model set with temperature=0.1 and max_tokens=150.
17
+ """
18
+ from __future__ import annotations
19
+
20
+ import json
21
+ import os
22
+ import re
23
+ import sys
24
+ import time
25
+ from typing import Dict, List
26
+
27
+ # Local imports
28
+ HERE = os.path.dirname(os.path.abspath(__file__))
29
+ ROOT = os.path.dirname(HERE)
30
+ sys.path.insert(0, ROOT)
31
+
32
+ import requests # noqa: E402
33
+ from openai import OpenAI # noqa: E402
34
+
35
+ from inference import ( # noqa: E402
36
+ SYSTEM_PROMPT,
37
+ TASK_HINTS,
38
+ TASKS,
39
+ )
40
+
41
+
42
+ ENV_URL = os.environ.get("ENV_URL", "https://shasidharyadavr-netweaver-sre.hf.space").rstrip("/")
43
+ API_BASE = os.environ.get("API_BASE_URL", "https://router.huggingface.co/v1")
44
+ API_KEY = os.environ.get("API_KEY") or os.environ.get("HF_TOKEN", "")
45
+ MAX_STEPS = int(os.environ.get("MAX_STEPS_PER_EPISODE", "8"))
46
+
47
+ if not API_KEY:
48
+ raise SystemExit(
49
+ "HF_TOKEN (or API_KEY) is required so the script can call the HF Router API."
50
+ )
51
+
52
+ MODELS_DEFAULT = [
53
+ "Qwen/Qwen2.5-0.5B-Instruct",
54
+ "Qwen/Qwen2.5-3B-Instruct",
55
+ "Qwen/Qwen2.5-7B-Instruct",
56
+ "meta-llama/Meta-Llama-3.1-8B-Instruct",
57
+ ]
58
+ MODELS = [m.strip() for m in os.environ.get("MODELS", ",".join(MODELS_DEFAULT)).split(",") if m.strip()]
59
+
60
+
61
+ # ── Single-task episode against the live env, scored by /grader ──────────────
62
+
63
+ def env_call(endpoint: str, payload=None, method: str = "POST") -> dict:
64
+ url = f"{ENV_URL}/{endpoint}"
65
+ if method == "GET":
66
+ r = requests.get(url, timeout=60)
67
+ else:
68
+ r = requests.post(url, json=payload or {}, timeout=60)
69
+ r.raise_for_status()
70
+ return r.json()
71
+
72
+
73
+ def parse_action_safe(text: str) -> dict:
74
+ """Extract a {command, target, value} dict from arbitrary model text."""
75
+ text = text or ""
76
+ out = {"command": "UNKNOWN", "target": "unknown", "value": None}
77
+ matches = re.findall(r"\{[\s\S]*?\}", text)
78
+ if not matches and "{" in text and "}" in text:
79
+ matches = [text[text.find("{"):text.rfind("}") + 1]]
80
+ for cand in reversed(matches):
81
+ try:
82
+ data = json.loads(cand)
83
+ if isinstance(data, dict) and "command" in data:
84
+ out["command"] = str(data.get("command", "UNKNOWN")).upper().strip()
85
+ out["target"] = str(data.get("target", "unknown")).strip()
86
+ raw = data.get("value")
87
+ if raw is None:
88
+ out["value"] = None
89
+ elif isinstance(raw, bool):
90
+ out["value"] = int(raw)
91
+ elif isinstance(raw, (int, float)):
92
+ out["value"] = int(raw)
93
+ elif isinstance(raw, str):
94
+ s = raw.strip().lower()
95
+ if s in {"none", "null", "nil", "", "n/a"}:
96
+ out["value"] = None
97
+ else:
98
+ try:
99
+ out["value"] = int(float(s))
100
+ except (ValueError, TypeError):
101
+ out["value"] = None
102
+ return out
103
+ except Exception:
104
+ continue
105
+ return out
106
+
107
+
108
+ def run_episode(client: OpenAI, model: str, task_id: str, level: str) -> dict:
109
+ rewards: List[float] = []
110
+ prev_actions: List[str] = []
111
+ score = 0.001
112
+ resolved = False
113
+ steps = 0
114
+ parse_failures = 0
115
+ error_msg = None
116
+
117
+ try:
118
+ env_call("set_level", {"task_level": level})
119
+ resp = env_call("reset", {})
120
+ done = bool(resp.get("done", False))
121
+ obs = resp.get("observation", {})
122
+
123
+ for step in range(1, MAX_STEPS + 1):
124
+ if done:
125
+ break
126
+ steps = step
127
+ sys_msg = SYSTEM_PROMPT.format(
128
+ alert=obs.get("alert", ""),
129
+ logs=json.dumps(obs.get("hardware_logs", [])),
130
+ q=json.dumps(obs.get("queue_depths", {})),
131
+ v=json.dumps(obs.get("gradient_variances", [])),
132
+ m=json.dumps(obs.get("gpu_memory_usage", [])),
133
+ health=obs.get("system_health", 1.0),
134
+ step=step,
135
+ prev_actions=json.dumps(prev_actions[-5:]),
136
+ hint=TASK_HINTS.get(level, ""),
137
+ )
138
+ try:
139
+ ans = client.chat.completions.create(
140
+ model=model,
141
+ messages=[{"role": "user", "content": sys_msg}],
142
+ max_tokens=150,
143
+ temperature=0.1,
144
+ )
145
+ payload = parse_action_safe(ans.choices[0].message.content)
146
+ except Exception as e:
147
+ parse_failures += 1
148
+ payload = {"command": "UNKNOWN", "target": "unknown", "value": None}
149
+ # Note the API error but keep going; the grader will reflect failure
150
+ if error_msg is None:
151
+ error_msg = str(e)[:120]
152
+
153
+ prev_actions.append(payload["command"])
154
+ try:
155
+ step_resp = env_call("step", {"action": payload})
156
+ done = bool(step_resp.get("done", False))
157
+ rewards.append(float(step_resp.get("reward", 0.0)))
158
+ obs = step_resp.get("observation", {})
159
+ except Exception as e:
160
+ error_msg = str(e)[:120]
161
+ break
162
+
163
+ try:
164
+ grader = env_call("grader", method="GET")
165
+ score = float(grader.get("total", 0.001))
166
+ resolved = bool(grader.get("resolved", False))
167
+ except Exception as e:
168
+ error_msg = str(e)[:120]
169
+ score = max(0.001, min(0.999, rewards[-1] if rewards else 0.001))
170
+ except Exception as e:
171
+ error_msg = str(e)[:120]
172
+
173
+ return {
174
+ "task_id": task_id,
175
+ "level": level,
176
+ "score": round(max(0.001, min(0.999, score)), 4),
177
+ "resolved": resolved,
178
+ "steps": steps,
179
+ "parse_failures": parse_failures,
180
+ "error": error_msg,
181
+ }
182
+
183
+
184
+ # ── Per-model run + summary ─────────────────────────────────────────────────
185
+
186
+ def run_model(model: str) -> dict:
187
+ print(f"\n[MODEL] {model}", flush=True)
188
+ client = OpenAI(api_key=API_KEY, base_url=API_BASE)
189
+ t0 = time.time()
190
+ per_task = []
191
+ for task_id, _difficulty, level in TASKS:
192
+ r = run_episode(client, model, task_id, level)
193
+ per_task.append(r)
194
+ marker = "OK " if r["resolved"] else "..."
195
+ print(f" {marker} {task_id:<24} score={r['score']:.3f} steps={r['steps']}",
196
+ flush=True)
197
+ elapsed = time.time() - t0
198
+ by_diff: Dict[str, List[float]] = {"easy": [], "medium": [], "hard": []}
199
+ for r, (_tid, diff, _lvl) in zip(per_task, TASKS):
200
+ by_diff[diff].append(r["score"])
201
+ avg = sum(r["score"] for r in per_task) / max(1, len(per_task))
202
+ resolved_n = sum(1 for r in per_task if r["resolved"])
203
+ summary = {
204
+ "model": model,
205
+ "avg_score": round(avg, 4),
206
+ "resolved": resolved_n,
207
+ "total": len(per_task),
208
+ "by_difficulty": {k: round(sum(v) / len(v), 4) if v else 0.001
209
+ for k, v in by_diff.items()},
210
+ "elapsed_sec": round(elapsed, 1),
211
+ "tasks": per_task,
212
+ }
213
+ print(f" [{model}] avg={avg:.3f} resolved={resolved_n}/22 in {elapsed:.1f}s",
214
+ flush=True)
215
+ return summary
216
+
217
+
218
+ def main():
219
+ print(f"=== NetWeaver SRE Leaderboard ===")
220
+ print(f" ENV_URL = {ENV_URL}")
221
+ print(f" models = {MODELS}")
222
+ print(f" tasks = {len(TASKS)} per model", flush=True)
223
+
224
+ # Pre-flight: env is reachable
225
+ try:
226
+ h = env_call("health", method="GET")
227
+ print(f" env health: {h}", flush=True)
228
+ except Exception as e:
229
+ raise SystemExit(f"Env at {ENV_URL} unreachable: {e}")
230
+
231
+ results = {"env_url": ENV_URL, "models": [], "timestamp": time.strftime("%Y-%m-%d %H:%M:%S")}
232
+ for m in MODELS:
233
+ try:
234
+ results["models"].append(run_model(m))
235
+ except Exception as e:
236
+ print(f"[ERROR] {m} failed entirely: {e}", flush=True)
237
+ results["models"].append({"model": m, "error": str(e), "avg_score": 0.001})
238
+
239
+ with open("leaderboard_results.json", "w") as f:
240
+ json.dump(results, f, indent=2)
241
+ print(f"\nSaved: leaderboard_results.json", flush=True)
242
+
243
+ # Plot
244
+ try:
245
+ _plot_leaderboard(results)
246
+ print(f"Saved: server/assets/leaderboard.png", flush=True)
247
+ except Exception as e:
248
+ print(f"[WARN] plot failed: {e}", flush=True)
249
+
250
+ print(f"\n=== Summary ===")
251
+ for m in results["models"]:
252
+ avg = m.get("avg_score", 0.001)
253
+ resolved = m.get("resolved", "?")
254
+ total = m.get("total", "?")
255
+ print(f" {m['model']:<48} avg={avg:.3f} resolved={resolved}/{total}")
256
+
257
+
258
+ def _plot_leaderboard(results: dict) -> None:
259
+ import matplotlib
260
+ matplotlib.use("Agg")
261
+ import matplotlib.pyplot as plt
262
+
263
+ os.makedirs("server/assets", exist_ok=True)
264
+
265
+ valid = [m for m in results["models"] if m.get("avg_score", 0.001) > 0.001]
266
+ if not valid:
267
+ return
268
+
269
+ # Short labels for the x-axis
270
+ def short(name: str) -> str:
271
+ return name.split("/")[-1].replace("-Instruct", "")
272
+
273
+ labels = [short(m["model"]) for m in valid]
274
+ scores = [m["avg_score"] for m in valid]
275
+ resolved_pct = [100 * m.get("resolved", 0) / max(1, m.get("total", 1)) for m in valid]
276
+
277
+ fig, ax1 = plt.subplots(figsize=(10, 5.5))
278
+ color1 = "#2563eb"
279
+ ax1.bar(labels, scores, color=color1, alpha=0.85, label="Avg rubric score")
280
+ ax1.set_ylabel("Avg rubric score (clamped 0.001-0.999)", color=color1)
281
+ ax1.set_ylim(0.0, 1.0)
282
+ ax1.tick_params(axis="y", labelcolor=color1)
283
+ for i, (s, label) in enumerate(zip(scores, labels)):
284
+ ax1.text(i, s + 0.02, f"{s:.3f}", ha="center", fontsize=10, fontweight="bold")
285
+
286
+ ax2 = ax1.twinx()
287
+ color2 = "#dc2626"
288
+ ax2.plot(labels, resolved_pct, color=color2, marker="o", linewidth=2.4,
289
+ label="% tasks resolved")
290
+ ax2.set_ylabel("% of 22 tasks resolved", color=color2)
291
+ ax2.set_ylim(0.0, 100.0)
292
+ ax2.tick_params(axis="y", labelcolor=color2)
293
+
294
+ plt.title("NetWeaver SRE β€” Zero-shot Inference Leaderboard")
295
+ fig.tight_layout()
296
+ plt.savefig("server/assets/leaderboard.png", dpi=160)
297
+ plt.close()
298
+
299
+
300
+ if __name__ == "__main__":
301
+ main()
server/assets/leaderboard.png ADDED

Git LFS Details

  • SHA256: 14547340298ce2e505077ee740b615002b46d25289c5e844e0a2e92b1bad4aff
  • Pointer size: 130 Bytes
  • Size of remote file: 64.6 kB