ybkim95 commited on
Commit
35bbe3b
·
verified ·
1 Parent(s): 074c1f1

Rebuild Space with Leaderboard / Instructions / Submit tabs and verified CLI commands

Browse files
Files changed (3) hide show
  1. README.md +5 -12
  2. app.py +175 -752
  3. requirements.txt +2 -2
README.md CHANGED
@@ -2,9 +2,9 @@
2
  title: TeamBench Leaderboard
3
  emoji: 📊
4
  colorFrom: blue
5
- colorTo: green
6
  sdk: gradio
7
- sdk_version: "5.12.0"
8
  python_version: "3.12"
9
  app_file: app.py
10
  pinned: true
@@ -13,14 +13,7 @@ license: mit
13
 
14
  # TeamBench Leaderboard
15
 
16
- Multi-agent benchmark evaluating LLM teamwork with OS-enforced role separation.
17
 
18
- ## Running locally
19
-
20
- ```bash
21
- cd leaderboard
22
- pip install -r requirements.txt
23
- python app.py
24
- ```
25
-
26
- The app loads data from `data/cross_model_stats.json`.
 
2
  title: TeamBench Leaderboard
3
  emoji: 📊
4
  colorFrom: blue
5
+ colorTo: indigo
6
  sdk: gradio
7
+ sdk_version: 5.12.0
8
  python_version: "3.12"
9
  app_file: app.py
10
  pinned: true
 
13
 
14
  # TeamBench Leaderboard
15
 
16
+ Multi-agent LLM coordination on 90 stratified tasks under OS-enforced Planner / Executor / Verifier role separation. Three tabs: **Leaderboard** (current results), **Instructions** (install + run + grade), **Submit** (upload your results JSON).
17
 
18
+ - Code: <https://github.com/ybkim95/TeamBench>
19
+ - Dataset: <https://huggingface.co/datasets/ybkim95/teambench>
 
 
 
 
 
 
 
app.py CHANGED
@@ -1,803 +1,226 @@
1
- """
2
- TeamBench Leaderboard — HuggingFace Spaces Gradio Application
3
 
4
- Evaluates LLM teamwork via OS-enforced role separation (Planner/Executor/Verifier).
5
- Primary metric: TNI (Teamwork Necessity Index).
 
 
6
  """
7
-
8
  from __future__ import annotations
9
-
10
  import json
11
  import os
12
- import zipfile
13
- import tempfile
14
  from pathlib import Path
15
 
16
  import gradio as gr
17
  import pandas as pd
18
 
19
- # ---------------------------------------------------------------------------
20
- # Data loading
21
- # ---------------------------------------------------------------------------
22
-
23
- DATA_DIR = Path(__file__).parent / "data"
24
- SUBMISSIONS_DIR = Path(__file__).parent / "submissions"
25
  SUBMISSIONS_DIR.mkdir(exist_ok=True)
26
 
