CADGenBench / app.py
Michael Rabinovich
docs: rewrite Space README + About tab as declarative current state
628bc9e
Raw
History Blame
7.23 kB
"""CADGenBench Leaderboard Space.
Step 3 prototype: a hand-crafted ``results.jsonl`` drives the leaderboard
table, and the Submit tab is a UI-only stub. The read path (Step 5) will
swap the JSONL for ``datasets.load_dataset(HF_SUBMISSIONS_REPO, 'results')``
and the write path (Step 6) will run ``cadgenbench evaluate`` and push a
result row back to the submissions dataset via ``HfApi``.
"""
from __future__ import annotations
import json
import os
from pathlib import Path
import gradio as gr
import pandas as pd
from huggingface_hub import hf_hub_download
HF_ORG = os.getenv("HF_ORG", "michaelr27")
HF_SUBMISSIONS_REPO = os.getenv(
"HF_SUBMISSIONS_REPO", f"{HF_ORG}/cadgenbench-submissions"
)
HF_DATA_REPO = os.getenv("HF_DATA_REPO", f"{HF_ORG}/cadgenbench-data")
LOCAL_RESULTS_PATH = Path(__file__).parent / "results.jsonl"
LEADERBOARD_COLS = [
"submission_name",
"submitter_name",
"aggregate_score",
"validity_rate",
"submitted_at",
"cadgenbench_version",
]
def _load_rows_from_hub() -> list[dict] | None:
"""Pull results.jsonl from the submissions dataset.
Returns None on any failure so callers can fall back to the local file.
"""
try:
path = hf_hub_download(
repo_id=HF_SUBMISSIONS_REPO,
filename="results.jsonl",
repo_type="dataset",
force_download=True,
)
return [
json.loads(line)
for line in Path(path).read_text().splitlines()
if line.strip()
]
except Exception as e: # noqa: BLE001 — any failure should fall back
print(f"[load_leaderboard] Hub fetch failed ({type(e).__name__}: {e})")
return None
def _load_rows_from_local() -> list[dict]:
if not LOCAL_RESULTS_PATH.exists():
return []
return [
json.loads(line)
for line in LOCAL_RESULTS_PATH.read_text().splitlines()
if line.strip()
]
def _fmt_pct(x: float | None) -> str:
"""Render a 0-1 fraction as 'NN%' (or 'NN.N%' for non-whole values)."""
if x is None:
return ""
pct = float(x) * 100
return f"{pct:.0f}%" if pct == int(pct) else f"{pct:.1f}%"
def load_leaderboard() -> pd.DataFrame:
rows = _load_rows_from_hub()
if rows is None:
print("[load_leaderboard] falling back to local results.jsonl")
rows = _load_rows_from_local()
if not rows:
return pd.DataFrame(columns=LEADERBOARD_COLS)
df = pd.DataFrame(rows)
cols = [c for c in LEADERBOARD_COLS if c in df.columns]
df = (
df[cols]
.sort_values("aggregate_score", ascending=False, na_position="last")
.reset_index(drop=True)
)
if "validity_rate" in df.columns:
df["validity_rate"] = df["validity_rate"].map(_fmt_pct)
return df
def handle_submit(
zip_file,
submission_name: str,
submitter: str,
agent_url: str,
notes: str,
agree: bool,
) -> str:
if zip_file is None:
return "**Error:** please attach a submission zip."
if not submission_name.strip():
return "**Error:** please fill in the Submission name."
if not submitter.strip():
return "**Error:** please fill in your Submitter name."
if not agree:
return "**Error:** you must agree to publish before submitting."
name = Path(zip_file.name).name
return (
f"Received `{name}` - submission `{submission_name}` by `{submitter}`.\n\n"
f"_Evaluation is not wired yet (Step 6 of the build plan). Once it "
f"is, this submission will run the CPU eval inline and append a row "
f"to `{HF_SUBMISSIONS_REPO}`._"
)
ABOUT_MD = f"""## About
**CADGenBench** evaluates AI-driven CAD generation: how well a model can
turn a description of a mechanical part into a valid, geometrically
correct 3D model.
- **Reference baseline**: an iterative AI agent that writes build123d Python.
- **Submission flow**: upload a zip of per-fixture STEP files; the Space
runs the eval and appends a row to the submissions dataset.
- **Datasets**: fixture inputs in
[`{HF_DATA_REPO}`](https://huggingface.co/datasets/{HF_DATA_REPO});
submissions and computed results in
[`{HF_SUBMISSIONS_REPO}`](https://huggingface.co/datasets/{HF_SUBMISSIONS_REPO}).
- **Code**: [`huggingface/cadgenbench`](https://github.com/huggingface/cadgenbench).
"""
with gr.Blocks(title="CADGenBench Leaderboard") as app:
gr.Markdown(
"# CADGenBench Leaderboard\n"
"_Benchmarking AI-driven CAD generation._"
)
with gr.Tab("Leaderboard"):
df_view = gr.Dataframe(
value=load_leaderboard(),
interactive=False,
wrap=True,
label="Results (sorted by aggregate CAD score)",
)
refresh_btn = gr.Button("Refresh", size="sm")
refresh_btn.click(fn=load_leaderboard, outputs=df_view)
with gr.Tab("Submit"):
gr.Markdown(
f"""
**Submission format.** A single zip with:
- one folder per fixture in `{HF_DATA_REPO}`, each containing `output.step`;
- a top-level `meta.json`:
```json
{{
"submitter_name": "your name or team",
"submission_name": "MyAgent v2.3 (or whatever describes your system)",
"agent_url": "https://github.com/... (optional)",
"notes": "free text, optional, max 500 chars, single line, plain text",
"agree_to_publish": true
}}
```
**Submission name.** Free text describing the system being benchmarked,
however you choose to describe it. The benchmark is system-agnostic - your
submission may use no LLM, one, or many. If you want to disclose your
stack, put it here or in `notes`.
**Notes field.** Plain text only (no markdown / HTML). Capped at 500 chars
and stripped to a single line. Shown in the per-submission detail view,
not in the main leaderboard table.
The Space runs the CPU eval inline and appends a row to
`{HF_SUBMISSIONS_REPO}`. You can fill the fields below to override
`meta.json` for a quick test.
"""
)
zip_in = gr.File(label="Submission ZIP", file_types=[".zip"])
with gr.Row():
submission_name_in = gr.Textbox(
label="Submission name",
placeholder='e.g. "MyAgent v2.3" or "build123d baseline (Claude Opus 4.7)"',
)
submitter_in = gr.Textbox(label="Submitter name")
with gr.Row():
agent_url_in = gr.Textbox(
label="Agent / paper URL (optional)",
placeholder="https://github.com/...",
)
notes_in = gr.Textbox(label="Notes (optional)")
agree_in = gr.Checkbox(
label="I agree to publish this result on the public leaderboard."
)
submit_btn = gr.Button("Submit", variant="primary")
submit_out = gr.Markdown()
submit_btn.click(
fn=handle_submit,
inputs=[
zip_in,
submission_name_in,
submitter_in,
agent_url_in,
notes_in,
agree_in,
],
outputs=submit_out,
)
with gr.Tab("About"):
gr.Markdown(ABOUT_MD)
if __name__ == "__main__":
app.launch(theme=gr.themes.Soft())