Zhengyang commited on
Commit ·
038337f
1
Parent(s): dfc6628
Deploy AISE-Bench leaderboard
Browse files- .gitignore +3 -0
- README.md +6 -4
- app.py +350 -0
- data/leaderboard.json +1 -0
- publish_result.py +71 -0
- requirements.txt +2 -0
- schema.py +101 -0
- storage.py +142 -0
.gitignore
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
runtime_submissions/
|
| 2 |
+
__pycache__/
|
| 3 |
+
*.log
|
README.md
CHANGED
|
@@ -1,10 +1,12 @@
|
|
| 1 |
---
|
| 2 |
-
title: AISE
|
| 3 |
-
emoji:
|
| 4 |
-
colorFrom:
|
| 5 |
colorTo: purple
|
| 6 |
sdk: static
|
| 7 |
pinned: false
|
| 8 |
---
|
| 9 |
|
| 10 |
-
|
|
|
|
|
|
|
|
|
| 1 |
---
|
| 2 |
+
title: AISE-Bench Leaderboard
|
| 3 |
+
emoji: compass
|
| 4 |
+
colorFrom: blue
|
| 5 |
colorTo: purple
|
| 6 |
sdk: static
|
| 7 |
pinned: false
|
| 8 |
---
|
| 9 |
|
| 10 |
+
# AISE-Bench Leaderboard
|
| 11 |
+
|
| 12 |
+
Static leaderboard page for AISE-Bench.
|
app.py
ADDED
|
@@ -0,0 +1,350 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from __future__ import annotations
|
| 2 |
+
|
| 3 |
+
import html
|
| 4 |
+
|
| 5 |
+
import gradio as gr
|
| 6 |
+
|
| 7 |
+
from storage import load_leaderboard
|
| 8 |
+
|
| 9 |
+
|
| 10 |
+
APP_CSS = """
|
| 11 |
+
footer, .api-docs, .show-api, .built-with, [data-testid="api-info"] { display: none !important; }
|
| 12 |
+
.gradio-container {
|
| 13 |
+
max-width: 100% !important;
|
| 14 |
+
padding: 18px 24px 24px !important;
|
| 15 |
+
font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif !important;
|
| 16 |
+
font-size: 14px !important;
|
| 17 |
+
}
|
| 18 |
+
.hero { text-align: center; margin-bottom: 8px; }
|
| 19 |
+
.hero h1 { font-size: 32px !important; line-height: 1.2 !important; margin: 8px 0 6px !important; }
|
| 20 |
+
.hero p { color: #4b5563; font-size: 14px !important; line-height: 1.6 !important; margin: 0 !important; }
|
| 21 |
+
.hero-links { display: flex; justify-content: center; gap: 10px; flex-wrap: wrap; margin-top: 8px; }
|
| 22 |
+
.hero-links a { color: #2563eb; text-decoration: none; }
|
| 23 |
+
.hero-links a:hover { text-decoration: underline; }
|
| 24 |
+
.summary-grid { display: grid; grid-template-columns: repeat(3, minmax(180px, 1fr)); gap: 12px; margin: 14px 0 18px; }
|
| 25 |
+
.summary-card { border: 1px solid #e5e7eb; border-radius: 10px; padding: 14px 16px; background: #ffffff; }
|
| 26 |
+
.summary-card strong { display: block; color: #111827; font-size: 18px; margin-bottom: 3px; }
|
| 27 |
+
.summary-card span { color: #6b7280; font-size: 13px; }
|
| 28 |
+
.submission-guide { max-width: 1100px; font-size: 14px !important; line-height: 1.65 !important; margin: 8px auto 22px; }
|
| 29 |
+
.submission-guide h2 { font-size: 21px !important; margin: 22px 0 8px !important; }
|
| 30 |
+
.submission-guide h3 { font-size: 17px !important; margin: 18px 0 8px !important; }
|
| 31 |
+
.submission-guide p, .submission-guide li { font-size: 14px !important; line-height: 1.65 !important; }
|
| 32 |
+
.submission-guide code { font-size: 13px !important; }
|
| 33 |
+
.submission-guide pre code { font-size: 13px !important; line-height: 1.5 !important; }
|
| 34 |
+
.leaderboard-title h2 { font-size: 24px !important; text-align: center; margin: 18px 0 4px !important; }
|
| 35 |
+
.leaderboard-title p { color: #6b7280; font-size: 14px !important; text-align: center; margin: 0 0 12px !important; }
|
| 36 |
+
.leaderboard-toolbar { display: flex; align-items: center; justify-content: flex-end; margin: 8px 0 10px; }
|
| 37 |
+
.table-meta { color: #6b7280; display: flex; gap: 12px; font-size: 13px; }
|
| 38 |
+
.aise-table-wrap { height: min(68vh, 760px); min-height: 460px; overflow: auto; border: 1px solid #e5e7eb; border-radius: 10px; background: #ffffff; }
|
| 39 |
+
.aise-table { border-collapse: separate; border-spacing: 0; width: 100%; min-width: 1320px; color: #263238; font-size: 13px; }
|
| 40 |
+
.aise-table th, .aise-table td { border-bottom: 1px solid #edf0f3; border-right: 1px solid #edf0f3; padding: 9px 10px; text-align: center; white-space: nowrap; }
|
| 41 |
+
.aise-table th { position: sticky; z-index: 2; background: #f8fafc; color: #111827; font-weight: 650; user-select: none; }
|
| 42 |
+
.aise-table thead tr:first-child th { top: 0; background: #e5e7eb; font-size: 13px; }
|
| 43 |
+
.aise-table thead tr:nth-child(2) th { top: 38px; cursor: pointer; }
|
| 44 |
+
.aise-table th[rowspan="2"] { top: 0; cursor: pointer; }
|
| 45 |
+
.aise-table tbody tr:nth-child(even) { background: #fafafa; }
|
| 46 |
+
.aise-table tbody tr:hover { background: #eef5ff; }
|
| 47 |
+
.aise-table .rank { width: 48px; color: #4b5563; font-variant-numeric: tabular-nums; }
|
| 48 |
+
.aise-table .model { min-width: 190px; text-align: left; white-space: normal; }
|
| 49 |
+
.aise-table .model a, .aise-table .model-name { color: #2563eb; font-size: 14px; font-weight: 650; text-decoration: none; }
|
| 50 |
+
.aise-table .model a:hover { text-decoration: underline; }
|
| 51 |
+
.aise-table .org { display: block; color: #8a8f98; font-size: 12px; margin-top: 3px; }
|
| 52 |
+
.aise-table .num { font-variant-numeric: tabular-nums; }
|
| 53 |
+
.verified-badge { display: inline-flex; align-items: center; border-radius: 999px; font-size: 11px; font-weight: 700; padding: 3px 8px; }
|
| 54 |
+
.verified-yes { background: #dcfce7; color: #166534; }
|
| 55 |
+
.verified-no { background: #f3f4f6; color: #6b7280; }
|
| 56 |
+
.aise-table .empty { padding: 28px; color: #7b8088; }
|
| 57 |
+
.submit-actions button { min-height: 42px !important; font-size: 14px !important; font-weight: 600 !important; }
|
| 58 |
+
.section-title h2 { font-size: 21px !important; line-height: 1.3 !important; margin: 22px 0 8px !important; }
|
| 59 |
+
label, .label-wrap, input, textarea { font-size: 14px !important; }
|
| 60 |
+
@media (max-width: 760px) {
|
| 61 |
+
.gradio-container { padding: 12px !important; }
|
| 62 |
+
.summary-grid { grid-template-columns: 1fr; }
|
| 63 |
+
.table-meta span:last-child { display: none; }
|
| 64 |
+
}
|
| 65 |
+
"""
|
| 66 |
+
|
| 67 |
+
|
| 68 |
+
TABLE_JS = """
|
| 69 |
+
() => {
|
| 70 |
+
const table = document.getElementById("aise-leaderboard-table");
|
| 71 |
+
if (!table) return;
|
| 72 |
+
|
| 73 |
+
const tbody = table.querySelector("tbody");
|
| 74 |
+
const rows = Array.from(tbody.querySelectorAll("tr[data-entry]"));
|
| 75 |
+
const headers = table.querySelectorAll("th[data-sort]");
|
| 76 |
+
const countElement = document.getElementById("aise-table-count");
|
| 77 |
+
const numericColumns = new Set([
|
| 78 |
+
"Rank", "Precision", "Recall", "Format", "Edit Dist.", "Para. Acc.",
|
| 79 |
+
"Success", "Correct.", "Complete.", "Faithful.", "F1-LM"
|
| 80 |
+
]);
|
| 81 |
+
let sortColumn = "F1-LM";
|
| 82 |
+
let sortDirection = "desc";
|
| 83 |
+
|
| 84 |
+
function parseValue(row, column) {
|
| 85 |
+
const cell = row.querySelector(`[data-col="${column}"]`);
|
| 86 |
+
if (!cell) return "";
|
| 87 |
+
const raw = cell.dataset.value || cell.textContent || "";
|
| 88 |
+
if (numericColumns.has(column)) {
|
| 89 |
+
const value = Number.parseFloat(raw);
|
| 90 |
+
return Number.isNaN(value) ? -Infinity : value;
|
| 91 |
+
}
|
| 92 |
+
return raw.toLowerCase();
|
| 93 |
+
}
|
| 94 |
+
|
| 95 |
+
function applyTableState() {
|
| 96 |
+
const sortedRows = rows.slice().sort((left, right) => {
|
| 97 |
+
const leftValue = parseValue(left, sortColumn);
|
| 98 |
+
const rightValue = parseValue(right, sortColumn);
|
| 99 |
+
if (leftValue < rightValue) return sortDirection === "asc" ? -1 : 1;
|
| 100 |
+
if (leftValue > rightValue) return sortDirection === "asc" ? 1 : -1;
|
| 101 |
+
return 0;
|
| 102 |
+
});
|
| 103 |
+
|
| 104 |
+
sortedRows.forEach((row, index) => {
|
| 105 |
+
tbody.appendChild(row);
|
| 106 |
+
const rank = index + 1;
|
| 107 |
+
const rankCell = row.querySelector('[data-col="Rank"]');
|
| 108 |
+
rankCell.textContent = String(rank);
|
| 109 |
+
rankCell.dataset.value = String(rank);
|
| 110 |
+
});
|
| 111 |
+
|
| 112 |
+
countElement.textContent = `${sortedRows.length} entries`;
|
| 113 |
+
headers.forEach(header => {
|
| 114 |
+
header.classList.remove("sort-asc", "sort-desc");
|
| 115 |
+
if (header.dataset.sort === sortColumn) {
|
| 116 |
+
header.classList.add(sortDirection === "asc" ? "sort-asc" : "sort-desc");
|
| 117 |
+
}
|
| 118 |
+
});
|
| 119 |
+
}
|
| 120 |
+
|
| 121 |
+
headers.forEach(header => {
|
| 122 |
+
header.addEventListener("click", () => {
|
| 123 |
+
const column = header.dataset.sort;
|
| 124 |
+
if (sortColumn === column) {
|
| 125 |
+
sortDirection = sortDirection === "asc" ? "desc" : "asc";
|
| 126 |
+
} else {
|
| 127 |
+
sortColumn = column;
|
| 128 |
+
sortDirection = numericColumns.has(column) ? "desc" : "asc";
|
| 129 |
+
}
|
| 130 |
+
applyTableState();
|
| 131 |
+
});
|
| 132 |
+
});
|
| 133 |
+
|
| 134 |
+
applyTableState();
|
| 135 |
+
}
|
| 136 |
+
"""
|
| 137 |
+
|
| 138 |
+
|
| 139 |
+
METRIC_COLUMNS = [
|
| 140 |
+
("Precision", "precision", None),
|
| 141 |
+
("Recall", "recall", None),
|
| 142 |
+
("Format", "format", "clarity"),
|
| 143 |
+
("Edit Dist.", "edit_distance", None),
|
| 144 |
+
("Para. Acc.", "para_acc", "planning"),
|
| 145 |
+
("Success", "success", "execution"),
|
| 146 |
+
("Correct.", "correct", "correctness"),
|
| 147 |
+
("Complete.", "complete", "completeness"),
|
| 148 |
+
("Faithful.", "faithful", "faithfulness"),
|
| 149 |
+
("F1-LM", "f1_lm", "overall"),
|
| 150 |
+
]
|
| 151 |
+
|
| 152 |
+
|
| 153 |
+
def _metric(record: dict, primary: str, fallback: str | None = None) -> object:
|
| 154 |
+
value = record.get(primary)
|
| 155 |
+
if value is None and fallback:
|
| 156 |
+
value = record.get(fallback)
|
| 157 |
+
return value
|
| 158 |
+
|
| 159 |
+
|
| 160 |
+
def _format_metric(value: object) -> str:
|
| 161 |
+
if value is None or value == "":
|
| 162 |
+
return "/"
|
| 163 |
+
if isinstance(value, str):
|
| 164 |
+
return html.escape(value)
|
| 165 |
+
try:
|
| 166 |
+
return f"{float(value):.4f}".rstrip("0").rstrip(".")
|
| 167 |
+
except (TypeError, ValueError):
|
| 168 |
+
return html.escape(str(value))
|
| 169 |
+
|
| 170 |
+
|
| 171 |
+
def _metric_value(value: object) -> str:
|
| 172 |
+
try:
|
| 173 |
+
return str(float(value))
|
| 174 |
+
except (TypeError, ValueError):
|
| 175 |
+
return ""
|
| 176 |
+
|
| 177 |
+
|
| 178 |
+
def _leaderboard_html() -> str:
|
| 179 |
+
rows = []
|
| 180 |
+
for rank, record in enumerate(load_leaderboard(), start=1):
|
| 181 |
+
model = html.escape(str(record.get("model", "")))
|
| 182 |
+
organization = html.escape(str(record.get("organization", "")))
|
| 183 |
+
model_url = str(record.get("model_url", ""))
|
| 184 |
+
if _valid_url(model_url):
|
| 185 |
+
model_cell = f'<a href="{html.escape(model_url, quote=True)}" target="_blank">{model}</a>'
|
| 186 |
+
else:
|
| 187 |
+
model_cell = f'<span class="model-name">{model}</span>'
|
| 188 |
+
model_cell += f'<span class="org">{organization}</span>'
|
| 189 |
+
metric_cells = []
|
| 190 |
+
for label, primary, fallback in METRIC_COLUMNS:
|
| 191 |
+
metric = _metric(record, primary, fallback)
|
| 192 |
+
metric_cells.append(
|
| 193 |
+
f'<td class="num" data-col="{label}" data-value="{_metric_value(metric)}">'
|
| 194 |
+
f'{_format_metric(metric)}</td>'
|
| 195 |
+
)
|
| 196 |
+
verified = bool(record.get("verified"))
|
| 197 |
+
verified_label = "Verified" if verified else "Pending"
|
| 198 |
+
verified_class = "verified-yes" if verified else "verified-no"
|
| 199 |
+
rows.append(
|
| 200 |
+
f"""
|
| 201 |
+
<tr data-entry="true">
|
| 202 |
+
<td class="rank" data-col="Rank" data-value="{rank}">{rank}</td>
|
| 203 |
+
<td class="model" data-col="Model" data-value="{model}">{model_cell}</td>
|
| 204 |
+
{''.join(metric_cells)}
|
| 205 |
+
<td data-col="Verified" data-value="{verified_label}"><span class="verified-badge {verified_class}">{verified_label}</span></td>
|
| 206 |
+
</tr>
|
| 207 |
+
"""
|
| 208 |
+
)
|
| 209 |
+
|
| 210 |
+
empty = '<tr><td class="empty" colspan="13">No verified submissions yet.</td></tr>'
|
| 211 |
+
return f"""
|
| 212 |
+
<div class="leaderboard-toolbar">
|
| 213 |
+
<div class="table-meta">
|
| 214 |
+
<span id="aise-table-count">{len(rows)} entries</span>
|
| 215 |
+
<span>Click a column header to sort</span>
|
| 216 |
+
</div>
|
| 217 |
+
</div>
|
| 218 |
+
<div class="aise-table-wrap">
|
| 219 |
+
<table id="aise-leaderboard-table" class="aise-table">
|
| 220 |
+
<thead>
|
| 221 |
+
<tr>
|
| 222 |
+
<th rowspan="2" data-sort="Rank">#</th>
|
| 223 |
+
<th rowspan="2" data-sort="Model">Model</th>
|
| 224 |
+
<th colspan="3">References and Formatting</th>
|
| 225 |
+
<th colspan="3">API-based Judge</th>
|
| 226 |
+
<th colspan="4">Answer Content</th>
|
| 227 |
+
<th rowspan="2" data-sort="Verified">Status</th>
|
| 228 |
+
</tr>
|
| 229 |
+
<tr>
|
| 230 |
+
<th data-sort="Precision">Precision</th><th data-sort="Recall">Recall</th><th data-sort="Format">Format</th>
|
| 231 |
+
<th data-sort="Edit Dist.">Edit Dist.</th><th data-sort="Para. Acc.">Para. Acc.</th><th data-sort="Success">Success</th>
|
| 232 |
+
<th data-sort="Correct.">Correct.</th><th data-sort="Complete.">Complete.</th><th data-sort="Faithful.">Faithful.</th><th data-sort="F1-LM">F1-LM</th>
|
| 233 |
+
</tr>
|
| 234 |
+
</thead>
|
| 235 |
+
<tbody>{''.join(rows) or empty}</tbody>
|
| 236 |
+
</table>
|
| 237 |
+
</div>
|
| 238 |
+
"""
|
| 239 |
+
|
| 240 |
+
|
| 241 |
+
with gr.Blocks(
|
| 242 |
+
title="AISE-Bench Leaderboard",
|
| 243 |
+
analytics_enabled=False,
|
| 244 |
+
) as demo:
|
| 245 |
+
gr.Markdown(
|
| 246 |
+
"""
|
| 247 |
+
# AISE-Bench Leaderboard
|
| 248 |
+
|
| 249 |
+
Benchmarking full-cycle academic information-seeking agents on planning, execution,
|
| 250 |
+
reference grounding, and answer quality.
|
| 251 |
+
|
| 252 |
+
<div class="hero-links">
|
| 253 |
+
<a href="https://aise-bench.github.io/" target="_blank" rel="noopener noreferrer">Project Page</a>
|
| 254 |
+
<span aria-hidden="true">·</span>
|
| 255 |
+
<a href="https://arxiv.org/abs/2607.20498" target="_blank" rel="noopener noreferrer">Paper</a>
|
| 256 |
+
<span aria-hidden="true">·</span>
|
| 257 |
+
<a href="https://huggingface.co/datasets/zhengyang6666/AISE-Bench" target="_blank" rel="noopener noreferrer">Dataset</a>
|
| 258 |
+
<span aria-hidden="true">·</span>
|
| 259 |
+
<a href="https://github.com/zai-org/AISE-Bench" target="_blank" rel="noopener noreferrer">GitHub</a>
|
| 260 |
+
</div>
|
| 261 |
+
""",
|
| 262 |
+
elem_classes=["hero"],
|
| 263 |
+
)
|
| 264 |
+
|
| 265 |
+
gr.HTML(
|
| 266 |
+
"""
|
| 267 |
+
<div class="summary-grid">
|
| 268 |
+
<div class="summary-card"><strong>500</strong><span>official test questions</span></div>
|
| 269 |
+
<div class="summary-card"><strong>10 metrics</strong><span>process and answer evaluation</span></div>
|
| 270 |
+
<div class="summary-card"><strong>Verified only</strong><span>scores produced by the official evaluator</span></div>
|
| 271 |
+
</div>
|
| 272 |
+
"""
|
| 273 |
+
)
|
| 274 |
+
|
| 275 |
+
gr.Markdown(
|
| 276 |
+
"""
|
| 277 |
+
## How to Submit
|
| 278 |
+
|
| 279 |
+
1. Fork [this repository](https://huggingface.co/datasets/zhengyang6666/AISE-Bench).
|
| 280 |
+
2. Create a new branch for your submission.
|
| 281 |
+
3. Add your submission folder under
|
| 282 |
+
`submissions/<organization>__<model>__<input_config>/`.
|
| 283 |
+
4. Open a Pull Request with the new submission folder.
|
| 284 |
+
|
| 285 |
+
### Submission Directory Requirements
|
| 286 |
+
|
| 287 |
+
Each submission directory must contain the metadata and predictions for one
|
| 288 |
+
model/input configuration pair:
|
| 289 |
+
|
| 290 |
+
```text
|
| 291 |
+
<organization>__<model>__<input_config>/
|
| 292 |
+
metadata.yaml
|
| 293 |
+
predictions.jsonl
|
| 294 |
+
generation_config.json # optional, recommended
|
| 295 |
+
artifacts/ # optional logs or prompt notes
|
| 296 |
+
```
|
| 297 |
+
|
| 298 |
+
Use URL-safe directory names. Replace spaces, slashes, and special characters
|
| 299 |
+
with hyphens; keep `input_config` as `TEXT`.
|
| 300 |
+
|
| 301 |
+
### `metadata.yaml`
|
| 302 |
+
|
| 303 |
+
```yaml
|
| 304 |
+
model_name: "My Model"
|
| 305 |
+
model_url: https://...
|
| 306 |
+
```
|
| 307 |
+
|
| 308 |
+
### `predictions.jsonl`
|
| 309 |
+
|
| 310 |
+
Each line must be one JSON object:
|
| 311 |
+
|
| 312 |
+
- `index`: one-based sequential position; it must be exactly `1, 2, ..., N` in file order
|
| 313 |
+
- `qid`: stable question identifier from the dataset
|
| 314 |
+
- `question`: original real academic search query submitted by AMiner users
|
| 315 |
+
- `planning_text`: model-produced multi-step API planning sequence
|
| 316 |
+
- `api_input`: standardized input parameters for each API call
|
| 317 |
+
- `api_output`: AMiner academic KG API results, entity IDs, and execution status
|
| 318 |
+
- `result_edit`: model-produced final answer grounded by reference citations `[1]`
|
| 319 |
+
|
| 320 |
+
```json
|
| 321 |
+
{
|
| 322 |
+
"index": 1,
|
| 323 |
+
"qid": 3,
|
| 324 |
+
"question": "question text",
|
| 325 |
+
"planning_text": [],
|
| 326 |
+
"api_input": {},
|
| 327 |
+
"api_output": {},
|
| 328 |
+
"result_edit": "{\"answer\":\"...\",\"reference\":{\"[1]\":\"...\"}}"
|
| 329 |
+
}
|
| 330 |
+
```
|
| 331 |
+
|
| 332 |
+
The file must contain exactly one line for every item in `test.json`.
|
| 333 |
+
`qid` must match the corresponding item in `test.json`.
|
| 334 |
+
""",
|
| 335 |
+
elem_classes=["submission-guide"],
|
| 336 |
+
)
|
| 337 |
+
|
| 338 |
+
gr.Markdown(
|
| 339 |
+
"""
|
| 340 |
+
## Leaderboard
|
| 341 |
+
|
| 342 |
+
Official results on the 500-question test set are sorted by **F1-LM** by default. The table
|
| 343 |
+
is intentionally empty until the first submission completes official evaluation and review.
|
| 344 |
+
""",
|
| 345 |
+
elem_classes=["leaderboard-title"],
|
| 346 |
+
)
|
| 347 |
+
leaderboard = gr.HTML(value=_leaderboard_html())
|
| 348 |
+
|
| 349 |
+
if __name__ == "__main__":
|
| 350 |
+
demo.launch(css=APP_CSS, js=TABLE_JS)
|
data/leaderboard.json
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
[]
|
publish_result.py
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from __future__ import annotations
|
| 2 |
+
|
| 3 |
+
import argparse
|
| 4 |
+
import json
|
| 5 |
+
from datetime import datetime, timezone
|
| 6 |
+
from pathlib import Path
|
| 7 |
+
|
| 8 |
+
from storage import load_pending_submission, publish_result
|
| 9 |
+
|
| 10 |
+
|
| 11 |
+
REQUIRED_METRICS = {
|
| 12 |
+
"precision",
|
| 13 |
+
"recall",
|
| 14 |
+
"clarity",
|
| 15 |
+
"correctness",
|
| 16 |
+
"integrality",
|
| 17 |
+
"completeness",
|
| 18 |
+
"faithfulness",
|
| 19 |
+
"success",
|
| 20 |
+
"edit_distance",
|
| 21 |
+
}
|
| 22 |
+
|
| 23 |
+
|
| 24 |
+
def parse_args() -> argparse.Namespace:
|
| 25 |
+
parser = argparse.ArgumentParser(description="Publish an evaluated AISE-Bench submission.")
|
| 26 |
+
parser.add_argument("submission_id")
|
| 27 |
+
parser.add_argument("metrics_json", type=Path)
|
| 28 |
+
parser.add_argument("--grounding", type=float, required=True)
|
| 29 |
+
parser.add_argument("--overall", type=float, required=True)
|
| 30 |
+
return parser.parse_args()
|
| 31 |
+
|
| 32 |
+
|
| 33 |
+
def main() -> None:
|
| 34 |
+
args = parse_args()
|
| 35 |
+
metrics = json.loads(args.metrics_json.read_text(encoding="utf-8-sig"))
|
| 36 |
+
missing = REQUIRED_METRICS - metrics.keys()
|
| 37 |
+
if missing:
|
| 38 |
+
raise SystemExit(f"Metrics file is missing: {', '.join(sorted(missing))}")
|
| 39 |
+
|
| 40 |
+
metadata = load_pending_submission(args.submission_id)
|
| 41 |
+
result = {
|
| 42 |
+
**metadata,
|
| 43 |
+
"status": "accepted",
|
| 44 |
+
"evaluated_at": datetime.now(timezone.utc).isoformat(),
|
| 45 |
+
"format": float(metrics["clarity"]),
|
| 46 |
+
"edit_distance": float(metrics["edit_distance"]),
|
| 47 |
+
"para_acc": metrics.get("para_acc", metrics.get("parameter_accuracy")),
|
| 48 |
+
"success": float(metrics["success"]),
|
| 49 |
+
"correct": float(metrics["correctness"]),
|
| 50 |
+
"complete": float(metrics["completeness"]),
|
| 51 |
+
"faithful": float(metrics["faithfulness"]),
|
| 52 |
+
"f1_lm": metrics.get("f1_lm"),
|
| 53 |
+
"planning": float(metrics["edit_distance"]),
|
| 54 |
+
"execution": float(metrics["success"]),
|
| 55 |
+
"precision": float(metrics["precision"]),
|
| 56 |
+
"recall": float(metrics["recall"]),
|
| 57 |
+
"clarity": float(metrics["clarity"]),
|
| 58 |
+
"correctness": float(metrics["correctness"]),
|
| 59 |
+
"integrality": float(metrics["integrality"]),
|
| 60 |
+
"completeness": float(metrics["completeness"]),
|
| 61 |
+
"faithfulness": float(metrics["faithfulness"]),
|
| 62 |
+
"grounding": args.grounding,
|
| 63 |
+
"overall": args.overall,
|
| 64 |
+
"verified": True,
|
| 65 |
+
}
|
| 66 |
+
publish_result(result)
|
| 67 |
+
print(f"Published {args.submission_id}")
|
| 68 |
+
|
| 69 |
+
|
| 70 |
+
if __name__ == "__main__":
|
| 71 |
+
main()
|
requirements.txt
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
|
|
|
|
|
|
|
| 1 |
+
gradio>=6.0,<7
|
| 2 |
+
huggingface_hub>=0.28
|
schema.py
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from __future__ import annotations
|
| 2 |
+
|
| 3 |
+
import hashlib
|
| 4 |
+
import json
|
| 5 |
+
from dataclasses import dataclass
|
| 6 |
+
from pathlib import Path
|
| 7 |
+
from typing import Any
|
| 8 |
+
|
| 9 |
+
|
| 10 |
+
MAX_SUBMISSION_BYTES = 100 * 1024 * 1024
|
| 11 |
+
|
| 12 |
+
|
| 13 |
+
@dataclass(frozen=True)
|
| 14 |
+
class Track:
|
| 15 |
+
name: str
|
| 16 |
+
expected_samples: int
|
| 17 |
+
|
| 18 |
+
|
| 19 |
+
TRACKS = {
|
| 20 |
+
"test-500": Track("Official Test 500", 500),
|
| 21 |
+
}
|
| 22 |
+
|
| 23 |
+
REQUIRED_PREDICTION_FIELDS = {
|
| 24 |
+
"index",
|
| 25 |
+
"qid",
|
| 26 |
+
"question",
|
| 27 |
+
"planning_text",
|
| 28 |
+
"api_input",
|
| 29 |
+
"api_output",
|
| 30 |
+
"result_edit",
|
| 31 |
+
}
|
| 32 |
+
|
| 33 |
+
|
| 34 |
+
def load_and_validate_submission(path: str | Path, track_id: str) -> tuple[list[dict[str, Any]], str]:
|
| 35 |
+
submission_path = Path(path)
|
| 36 |
+
if not submission_path.is_file():
|
| 37 |
+
raise ValueError("Submission file does not exist.")
|
| 38 |
+
if submission_path.stat().st_size > MAX_SUBMISSION_BYTES:
|
| 39 |
+
raise ValueError("Submission file exceeds the 100 MB limit.")
|
| 40 |
+
if track_id not in TRACKS:
|
| 41 |
+
raise ValueError(f"Unknown track: {track_id}")
|
| 42 |
+
|
| 43 |
+
try:
|
| 44 |
+
payload = json.loads(submission_path.read_text(encoding="utf-8"))
|
| 45 |
+
except (UnicodeDecodeError, json.JSONDecodeError) as exc:
|
| 46 |
+
raise ValueError(f"Submission must be valid UTF-8 JSON: {exc}") from exc
|
| 47 |
+
|
| 48 |
+
predictions = payload.get("predictions") if isinstance(payload, dict) else payload
|
| 49 |
+
if not isinstance(predictions, list):
|
| 50 |
+
raise ValueError('Submission must be a JSON list or {"predictions": [...]}.')
|
| 51 |
+
|
| 52 |
+
expected_samples = TRACKS[track_id].expected_samples
|
| 53 |
+
if len(predictions) != expected_samples:
|
| 54 |
+
raise ValueError(
|
| 55 |
+
f"Track {track_id} requires {expected_samples} predictions; received {len(predictions)}."
|
| 56 |
+
)
|
| 57 |
+
|
| 58 |
+
seen_ids: set[str] = set()
|
| 59 |
+
for position, prediction in enumerate(predictions, start=1):
|
| 60 |
+
if not isinstance(prediction, dict):
|
| 61 |
+
raise ValueError(f"Prediction #{position} must be a JSON object.")
|
| 62 |
+
missing_fields = REQUIRED_PREDICTION_FIELDS - prediction.keys()
|
| 63 |
+
if missing_fields:
|
| 64 |
+
missing = ", ".join(sorted(missing_fields))
|
| 65 |
+
raise ValueError(f"Prediction #{position} is missing required fields: {missing}.")
|
| 66 |
+
if "id" in prediction:
|
| 67 |
+
raise ValueError(f"Prediction #{position} must use 'qid'; field 'id' is not accepted.")
|
| 68 |
+
|
| 69 |
+
sequence_index = prediction["index"]
|
| 70 |
+
if isinstance(sequence_index, bool) or not isinstance(sequence_index, int):
|
| 71 |
+
raise ValueError(f"Prediction #{position} field 'index' must be an integer.")
|
| 72 |
+
if sequence_index != position:
|
| 73 |
+
raise ValueError(
|
| 74 |
+
f"Prediction #{position} must have index {position}; received {sequence_index}."
|
| 75 |
+
)
|
| 76 |
+
|
| 77 |
+
qid = prediction["qid"]
|
| 78 |
+
if isinstance(qid, bool) or not isinstance(qid, (int, str)):
|
| 79 |
+
raise ValueError(f"Prediction #{position} field 'qid' must be an integer or string.")
|
| 80 |
+
prediction_id = str(qid).strip()
|
| 81 |
+
if not prediction_id:
|
| 82 |
+
raise ValueError(f"Prediction #{position} field 'qid' must not be empty.")
|
| 83 |
+
if prediction_id in seen_ids:
|
| 84 |
+
raise ValueError(f"Duplicate prediction qid: {prediction_id}")
|
| 85 |
+
seen_ids.add(prediction_id)
|
| 86 |
+
|
| 87 |
+
if not isinstance(prediction["question"], str):
|
| 88 |
+
raise ValueError(f"Prediction {prediction_id} field 'question' must be a string.")
|
| 89 |
+
if not isinstance(prediction["planning_text"], (str, list)):
|
| 90 |
+
raise ValueError(
|
| 91 |
+
f"Prediction {prediction_id} field 'planning_text' must be a string or list."
|
| 92 |
+
)
|
| 93 |
+
if not isinstance(prediction["api_input"], (list, dict)):
|
| 94 |
+
raise ValueError(f"Prediction {prediction_id} field 'api_input' must be a list or object.")
|
| 95 |
+
if not isinstance(prediction["api_output"], (list, dict)):
|
| 96 |
+
raise ValueError(f"Prediction {prediction_id} field 'api_output' must be a list or object.")
|
| 97 |
+
if not isinstance(prediction["result_edit"], str):
|
| 98 |
+
raise ValueError(f"Prediction {prediction_id} field 'result_edit' must be a string.")
|
| 99 |
+
|
| 100 |
+
digest = hashlib.sha256(submission_path.read_bytes()).hexdigest()
|
| 101 |
+
return predictions, digest
|
storage.py
ADDED
|
@@ -0,0 +1,142 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from __future__ import annotations
|
| 2 |
+
|
| 3 |
+
import json
|
| 4 |
+
import os
|
| 5 |
+
from datetime import datetime, timezone
|
| 6 |
+
from io import BytesIO
|
| 7 |
+
from pathlib import Path
|
| 8 |
+
from typing import Any
|
| 9 |
+
|
| 10 |
+
from huggingface_hub import HfApi, hf_hub_download, list_repo_files
|
| 11 |
+
|
| 12 |
+
|
| 13 |
+
BASE_DIR = Path(__file__).resolve().parent
|
| 14 |
+
SEED_PATH = BASE_DIR / "data" / "leaderboard.json"
|
| 15 |
+
LOCAL_RUNTIME_DIR = BASE_DIR / "runtime_submissions"
|
| 16 |
+
|
| 17 |
+
|
| 18 |
+
def _dataset_config() -> tuple[str, str] | None:
|
| 19 |
+
repo_id = os.getenv("LEADERBOARD_DATASET_REPO", "").strip()
|
| 20 |
+
token = os.getenv("HF_TOKEN", "").strip()
|
| 21 |
+
return (repo_id, token) if repo_id and token else None
|
| 22 |
+
|
| 23 |
+
|
| 24 |
+
def _read_json(path: str | Path) -> Any:
|
| 25 |
+
return json.loads(Path(path).read_text(encoding="utf-8"))
|
| 26 |
+
|
| 27 |
+
|
| 28 |
+
def load_leaderboard() -> list[dict[str, Any]]:
|
| 29 |
+
records = list(_read_json(SEED_PATH))
|
| 30 |
+
config = _dataset_config()
|
| 31 |
+
|
| 32 |
+
if config:
|
| 33 |
+
repo_id, token = config
|
| 34 |
+
try:
|
| 35 |
+
files = list_repo_files(repo_id, repo_type="dataset", token=token)
|
| 36 |
+
for filename in files:
|
| 37 |
+
if not filename.startswith("results/") or not filename.endswith(".json"):
|
| 38 |
+
continue
|
| 39 |
+
local_path = hf_hub_download(
|
| 40 |
+
repo_id,
|
| 41 |
+
filename,
|
| 42 |
+
repo_type="dataset",
|
| 43 |
+
token=token,
|
| 44 |
+
)
|
| 45 |
+
record = _read_json(local_path)
|
| 46 |
+
if record.get("status") == "accepted":
|
| 47 |
+
records.append(record)
|
| 48 |
+
except Exception:
|
| 49 |
+
pass
|
| 50 |
+
elif LOCAL_RUNTIME_DIR.exists():
|
| 51 |
+
for path in LOCAL_RUNTIME_DIR.glob("results/*.json"):
|
| 52 |
+
record = _read_json(path)
|
| 53 |
+
if record.get("status") == "accepted":
|
| 54 |
+
records.append(record)
|
| 55 |
+
|
| 56 |
+
def score(item: dict[str, Any]) -> float:
|
| 57 |
+
value = item.get("f1_lm", item.get("overall"))
|
| 58 |
+
try:
|
| 59 |
+
return float(value)
|
| 60 |
+
except (TypeError, ValueError):
|
| 61 |
+
return float("-inf")
|
| 62 |
+
|
| 63 |
+
return sorted(
|
| 64 |
+
records,
|
| 65 |
+
key=lambda item: (
|
| 66 |
+
-score(item),
|
| 67 |
+
str(item.get("model", "")).lower(),
|
| 68 |
+
),
|
| 69 |
+
)
|
| 70 |
+
|
| 71 |
+
|
| 72 |
+
def queue_submission(metadata: dict[str, Any], prediction_path: str | Path) -> None:
|
| 73 |
+
metadata = {
|
| 74 |
+
**metadata,
|
| 75 |
+
"status": "pending",
|
| 76 |
+
"submitted_at": datetime.now(timezone.utc).isoformat(),
|
| 77 |
+
}
|
| 78 |
+
submission_id = metadata["submission_id"]
|
| 79 |
+
prediction_target = f"predictions/{submission_id}.json"
|
| 80 |
+
metadata_target = f"pending/{submission_id}.json"
|
| 81 |
+
config = _dataset_config()
|
| 82 |
+
|
| 83 |
+
if config:
|
| 84 |
+
repo_id, token = config
|
| 85 |
+
api = HfApi(token=token)
|
| 86 |
+
api.upload_file(
|
| 87 |
+
path_or_fileobj=str(prediction_path),
|
| 88 |
+
path_in_repo=prediction_target,
|
| 89 |
+
repo_id=repo_id,
|
| 90 |
+
repo_type="dataset",
|
| 91 |
+
commit_message=f"Add predictions for {submission_id}",
|
| 92 |
+
)
|
| 93 |
+
api.upload_file(
|
| 94 |
+
path_or_fileobj=BytesIO(
|
| 95 |
+
json.dumps(metadata, ensure_ascii=False, indent=2).encode("utf-8")
|
| 96 |
+
),
|
| 97 |
+
path_in_repo=metadata_target,
|
| 98 |
+
repo_id=repo_id,
|
| 99 |
+
repo_type="dataset",
|
| 100 |
+
commit_message=f"Queue submission {submission_id}",
|
| 101 |
+
)
|
| 102 |
+
return
|
| 103 |
+
|
| 104 |
+
(LOCAL_RUNTIME_DIR / "predictions").mkdir(parents=True, exist_ok=True)
|
| 105 |
+
(LOCAL_RUNTIME_DIR / "pending").mkdir(parents=True, exist_ok=True)
|
| 106 |
+
destination = LOCAL_RUNTIME_DIR / prediction_target
|
| 107 |
+
destination.write_bytes(Path(prediction_path).read_bytes())
|
| 108 |
+
(LOCAL_RUNTIME_DIR / metadata_target).write_text(
|
| 109 |
+
json.dumps(metadata, ensure_ascii=False, indent=2),
|
| 110 |
+
encoding="utf-8",
|
| 111 |
+
)
|
| 112 |
+
|
| 113 |
+
|
| 114 |
+
def load_pending_submission(submission_id: str) -> dict[str, Any]:
|
| 115 |
+
filename = f"pending/{submission_id}.json"
|
| 116 |
+
config = _dataset_config()
|
| 117 |
+
if config:
|
| 118 |
+
repo_id, token = config
|
| 119 |
+
local_path = hf_hub_download(repo_id, filename, repo_type="dataset", token=token)
|
| 120 |
+
return _read_json(local_path)
|
| 121 |
+
return _read_json(LOCAL_RUNTIME_DIR / filename)
|
| 122 |
+
|
| 123 |
+
|
| 124 |
+
def publish_result(record: dict[str, Any]) -> None:
|
| 125 |
+
submission_id = record["submission_id"]
|
| 126 |
+
filename = f"results/{submission_id}.json"
|
| 127 |
+
payload = json.dumps(record, ensure_ascii=False, indent=2).encode("utf-8")
|
| 128 |
+
config = _dataset_config()
|
| 129 |
+
if config:
|
| 130 |
+
repo_id, token = config
|
| 131 |
+
HfApi(token=token).upload_file(
|
| 132 |
+
path_or_fileobj=BytesIO(payload),
|
| 133 |
+
path_in_repo=filename,
|
| 134 |
+
repo_id=repo_id,
|
| 135 |
+
repo_type="dataset",
|
| 136 |
+
commit_message=f"Publish result {submission_id}",
|
| 137 |
+
)
|
| 138 |
+
return
|
| 139 |
+
|
| 140 |
+
destination = LOCAL_RUNTIME_DIR / filename
|
| 141 |
+
destination.parent.mkdir(parents=True, exist_ok=True)
|
| 142 |
+
destination.write_bytes(payload)
|