27
- _data: dict = {}
28
-
29
-
30
- def _load() -> dict:
31
- global _data
32
- if _data:
33
- return _data
34
- path = DATA_DIR / "leaderboard_data.json"
35
- with open(path) as f:
36
- _data = json.load(f)
37
- return _data
38
-
39
-
40
- # ---------------------------------------------------------------------------
41
- # Display name maps
42
- # ---------------------------------------------------------------------------
43
-
44
- MODEL_DISPLAY = {
45
- "gemini-3-flash-preview": "Gemini 3 Flash",
46
- "gemini-3.1-flash-lite-preview": "Gemini 3.1 Flash Lite",
47
- "gpt-5-mini": "GPT-5 Mini",
48
- "gpt-5-nano": "GPT-5 Nano",
49
- }
50
-
51
- # ---------------------------------------------------------------------------
52
- # CSS
53
- # ---------------------------------------------------------------------------
54
-
55
- CUSTOM_CSS = """
56
- :root {
57
- --bench-blue: #2563eb; --bench-green: #16a34a; --bench-red: #dc2626;
58
- --bench-amber: #d97706; --bench-purple: #7c3aed; --bench-gray: #6b7280;
59
- --badge-radius: 4px;
60
- }
61
-
62
- .tb-header {
63
- background: linear-gradient(135deg, #1e3a8a 0%, #1d4ed8 50%, #2563eb 100%);
64
- border-radius: 12px; padding: 28px 32px 24px; margin-bottom: 8px;
65
- }
66
- .tb-header h1 {
67
- margin: 0 0 6px; font-size: 2rem; font-weight: 700;
68
- letter-spacing: -0.5px; color: #ffffff !important;
69
- }
70
- .tb-header p {
71
- margin: 0; font-size: 1rem; color: #ffffff !important; opacity: 0.92;
72
- }
73
-
74
- .stat-grid {
75
- display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
76
- gap: 12px; margin-bottom: 16px;
77
- }
78
- .stat-card {
79
- background: var(--background-fill-primary, #fff);
80
- border: 1px solid var(--border-color-primary, #e5e7eb);
81
- border-radius: 10px; padding: 16px 18px; text-align: center;
82
- }
83
- .stat-card .stat-value { font-size: 1.5rem; font-weight: 700; color: var(--bench-blue); line-height: 1; }
84
- .stat-card .stat-label { font-size: 0.78rem; color: var(--bench-gray); margin-top: 4px; }
85
-
86
- .badge-HIGH-TNI { background:#dbeafe; color:#1e40af; padding:2px 8px; border-radius:var(--badge-radius); font-size:0.78rem; font-weight:600; }
87
- .badge-TEAM-HELPS { background:#dcfce7; color:#166534; padding:2px 8px; border-radius:var(--badge-radius); font-size:0.78rem; font-weight:600; }
88
- .badge-NEUTRAL { background:#f3f4f6; color:#374151; padding:2px 8px; border-radius:var(--badge-radius); font-size:0.78rem; font-weight:600; }
89
- .badge-TEAM-HURTS { background:#fee2e2; color:#991b1b; padding:2px 8px; border-radius:var(--badge-radius); font-size:0.78rem; font-weight:600; }
90
-
91
- .section-title {
92
- font-size: 1.05rem; font-weight: 600; margin: 20px 0 10px;
93
- padding-bottom: 6px; border-bottom: 2px solid var(--border-color-primary, #e5e7eb);
94
- }
95
-
96
- .about-card {
97
- background: var(--background-fill-primary, #fff);
98
- border: 1px solid var(--border-color-primary, #e5e7eb);
99
- border-radius: 10px; padding: 20px 24px; margin-bottom: 12px;
100
- }
101
- .about-card h3 { margin: 0 0 10px; font-size: 1rem; font-weight: 700; }
102
- .about-card p, .about-card li { font-size: 0.9rem; line-height: 1.65; }
103
- .about-card ul { padding-left: 20px; margin: 8px 0; }
104
-
105
- .role-planner { background:#ede9fe; color:#5b21b6; padding:3px 10px; border-radius:20px; font-size:0.82rem; font-weight:600; }
106
- .role-executor { background:#fef3c7; color:#92400e; padding:3px 10px; border-radius:20px; font-size:0.82rem; font-weight:600; }
107
- .role-verifier { background:#d1fae5; color:#065f46; padding:3px 10px; border-radius:20px; font-size:0.82rem; font-weight:600; }
108
-
109
- .submit-info {
110
- background: #eff6ff; border: 1px solid #bfdbfe; border-radius: 10px;
111
- padding: 18px 22px; margin-bottom: 16px; font-size: 0.9rem; line-height: 1.7;
112
- }
113
- .submit-info code {
114
- background: #dbeafe; padding: 2px 6px; border-radius: 4px; font-size: 0.82rem;
115
- }
116
- .submit-step {
117
- background: var(--background-fill-primary, #fff);
118
- border: 1px solid var(--border-color-primary, #e5e7eb);
119
- border-radius: 10px; padding: 16px 20px; margin-bottom: 12px;
120
- }
121
- .submit-step h4 { margin: 0 0 8px; font-size: 0.95rem; }
122
-
123
- .gr-dataframe table { width: 100%; font-size: 0.875rem; }
124
- .gr-dataframe th { font-weight: 600; }
125
- """
126
-
127
- # ---------------------------------------------------------------------------
128
- # HTML builders
129
- # ---------------------------------------------------------------------------
130
-
131
- def create_header() -> str:
132
- return """
133
- <div class="tb-header">
134
- <h1>TeamBench Leaderboard</h1>
135
- <p>Multi-agent benchmark evaluating LLM teamwork with OS-enforced role separation</p>
136
- </div>
137
- """
138
-
139
-
140
- def create_stat_cards() -> str:
141
- d = _load()
142
- agg = d["aggregate"]
143
- n = d["total_tasks"]
144
- n_hard = d["total_hard"]
145
- cats = len(d["categories"])
146
- helps = agg["team_helps_count"]
147
- high_tni = agg["high_tni_count"]
148
- avg_tni = agg["avg_tni"]
149
- return f"""
150
- <div class="stat-grid">
151
- <div class="stat-card">
152
- <div class="stat-value">{n}</div>
153
- <div class="stat-label">Total Tasks</div>
154
- </div>
155
- <div class="stat-card">
156
- <div class="stat-value">{n_hard}</div>
157
- <div class="stat-label">Hard / Expert Tasks</div>
158
- </div>
159
- <div class="stat-card">
160
- <div class="stat-value">{cats}</div>
161
- <div class="stat-label">Categories</div>
162
- </div>
163
- <div class="stat-card">
164
- <div class="stat-value">{helps}</div>
165
- <div class="stat-label">Team-Helps Tasks</div>
166
- </div>
167
- <div class="stat-card">
168
- <div class="stat-value">{high_tni}</div>
169
- <div class="stat-label">High-TNI (team &ge; oracle)</div>
170
- </div>
171
- <div class="stat-card">
172
- <div class="stat-value">{avg_tni:.2f}</div>
173
- <div class="stat-label">Avg TNI</div>
174
- </div>
175
- </div>
176
- """
177
-
178
-
179
  # ---------------------------------------------------------------------------
180
- # Tab: Performance Cross-Model (28 tasks, 4 models)
 
181
  # ---------------------------------------------------------------------------
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
182
 
183
- def build_crossmodel_leaderboard() -> pd.DataFrame:
184
- d = _load()
185
- cm = d["cross_model"]
186
- models_ordered = sorted(cm["per_model"].items(),
187
- key=lambda kv: kv[1]["avg_uplift"], reverse=True)
188
- rows = []
189
- for i, (mid, stats) in enumerate(models_ordered):
190
- u = stats["avg_uplift"]
191
- rows.append({
192
- "Rank": f"#{i+1}",
193
- "Model": MODEL_DISPLAY.get(mid, mid),
194
- "Team Score": f"{stats['avg_team']:.1%}",
195
- "Oracle Score": f"{stats['avg_oracle']:.1%}",
196
- "Team Uplift": f"+{u:.1%}" if u >= 0 else f"{u:.1%}",
197
- "Avg TNI": f"{stats['avg_tni']:.3f}",
198
- "Team > Oracle": f"{stats['team_helps_pct']:.0%}",
199
- })
200
- return pd.DataFrame(rows)
201
-
202
-
203
- def build_crossmodel_task_df(category: str = "All",
204
- classification: str = "All") -> pd.DataFrame:
205
- d = _load()
206
- cm = d["cross_model"]
207
- models = cm["models"]
208
-
209
- rows = []
210
- for task in cm["per_task"]:
211
- cat = task["category"]
212
- if category != "All" and cat != category:
213
- continue
214
-
215
- classes = [task["models"][m]["classification"] for m in models if m in task["models"]]
216
- counts: dict[str, int] = {}
217
- for c in classes:
218
- counts[c] = counts.get(c, 0) + 1
219
- agg_class = max(counts, key=counts.get) if counts else "NEUTRAL"
220
- if classification != "All" and agg_class != classification:
221
- continue
222
-
223
- row: dict = {"Task": task["task_id"], "Category": cat}
224
- for mid in models:
225
- short = MODEL_DISPLAY.get(mid, mid)
226
- mdata = task["models"].get(mid, {})
227
- team = mdata.get("team", 0) or 0
228
- uplift = mdata.get("team_uplift", 0) or 0
229
- row[f"{short}"] = f"{team:.0%}"
230
- row[f"{short} Uplift"] = f"+{uplift:.0%}" if uplift >= 0 else f"{uplift:.0%}"
231
- row["Class"] = agg_class
232
- rows.append(row)
233
-
234
- if not rows:
235
- return pd.DataFrame()
236
- return pd.DataFrame(rows).sort_values("Task").reset_index(drop=True)
237
 
238
-
239
- # ---------------------------------------------------------------------------
240
- # Tab: Overview — All 155 tasks (reference model)
241
- # ---------------------------------------------------------------------------
242
-
243
- def build_overview_df(track: str = "Full", category: str = "All",
244
- classification: str = "All") -> pd.DataFrame:
245
- d = _load()
246
- rows = []
247
- for t in d["per_task"]:
248
- if track == "Hard" and t["difficulty_track"] != "hard":
249
- continue
250
- if category != "All" and t["category"] != category:
251
- continue
252
- if classification != "All" and t["classification"] != classification:
253
- continue
254
- uplift = t["team_uplift"]
255
- rows.append({
256
- "Task": t["task_id"],
257
- "Category": t["category"],
258
- "Difficulty": t["difficulty"].capitalize(),
259
- "Oracle": f"{t['oracle']:.0%}",
260
- "Restricted": f"{t['restricted']:.0%}",
261
- "Team": f"{t['team']:.0%}",
262
- "Uplift": f"+{uplift:.0%}" if uplift >= 0 else f"{uplift:.0%}",
263
- "TNI": f"{t['tni']:.2f}" if t["tni"] is not None else "—",
264
- "Class": t["classification"],
265
- })
266
- if not rows:
267
- return pd.DataFrame()
268
- return pd.DataFrame(rows).sort_values("Task").reset_index(drop=True)
269
-
270
-
271
- def build_category_summary_df(track: str = "Full") -> pd.DataFrame:
272
- d = _load()
273
- cat_data: dict[str, list] = {}
274
- for t in d["per_task"]:
275
- if track == "Hard" and t["difficulty_track"] != "hard":
276
- continue
277
- cat = t["category"]
278
- if cat not in cat_data:
279
- cat_data[cat] = []
280
- cat_data[cat].append(t)
281
-
282
- rows = []
283
- for cat in sorted(cat_data):
284
- tasks = cat_data[cat]
285
- n = len(tasks)
286
- avg_team = sum(t["team"] for t in tasks) / n
287
- avg_oracle = sum(t["oracle"] for t in tasks) / n
288
- avg_uplift = sum(t["team_uplift"] for t in tasks) / n
289
- helps = sum(1 for t in tasks if t["team_uplift"] > 0.05)
290
- high = sum(1 for t in tasks if t["classification"] == "HIGH-TNI")
291
- rows.append({
292
- "Category": cat,
293
- "Tasks": n,
294
- "Avg Team": f"{avg_team:.0%}",
295
- "Avg Oracle": f"{avg_oracle:.0%}",
296
- "Avg Uplift": f"+{avg_uplift:.0%}" if avg_uplift >= 0 else f"{avg_uplift:.0%}",
297
- "Team Helps": f"{helps}/{n}",
298
- "High-TNI": high,
299
- })
300
- return pd.DataFrame(rows).sort_values("Category").reset_index(drop=True)
301
 
302
 
303
  # ---------------------------------------------------------------------------
304
- # Submission handler — workspace artifact zip (server-graded)
305
  # ---------------------------------------------------------------------------
306
-
307
- MAX_UPLOAD_MB = 100
308
 
309
 
310
- def _get_valid_task_ids() -> set[str]:
311
- return {t["task_id"] for t in _load()["per_task"]}
312
-
313
-
314
- def validate_and_accept_submission(
315
- file, model_name: str, team_name: str, framework: str,
316
- contact: str, description: str, seed: str,
317
- ) -> str:
318
- """Validate a workspace artifact zip and queue it for server-side grading."""
319
-
320
- # --- basic field validation ---
321
- if not file:
322
- return "Please upload a workspace artifact zip file."
323
- if not model_name or not model_name.strip():
324
- return "Please provide a model name."
325
- if not team_name or not team_name.strip():
326
- return "Please provide a team or organization name."
327
-
328
- file_path = file.name if hasattr(file, "name") else str(file)
329
-
330
- # --- file size check ---
331
- file_size_mb = os.path.getsize(file_path) / (1024 * 1024)
332
- if file_size_mb > MAX_UPLOAD_MB:
333
- return f"File too large ({file_size_mb:.1f} MB). Maximum is {MAX_UPLOAD_MB} MB."
334
-
335
- # --- zip validation ---
336
- if not zipfile.is_zipfile(file_path):
337
- return "Uploaded file is not a valid zip archive."
338
-
339
- valid_tasks = _get_valid_task_ids()
340
- found_tasks: dict[str, list[str]] = {} # task_id -> list of files
341
- has_meta = False
342
 
 
343
  try:
344
- with zipfile.ZipFile(file_path, "r") as zf:
345
- names = zf.namelist()
346
-
347
- # Security: reject paths with .. or absolute paths
348
- for name in names:
349
- if ".." in name or name.startswith("/"):
350
- return f"Rejected: unsafe path in zip: `{name}`"
351
-
352
- # Check for meta.json
353
- for name in names:
354
- if name.endswith("meta.json") and name.count("/") <= 1:
355
- has_meta = True
356
-
357
- # Find workspace directories. Accept multiple layouts:
358
- # tasks/{TASK_ID}/workspace/...
359
- # tasks/{TASK_ID}/{condition}/workspace/...
360
- # {TASK_ID}/workspace/...
361
- # {TASK_ID}/{condition}/workspace/...
362
- valid_upper = {t.upper(): t for t in valid_tasks}
363
-
364
- for name in names:
365
- parts = name.split("/")
366
- if "workspace" not in parts:
367
- continue
368
- ws_idx = parts.index("workspace")
369
- # Try task_id at ws_idx-1 or ws_idx-2
370
- for offset in [1, 2]:
371
- candidate_idx = ws_idx - offset
372
- if candidate_idx < 0:
373
- continue
374
- candidate = parts[candidate_idx]
375
- matched = (
376
- candidate if candidate in valid_tasks
377
- else valid_upper.get(candidate.upper())
378
- )
379
- if matched:
380
- if matched not in found_tasks:
381
- found_tasks[matched] = []
382
- found_tasks[matched].append(name)
383
- break
384
-
385
- except zipfile.BadZipFile:
386
- return "Corrupted zip file. Please re-create and upload again."
387
-
388
- if not found_tasks:
389
- return (
390
- "No valid task workspaces found in the zip.\n\n"
391
- "Expected structure:\n"
392
- "```\n"
393
- "submission.zip/\n"
394
- " meta.json\n"
395
- " tasks/\n"
396
- " S1_hidden_spec/\n"
397
- " workspace/\n"
398
- " <files your agent produced>\n"
399
- " D1_schema_drift/\n"
400
- " workspace/\n"
401
- " ...\n"
402
- "```"
403
- )
404
-
405
- # --- save submission ---
406
- safe_model = "".join(c if c.isalnum() or c in "-_" else "_" for c in model_name.strip())
407
- safe_team = "".join(c if c.isalnum() or c in "-_" else "_" for c in team_name.strip())
408
- submission_id = f"{safe_team}__{safe_model}"
409
-
410
- # Save metadata
411
  meta = {
412
- "submission_id": submission_id,
413
- "model": model_name.strip(),
414
- "team": team_name.strip(),
415
- "framework": framework.strip() if framework else "",
416
- "contact": contact.strip() if contact else "",
417
- "description": description.strip() if description else "",
418
- "seed": int(seed) if seed and seed.isdigit() else 0,
419
- "tasks_submitted": sorted(found_tasks.keys()),
420
- "task_count": len(found_tasks),
421
- "file_count": sum(len(v) for v in found_tasks.values()),
422
- "status": "pending_grading",
423
  }
424
-
425
- meta_path = SUBMISSIONS_DIR / f"{submission_id}.json"
426
- with open(meta_path, "w") as f:
427
- json.dump(meta, f, indent=2)
428
-
429
- # Save the zip itself
430
- import shutil
431
- zip_path = SUBMISSIONS_DIR / f"{submission_id}.zip"
432
- shutil.copy2(file_path, zip_path)
433
-
434
- # --- build response ---
435
- missing = valid_tasks - set(found_tasks.keys())
436
- conditions = ["oracle", "restricted", "full", "team_no_plan", "team_no_verify"]
437
-
438
- response_parts = [
439
- f"### Submission Received",
440
- f"",
441
- f"**Model**: {model_name.strip()}",
442
- f"**Team**: {team_name.strip()}",
443
- f"**Tasks**: {len(found_tasks)} / {len(valid_tasks)}",
444
- f"**Files**: {sum(len(v) for v in found_tasks.values())} workspace artifacts",
445
- f"**Seed**: {meta['seed']}",
446
- ]
447
-
448
- if framework and framework.strip():
449
- response_parts.append(f"**Framework**: {framework.strip()}")
450
-
451
- response_parts.append("")
452
-
453
- if len(found_tasks) < len(valid_tasks):
454
- response_parts.append(
455
- f"*{len(missing)} tasks not included (partial submission accepted).*"
456
- )
457
-
458
- response_parts += [
459
- "",
460
- "**Status: Queued for server-side grading.**",
461
- "",
462
- "Your workspace artifacts will be graded against our hidden test suites. "
463
- "Scores are computed server-side — not self-reported — to ensure benchmark integrity.",
464
- "",
465
- "Results will appear on the leaderboard once grading completes (typically within 24-48 hours). "
466
- "You will be contacted at the provided email if there are any issues.",
467
- ]
468
-
469
- return "\n".join(response_parts)
470
 
471
 
472
  # ---------------------------------------------------------------------------
473
- # About HTML
474
  # ---------------------------------------------------------------------------
475
-
476
- def create_about_html() -> str:
477
- return """
478
- <div class="about-card">
479
- <h3>What is TeamBench?</h3>
480
- <p>TeamBench is a multi-agent benchmark that evaluates whether structured LLM teamwork
481
- improves task performance beyond what a single all-capable (oracle) agent can achieve.
482
- 155 tasks span 19 categories including Security, Data Engineering, Distributed Systems,
483
- Testing, and Adversarial Specification &mdash; designed to require coordination, verification,
484
- and planning across roles.</p>
485
- <p>All roles execute in isolated Docker containers with OS-enforced permission boundaries,
486
- preventing role confusion and ensuring genuine separation of concerns.</p>
487
- </div>
488
-
489
- <div class="about-card">
490
- <h3>The Three Roles</h3>
491
- <ul>
492
- <li><span class="role-planner">Planner</span> &mdash; reads code and documentation, identifies what needs to change,
493
- produces a structured plan. Has read-only filesystem access plus static analysis tools.</li>
494
- <li><span class="role-executor">Executor</span> &mdash; implements the plan. Has read-write access to the workspace
495
- but cannot run tests or arbitrary shell commands.</li>
496
- <li><span class="role-verifier">Verifier</span> &mdash; runs tests and validates the implementation. Has pytest,
497
- hypothesis, and mutation testing but cannot modify source files.</li>
498
- </ul>
499
- </div>
500
-
501
- <div class="about-card">
502
- <h3>Ablation Conditions</h3>
503
- <ul>
504
- <li><strong>Oracle</strong> &mdash; single agent with full unrestricted access (upper bound).</li>
505
- <li><strong>Restricted</strong> &mdash; single agent with only executor-level permissions (lower bound).</li>
506
- <li><strong>Full Team</strong> &mdash; all three roles: Planner &rarr; Executor &rarr; Verifier.</li>
507
- <li><strong>Team (No Plan)</strong> &mdash; Executor + Verifier only; measures planning value.</li>
508
- <li><strong>Team (No Verify)</strong> &mdash; Planner + Executor only; measures verification value.</li>
509
- </ul>
510
- </div>
511
-
512
- <div class="about-card">
513
- <h3>TNI &mdash; Teamwork Necessity Index</h3>
514
- <p style="font-family:monospace; background:#f3f4f6; padding:8px 12px; border-radius:6px; display:inline-block;">
515
- TNI = (team &minus; restricted) / (oracle &minus; restricted)
516
- </p>
517
- <p>TNI = 1.0 means the team matches the oracle. TNI &gt; 1.0 means the team <em>exceeds</em>
518
- the oracle &mdash; true super-additive teamwork.</p>
519
- <ul>
520
- <li><span class="badge-HIGH-TNI">HIGH-TNI</span> TNI &ge; 1.0: team closes or exceeds the oracle gap.</li>
521
- <li><span class="badge-TEAM-HELPS">TEAM-HELPS</span> positive uplift, TNI &lt; 1.0.</li>
522
- <li><span class="badge-NEUTRAL">NEUTRAL</span> no significant change.</li>
523
- <li><span class="badge-TEAM-HURTS">TEAM-HURTS</span> coordination degrades performance.</li>
524
- </ul>
525
- </div>
526
-
527
- <div class="about-card">
528
- <h3>Difficulty Tracks</h3>
529
- <ul>
530
- <li><strong>Full</strong> &mdash; all 155 tasks (easy + medium + hard + expert).</li>
531
- <li><strong>Hard</strong> &mdash; 122 tasks rated hard or expert only.</li>
532
- </ul>
533
- </div>
534
-
535
- <div class="about-card">
536
- <h3>Citation</h3>
537
- <p>If you use TeamBench in your research, please cite:</p>
538
- <pre style="background:#f3f4f6; padding:12px 16px; border-radius:8px; font-size:0.82rem; line-height:1.6; overflow-x:auto;">@misc{kim2026teambench,
539
- title = {TeamBench: Evaluating Structured LLM Teamwork
540
- via OS-Enforced Role Separation},
541
- author = {Kim, Yubin},
542
- year = {2026},
543
- url = {https://github.com/ybkim95/TeamBench}
544
- }</pre>
545
- </div>
546
-
547
- <div class="about-card">
548
- <h3>Links</h3>
549
- <ul>
550
- <li>Repository: <a href="https://github.com/ybkim95/TeamBench" target="_blank">github.com/ybkim95/TeamBench</a></li>
551
- <li>Leaderboard: <a href="https://huggingface.co/spaces/ybkim95/teambench-leaderboard" target="_blank">huggingface.co/spaces/ybkim95/teambench-leaderboard</a></li>
552
- </ul>
553
- </div>
554
  """
555
 
556
 
557
- # ---------------------------------------------------------------------------
558
- # Main app
559
- # ---------------------------------------------------------------------------
560
-
561
- def build_app() -> gr.Blocks:
562
- _load()
563
-
564
- categories_all = ["All"] + _load()["categories"]
565
- classifications = ["All", "HIGH-TNI", "TEAM-HELPS", "NEUTRAL", "TEAM-HURTS"]
566
- cm_categories = ["All"] + sorted({
567
- t["category"] for t in _load()["cross_model"]["per_task"]
568
- })
569
-
570
- _theme = gr.themes.Default(
571
- primary_hue="blue",
572
- secondary_hue="emerald",
573
- font=[gr.themes.GoogleFont("Inter"), "ui-sans-serif", "sans-serif"],
574
- )
575
-
576
- with gr.Blocks(title="TeamBench Leaderboard", theme=_theme, css=CUSTOM_CSS) as demo:
577
- gr.HTML(create_header())
578
 
579
  with gr.Tabs():
580
-
581
- # ==============================================================
582
- # TAB 1: Performance (Cross-Model) — default tab
583
- # ==============================================================
584
- with gr.Tab("Performance"):
585
- gr.HTML(create_stat_cards())
586
-
587
  gr.Markdown(
588
- "### Cross-Model Leaderboard\n"
589
- "28 tasks evaluated across 4 models. "
590
- "Ranked by average team uplift over oracle."
591
- )
592
- gr.Dataframe(
593
- value=build_crossmodel_leaderboard(),
594
- interactive=False, wrap=False,
595
- column_widths=["70px", "180px", "110px", "110px", "110px", "90px", "120px"],
596
  )
597
-
598
- gr.Markdown("### Per-Task Breakdown (28 tasks)")
599
- with gr.Row():
600
- cm_cat_dd = gr.Dropdown(
601
- choices=cm_categories, value="All",
602
- label="Category", scale=2,
603
- )
604
- cm_cls_dd = gr.Dropdown(
605
- choices=classifications, value="All",
606
- label="Classification", scale=2,
607
- )
608
-
609
- cm_task_table = gr.Dataframe(
610
- value=build_crossmodel_task_df(),
611
- interactive=False, wrap=False,
612
- )
613
-
614
- def _update_cm_tasks(cat, cls):
615
- return build_crossmodel_task_df(cat, cls)
616
-
617
- for w in [cm_cat_dd, cm_cls_dd]:
618
- w.change(fn=_update_cm_tasks,
619
- inputs=[cm_cat_dd, cm_cls_dd],
620
- outputs=cm_task_table)
621
-
622
- # ==============================================================
623
- # TAB 2: About
624
- # ==============================================================
625
- with gr.Tab("About"):
626
- gr.HTML(create_about_html())
627
-
628
- # ==============================================================
629
- # TAB 3: Overview (all 155 tasks)
630
- # ==============================================================
631
- with gr.Tab("Overview"):
632
  gr.Markdown(
633
- "### All 155 Tasks\n"
634
- "Evaluated across 5 ablation conditions at seed 0. "
635
- "Use **Track** to switch between Full (all) and Hard (hard + expert only)."
636
- )
637
-
638
- with gr.Row():
639
- track_dd = gr.Dropdown(
640
- choices=["Full", "Hard"],
641
- value="Full", label="Track", scale=1,
642
- )
643
- cat_dd = gr.Dropdown(
644
- choices=categories_all,
645
- value="All", label="Category", scale=2,
646
- )
647
- class_dd = gr.Dropdown(
648
- choices=classifications,
649
- value="All", label="Classification", scale=2,
650
- )
651
-
652
- task_table = gr.Dataframe(
653
- value=build_overview_df(), interactive=False, wrap=False,
654
  )
655
 
656
- def _update_overview(track, cat, cls):
657
- return build_overview_df(track, cat, cls)
658
-
659
- for w in [track_dd, cat_dd, class_dd]:
660
- w.change(fn=_update_overview,
661
- inputs=[track_dd, cat_dd, class_dd],
662
- outputs=task_table)
663
-
664
- gr.Markdown("### Per-Category Summary")
665
- cat_summary = gr.Dataframe(
666
- value=build_category_summary_df(), interactive=False, wrap=False,
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
667
  )
668
 
669
- def _update_cat_summary(track):
670
- return build_category_summary_df(track)
671
-
672
- track_dd.change(fn=_update_cat_summary,
673
- inputs=[track_dd], outputs=cat_summary)
674
-
675
- # ==============================================================
676
- # TAB 4: Submit
677
- # ==============================================================
678
  with gr.Tab("Submit"):
679
- gr.HTML("""
680
- <div class="submit-info">
681
- <h3 style="margin:0 0 12px;">Submit Your Results</h3>
682
- <p>TeamBench uses <strong>server-side grading</strong> to ensure benchmark integrity.
683
- You submit workspace artifacts (the files your agent produced) &mdash;
684
- not self-reported scores. We grade them against hidden test suites.</p>
685
- </div>
686
- """)
687
-
688
- gr.HTML("""
689
- <div class="submit-step">
690
- <h4>Step 1: Install and Run</h4>
691
- <pre style="background:#f3f4f6; padding:10px 14px; border-radius:6px; font-size:0.82rem; overflow-x:auto; margin:8px 0;">pip install teambench
692
- teambench run --model your-model --seed 0 --output submission/</pre>
693
- <p style="font-size:0.85rem; color:#6b7280; margin:4px 0 0;">
694
- This runs your agent on all tasks and saves the workspace artifacts (files your agent wrote).
695
- You must run all 5 conditions: <code>oracle</code>, <code>restricted</code>, <code>full</code>,
696
- <code>team_no_plan</code>, <code>team_no_verify</code>.
697
- </p>
698
- </div>
699
-
700
- <div class="submit-step">
701
- <h4>Step 2: Package</h4>
702
- <pre style="background:#f3f4f6; padding:10px 14px; border-radius:6px; font-size:0.82rem; overflow-x:auto; margin:8px 0;">teambench package submission/ --output submission.zip</pre>
703
- <p style="font-size:0.85rem; color:#6b7280; margin:4px 0 0;">
704
- Creates a zip with the required structure. Partial submissions (subset of 155 tasks) are accepted.
705
- </p>
706
- </div>
707
-
708
- <div class="submit-step">
709
- <h4>Step 3: Upload</h4>
710
- <p style="font-size:0.85rem; color:#6b7280;">
711
- Fill in the fields below and upload your <code>submission.zip</code>.
712
- Results appear on the leaderboard after server-side grading (24-48 hours).
713
- </p>
714
- </div>
715
- """)
716
-
717
- gr.HTML("""
718
- <details style="margin-bottom:16px;">
719
- <summary style="cursor:pointer; font-weight:600; font-size:0.9rem; padding:8px 0;">
720
- Expected zip structure
721
- </summary>
722
- <pre style="background:#f3f4f6; padding:12px 16px; border-radius:6px; font-size:0.82rem; margin-top:8px;">submission.zip/
723
- meta.json # auto-generated by teambench package
724
- tasks/
725
- S1_hidden_spec/
726
- oracle/workspace/ # workspace after oracle condition
727
- restricted/workspace/ # workspace after restricted condition
728
- full/workspace/ # workspace after full team condition
729
- team_no_plan/workspace/ # workspace after no-plan condition
730
- team_no_verify/workspace/ # workspace after no-verify condition
731
- D1_schema_drift/
732
- oracle/workspace/
733
- ...
734
- ...</pre>
735
- </details>
736
- """)
737
-
738
- with gr.Row():
739
- model_input = gr.Textbox(
740
- label="Model Name *",
741
- placeholder="e.g. gpt-5, claude-opus-4, gemini-3-pro",
742
- scale=2,
743
- )
744
- team_input = gr.Textbox(
745
- label="Team / Organization *",
746
- placeholder="e.g. OpenAI, Google DeepMind, your-lab-name",
747
- scale=2,
748
- )
749
  with gr.Row():
750
- framework_input = gr.Textbox(
751
- label="Framework (optional)",
752
- placeholder="e.g. langgraph, crewai, google-adk, custom",
753
- scale=2,
754
- )
755
- contact_input = gr.Textbox(
756
- label="Contact Email (optional)",
757
- placeholder="your@email.com",
758
- scale=2,
759
- )
760
  with gr.Row():
761
- description_input = gr.Textbox(
762
- label="Description (optional)",
763
- placeholder="Brief description of your agent setup, tools used, etc.",
764
- lines=2,
765
- scale=3,
766
- )
767
- seed_input = gr.Textbox(
768
- label="Seed",
769
- value="0",
770
- placeholder="0",
771
- scale=1,
772
- )
773
-
774
- file_input = gr.File(
775
- label="Upload submission.zip (workspace artifacts, max 100 MB)",
776
- file_types=[".zip"],
777
- )
778
-
779
- submit_btn = gr.Button("Submit for Grading", variant="primary")
780
- result_output = gr.Markdown("")
781
-
782
- submit_btn.click(
783
- fn=validate_and_accept_submission,
784
- inputs=[file_input, model_input, team_input,
785
- framework_input, contact_input,
786
- description_input, seed_input],
787
- outputs=result_output,
788
  )
789
 
 
 
 
 
 
 
 
790
  return demo
791
 
792
 
793
- # ---------------------------------------------------------------------------
794
- # Entry point
795
- # ---------------------------------------------------------------------------
796
-
797
  if __name__ == "__main__":
798
- app = build_app()
799
- app.launch(
800
- server_name="0.0.0.0",
801
- server_port=int(os.environ.get("PORT", 7860)),
802
- share=False,
803
- )
 
1
+ """TeamBench Leaderboard — Hugging Face Spaces app
 
2
 
3
+ Three tabs:
4
+ - Leaderboard (paper Table 4 / LB90, 13 models, 5 conditions)
5
+ - Instructions (install, run an evaluation, compute TNI)
6
+ - Submit (model name + results JSON upload, written to submissions/)
7
  """
 
8
  from __future__ import annotations
 
9
  import json
10
  import os
11
+ import re
12
+ import time
13
  from pathlib import Path
14
 
15
  import gradio as gr
16
  import pandas as pd
17
 
18
+ ROOT = Path(__file__).parent
19
+ SUBMISSIONS_DIR = ROOT / "submissions"
 
 
 
 
20
  SUBMISSIONS_DIR.mkdir(exist_ok=True)
21
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
22
  # ---------------------------------------------------------------------------
23
+ # LB90 leaderboard from paper Table tab:lb90-leaderboard
24
+ # Pass-rate (%) per condition. Sort: max(Solo, Full) desc.
25
  # ---------------------------------------------------------------------------
26
+ LEADERBOARD = [
27
+ {"model": "Claude Opus 4.7", "provider": "Anthropic", "Solo": 35.6, "Restricted": 33.3, "No Plan": 35.6, "No Eval": 33.3, "Full": 37.8},
28
+ {"model": "GPT-5.4 Mini", "provider": "OpenAI", "Solo": 33.3, "Restricted": 23.3, "No Plan": 25.6, "No Eval": 24.4, "Full": 28.9},
29
+ {"model": "Claude Haiku 4.5", "provider": "Anthropic", "Solo": 12.2, "Restricted": 31.1, "No Plan": 18.9, "No Eval": 1.1, "Full": 28.9},
30
+ {"model": "Gemini-3.1 Pro", "provider": "Google", "Solo": 27.8, "Restricted": 22.2, "No Plan": 16.7, "No Eval": 25.6, "Full": 28.9},
31
+ {"model": "Claude Sonnet 4.6", "provider": "Anthropic", "Solo": 7.8, "Restricted": 27.8, "No Plan": 10.0, "No Eval": 6.7, "Full": 27.8},
32
+ {"model": "GPT-5.4", "provider": "OpenAI", "Solo": 12.2, "Restricted": 35.6, "No Plan": 23.3, "No Eval": 34.4, "Full": 27.8},
33
+ {"model": "Gemma 4 31B", "provider": "Google", "Solo": 27.8, "Restricted": 25.6, "No Plan": 24.4, "No Eval": 20.0, "Full": 22.2},
34
+ {"model": "Gemini-3 Flash", "provider": "Google", "Solo": 13.3, "Restricted": 18.9, "No Plan": 14.4, "No Eval": 27.8, "Full": 25.6},
35
+ {"model": "Gemini-3.1 Flash Lite", "provider": "Google", "Solo": 5.6, "Restricted": 21.1, "No Plan": 8.9, "No Eval": 17.8, "Full": 17.8},
36
+ {"model": "gpt-oss-20b", "provider": "OpenAI", "Solo": 17.8, "Restricted": 17.8, "No Plan": 12.2, "No Eval": 7.8, "Full": 2.2},
37
+ {"model": "Qwen 3 14B", "provider": "Alibaba", "Solo": 5.6, "Restricted": 2.2, "No Plan": 2.2, "No Eval": 1.1, "Full": 2.2},
38
+ {"model": "Qwen 3 32B", "provider": "Alibaba", "Solo": 5.6, "Restricted": 3.3, "No Plan": 0.0, "No Eval": 5.6, "Full": 1.1},
39
+ {"model": "Qwen 3 8B", "provider": "Alibaba", "Solo": 2.2, "Restricted": 5.6, "No Plan": 1.1, "No Eval": 3.3, "Full": 0.0},
40
+ ]
41
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
42
 
43
+ def build_leaderboard_df():
44
+ df = pd.DataFrame(LEADERBOARD)
45
+ df.insert(0, "#", range(1, len(df) + 1))
46
+ return df
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
47
 
48
 
49
  # ---------------------------------------------------------------------------
50
+ # Submission validator
51
  # ---------------------------------------------------------------------------
52
+ SAFE_NAME = re.compile(r"^[A-Za-z0-9_.\-]+$")
 
53
 
54
 
55
+ def validate_submission(file_obj, model: str, team: str, contact: str, framework: str, description: str):
56
+ if file_obj is None:
57
+ return "**Error.** Please attach a JSON results file produced by `harness.run_all` or `harness.ablation`."
58
+ if not model or not SAFE_NAME.match(model):
59
+ return "**Error.** Model name is required and may only contain letters, digits, `.`, `_`, `-`."
60
+ if not team:
61
+ return "**Error.** Team / organization is required."
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
62
 
63
+ src = Path(file_obj.name if hasattr(file_obj, "name") else file_obj)
64
  try:
65
+ with open(src) as f:
66
+ payload = json.load(f)
67
+ except Exception as e:
68
+ return f"**Error.** Could not parse JSON: `{e}`"
69
+
70
+ if not isinstance(payload, (list, dict)):
71
+ return "**Error.** Results JSON must be a list of run records or a `{tasks: [...], conditions: {...}}` object."
72
+
73
+ ts = time.strftime("%Y%m%dT%H%M%SZ", time.gmtime())
74
+ out_dir = SUBMISSIONS_DIR / f"{model}-{ts}"
75
+ out_dir.mkdir(parents=True, exist_ok=True)
76
+ (out_dir / "results.json").write_text(json.dumps(payload, indent=2))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
77
  meta = {
78
+ "submitted_at_utc": ts,
79
+ "model": model,
80
+ "team": team,
81
+ "framework": framework or None,
82
+ "contact": contact or None,
83
+ "description": description or None,
84
+ "filename": src.name,
85
+ "n_records": len(payload) if isinstance(payload, list) else None,
 
 
 
86
  }
87
+ (out_dir / "meta.json").write_text(json.dumps(meta, indent=2))
88
+ return (
89
+ f"**Submission accepted** at `submissions/{out_dir.name}/`. We will re-run the "
90
+ f"deterministic graders server-side and add the model to the leaderboard once "
91
+ f"verified (typical turnaround: 24-48h). For status, open an issue at "
92
+ f"https://github.com/ybkim95/TeamBench/issues."
93
+ )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
94
 
95
 
96
  # ---------------------------------------------------------------------------
97
+ # UI
98
  # ---------------------------------------------------------------------------
99
+ CSS = """
100
+ .tb-hero { padding: 1.25rem 1.5rem; border-radius: 12px; background: linear-gradient(135deg,#1e3a8a 0%,#1d4ed8 50%,#2563eb 100%); color: #fff; margin-bottom: 1rem; }
101
+ .tb-hero h1 { color: #fff !important; margin: 0 0 0.35rem; font-weight: 700; }
102
+ .tb-hero p { color: rgba(255,255,255,0.95) !important; margin: 0; }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
103
  """
104
 
105
 
106
+ def build_app():
107
+ with gr.Blocks(css=CSS, title="TeamBench Leaderboard") as demo:
108
+ gr.HTML(
109
+ """
110
+ <div class="tb-hero">
111
+ <h1>TeamBench Leaderboard</h1>
112
+ <p>Multi-agent LLM coordination on 90 stratified tasks under OS-enforced Planner / Executor / Verifier role separation. Five conditions per task, deterministic graders, MIT-licensed.</p>
113
+ </div>
114
+ """
115
+ )
 
 
 
 
 
 
 
 
 
 
 
116
 
117
  with gr.Tabs():
118
+ with gr.Tab("Leaderboard"):
 
 
 
 
 
 
119
  gr.Markdown(
120
+ "Pass rate (%) per condition on **TeamBench-90**. Models are sorted by `max(Solo, Full)`. "
121
+ "Source: paper main results (TeamBench-Verified covers 57 of these 90 tasks)."
 
 
 
 
 
 
122
  )
123
+ gr.Dataframe(value=build_leaderboard_df(), interactive=False, wrap=False)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
124
  gr.Markdown(
125
+ "**Five conditions** &nbsp; "
126
+ "`Solo` (one agent, full access) · "
127
+ "`Restricted` (one agent, executor tools only) · "
128
+ "`No Plan` (Executor + Verifier; Verifier holds the spec) · "
129
+ "`No Eval` (Planner + Executor) · "
130
+ "`Full` (Planner + Executor + Verifier)."
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
131
  )
132
 
133
+ with gr.Tab("Instructions"):
134
+ gr.Markdown(
135
+ """
136
+ ### 1. Install
137
+
138
+ ```bash
139
+ git clone https://github.com/ybkim95/TeamBench.git
140
+ cd TeamBench
141
+ pip install -e ".[all]"
142
+ docker compose build
143
+ ```
144
+
145
+ Set the providers you want to evaluate:
146
+
147
+ ```bash
148
+ export ANTHROPIC_API_KEY=...
149
+ export OPENAI_API_KEY=...
150
+ export GEMINI_API_KEY=...
151
+ ```
152
+
153
+ ### 2. Run a single task across all five conditions
154
+
155
+ ```bash
156
+ python -m harness.ablation \\
157
+ --model gemini-3-flash-preview \\
158
+ --tasks DIST1_queue_race \\
159
+ --seeds 0 \\
160
+ --conditions oracle restricted full team_no_plan team_no_verify \\
161
+ --output shared/runs/example
162
+ ```
163
+
164
+ Each `score.json` under `shared/runs/example/<task>/<condition>/` contains `passed: true|false` and a partial score in `[0, 1]` from the deterministic shell-script grader. No API keys handy? `--model mock` runs an in-process stub that exercises the grader pipeline without provider calls.
165
+
166
+ ### 3. Run the full TeamBench-90 sweep
167
+
168
+ ```bash
169
+ python -m harness.run_all \\
170
+ --tasks $(jq -r '.[]' leaderboard/data/leaderboard_90_tasks.json) \\
171
+ --seeds 0 \\
172
+ --runs_dir shared/runs/lb90_<model> \\
173
+ --output shared/ablation_results/lb90_<model>_seed0.json
174
+ ```
175
+
176
+ ### 4. Compute TNI and the paper tables
177
+
178
+ ```bash
179
+ python -m harness.compute_tni \\
180
+ --ablation shared/ablation_results/lb90_<model>_seed0.json \\
181
+ --output shared/ablation_results/tni_<model>.json
182
+
183
+ python -m harness.paper_tables \\
184
+ --ablation shared/ablation_results/lb90_<model>_seed0.json \\
185
+ --output-dir shared/paper/
186
+ ```
187
+
188
+ ### 5. Submit
189
+
190
+ Upload `shared/ablation_results/lb90_<model>_seed0.json` from the Submit tab. We re-run the deterministic graders server-side and add your model to the leaderboard once verified.
191
+ """
192
  )
193
 
 
 
 
 
 
 
 
 
 
194
  with gr.Tab("Submit"):
195
+ gr.Markdown(
196
+ "Submit a results JSON produced by `python -m harness.run_all` or `python -m harness.ablation`. "
197
+ "We re-run the deterministic graders server-side to verify."
198
+ )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
199
  with gr.Row():
200
+ model_in = gr.Textbox(label="Model name *", placeholder="e.g. claude-sonnet-4-6")
201
+ team_in = gr.Textbox(label="Team / organization *", placeholder="e.g. Anthropic, Google DeepMind, your-lab")
 
 
 
 
 
 
 
 
202
  with gr.Row():
203
+ framework_in = gr.Textbox(label="Framework (optional)", placeholder="e.g. langgraph, crewai, custom")
204
+ contact_in = gr.Textbox(label="Contact email (optional)", placeholder="you@example.com")
205
+ description_in = gr.Textbox(label="Description (optional)", placeholder="Tools, prompts, framework version, anything reviewers should know.", lines=2)
206
+ file_in = gr.File(label="Results JSON (the file harness.run_all wrote)", file_types=[".json"])
207
+ btn = gr.Button("Submit for grading", variant="primary")
208
+ out = gr.Markdown("")
209
+ btn.click(
210
+ fn=validate_submission,
211
+ inputs=[file_in, model_in, team_in, contact_in, framework_in, description_in],
212
+ outputs=out,
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
213
  )
214
 
215
+ gr.Markdown(
216
+ "<div style='text-align:center;color:#9ca3af;font-size:0.78rem;margin-top:1.25rem;'>"
217
+ "TeamBench &middot; <a href='https://github.com/ybkim95/TeamBench'>GitHub</a> &middot; "
218
+ "<a href='https://huggingface.co/datasets/ybkim95/teambench'>HuggingFace</a> &middot; "
219
+ "<a href='https://teambench.github.io'>teambench.github.io</a>"
220
+ "</div>"
221
+ )
222
  return demo
223
 
224
 
 
 
 
 
225
  if __name__ == "__main__":
226
+ build_app().launch(server_name="0.0.0.0", server_port=int(os.environ.get("PORT", 7860)))
 
 
 
 
 
requirements.txt CHANGED
@@ -1,2 +1,2 @@
1
- gradio>=5.0.0
2
- pandas>=2.0.0
 
1
+ gradio==5.12.0
2
+ pandas>=2.0