benchkit-cli 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- benchkit/__init__.py +1 -0
- benchkit/_pi_proxy.py +219 -0
- benchkit/answer_key_guard.ts +49 -0
- benchkit/artifacts.py +87 -0
- benchkit/benchmarks/__init__.py +105 -0
- benchkit/benchmarks/aider_polyglot.py +405 -0
- benchkit/benchmarks/arc.py +52 -0
- benchkit/benchmarks/banking77.py +149 -0
- benchkit/benchmarks/base.py +19 -0
- benchkit/benchmarks/boolq.py +49 -0
- benchkit/benchmarks/chartqa.py +133 -0
- benchkit/benchmarks/code_verifier.py +77 -0
- benchkit/benchmarks/downloads.py +166 -0
- benchkit/benchmarks/evalplus.py +288 -0
- benchkit/benchmarks/git_surgery.py +485 -0
- benchkit/benchmarks/gpqa.py +56 -0
- benchkit/benchmarks/gsm8k.py +97 -0
- benchkit/benchmarks/hellaswag.py +49 -0
- benchkit/benchmarks/humaneval.py +103 -0
- benchkit/benchmarks/ifeval.py +56 -0
- benchkit/benchmarks/ifeval_instructions.py +477 -0
- benchkit/benchmarks/mbpp.py +60 -0
- benchkit/benchmarks/mc_arena.py +479 -0
- benchkit/benchmarks/mcq.py +94 -0
- benchkit/benchmarks/medfailbench.py +537 -0
- benchkit/benchmarks/mmlu.py +54 -0
- benchkit/benchmarks/mmlu_pro.py +62 -0
- benchkit/benchmarks/mmvp.py +115 -0
- benchkit/benchmarks/ocrbench.py +96 -0
- benchkit/benchmarks/openbookqa.py +52 -0
- benchkit/benchmarks/patcheval.py +929 -0
- benchkit/benchmarks/piqa.py +52 -0
- benchkit/benchmarks/ruler.py +172 -0
- benchkit/benchmarks/ruler_generator.py +535 -0
- benchkit/benchmarks/sanity.py +174 -0
- benchkit/benchmarks/treejs_arena.py +268 -0
- benchkit/benchmarks/truthfulqa.py +52 -0
- benchkit/benchmarks/utils.py +25 -0
- benchkit/benchmarks/vision.py +230 -0
- benchkit/benchmarks/vstar.py +108 -0
- benchkit/benchmarks/winogrande.py +52 -0
- benchkit/benchmarks/xstest.py +116 -0
- benchkit/browser.py +429 -0
- benchkit/catalog.py +121 -0
- benchkit/cli.py +1000 -0
- benchkit/client.py +1413 -0
- benchkit/controls.py +87 -0
- benchkit/datasets/MMLU_LICENSE.txt +21 -0
- benchkit/datasets/README.md +142 -0
- benchkit/datasets/arc_challenge.jsonl +1172 -0
- benchkit/datasets/banking77.jsonl +3080 -0
- benchkit/datasets/banking77_intents.json +79 -0
- benchkit/datasets/boolq.jsonl +3270 -0
- benchkit/datasets/code_verifier.jsonl +500 -0
- benchkit/datasets/gpqa_diamond.jsonl +198 -0
- benchkit/datasets/gsm8k.jsonl +1319 -0
- benchkit/datasets/hellaswag.jsonl +1000 -0
- benchkit/datasets/humaneval.jsonl +164 -0
- benchkit/datasets/ifeval.jsonl +541 -0
- benchkit/datasets/mbpp.jsonl +500 -0
- benchkit/datasets/mc_blocks_1_20_1.jsonl +1003 -0
- benchkit/datasets/medfailbench_hard30.jsonl +30 -0
- benchkit/datasets/medfailbench_hard30.md +13 -0
- benchkit/datasets/openbookqa.jsonl +500 -0
- benchkit/datasets/piqa.jsonl +1838 -0
- benchkit/datasets/sanity_code.jsonl +6 -0
- benchkit/datasets/truthfulqa.jsonl +817 -0
- benchkit/datasets/winogrande.jsonl +1267 -0
- benchkit/datasets/xstest.csv +451 -0
- benchkit/decision.py +194 -0
- benchkit/demo.py +451 -0
- benchkit/dockerfiles.py +247 -0
- benchkit/engine.py +1270 -0
- benchkit/evaluation.py +195 -0
- benchkit/events.py +141 -0
- benchkit/executor.py +96 -0
- benchkit/git_surgery/backport-release-stack/setup.sh +328 -0
- benchkit/git_surgery/backport-release-stack/verify.sh +119 -0
- benchkit/git_surgery/bisect-the-regression/setup.sh +49 -0
- benchkit/git_surgery/bisect-the-regression/verify.sh +34 -0
- benchkit/git_surgery/rebase-conflict-chain/setup.sh +65 -0
- benchkit/git_surgery/rebase-conflict-chain/verify.sh +43 -0
- benchkit/git_surgery/recover-complex-stash/setup.sh +152 -0
- benchkit/git_surgery/recover-complex-stash/verify.sh +95 -0
- benchkit/git_surgery/recover-lost-work/setup.sh +40 -0
- benchkit/git_surgery/recover-lost-work/verify.sh +36 -0
- benchkit/git_surgery/repair-force-pushed-remote/setup.sh +171 -0
- benchkit/git_surgery/repair-force-pushed-remote/verify.sh +83 -0
- benchkit/git_surgery/revert-merge-with-followups/setup.sh +198 -0
- benchkit/git_surgery/revert-merge-with-followups/verify.sh +98 -0
- benchkit/git_surgery/secret-in-history/README.md +27 -0
- benchkit/git_surgery/secret-in-history/setup.sh +107 -0
- benchkit/git_surgery/secret-in-history/verify.sh +106 -0
- benchkit/git_surgery/split-the-mega-commit/setup.sh +50 -0
- benchkit/git_surgery/split-the-mega-commit/verify.sh +48 -0
- benchkit/git_surgery/untangle-nested-submodules/setup.sh +193 -0
- benchkit/git_surgery/untangle-nested-submodules/verify.sh +90 -0
- benchkit/history.py +474 -0
- benchkit/jobs.py +332 -0
- benchkit/leakage.py +51 -0
- benchkit/looping.py +873 -0
- benchkit/mc_render.py +274 -0
- benchkit/mc_viewer/dist/atlas.png +0 -0
- benchkit/mc_viewer/dist/blockStates.json.gz +0 -0
- benchkit/mc_viewer/dist/version.json +4 -0
- benchkit/mc_viewer/dist/viewer.html +23 -0
- benchkit/mc_viewer/dist/viewer.js +2 -0
- benchkit/mc_viewer/dist/viewer.js.LICENSE.txt +25 -0
- benchkit/mc_viewer/dist/worker.js +2 -0
- benchkit/mc_viewer/dist/worker.js.LICENSE.txt +19 -0
- benchkit/metrics.py +100 -0
- benchkit/ollama.py +60 -0
- benchkit/perf.py +353 -0
- benchkit/perf_report.py +207 -0
- benchkit/perturbations.py +208 -0
- benchkit/pi_agent.py +476 -0
- benchkit/pi_package/package-lock.json +1827 -0
- benchkit/pi_package/package.json +8 -0
- benchkit/report.py +142 -0
- benchkit/report_markdown.py +645 -0
- benchkit/results.py +688 -0
- benchkit/retry.py +163 -0
- benchkit/runner.py +769 -0
- benchkit/sandbox.py +1167 -0
- benchkit/template_check.py +291 -0
- benchkit/templates/arena.html +504 -0
- benchkit/templates/history.html +58 -0
- benchkit/templates/perf.html +509 -0
- benchkit/templates/report.html +769 -0
- benchkit/tui/__init__.py +5 -0
- benchkit/tui/app.py +87 -0
- benchkit/tui/app.tcss +512 -0
- benchkit/tui/formatting.py +84 -0
- benchkit/tui/screens/__init__.py +1 -0
- benchkit/tui/screens/connect.py +191 -0
- benchkit/tui/screens/detail.py +288 -0
- benchkit/tui/screens/help.py +84 -0
- benchkit/tui/screens/modals.py +544 -0
- benchkit/tui/screens/results.py +361 -0
- benchkit/tui/screens/run.py +988 -0
- benchkit/tui/screens/setup.py +733 -0
- benchkit/tui/theme.py +135 -0
- benchkit/tui/widgets.py +120 -0
- benchkit_cli-0.1.0.dist-info/METADATA +294 -0
- benchkit_cli-0.1.0.dist-info/RECORD +149 -0
- benchkit_cli-0.1.0.dist-info/WHEEL +5 -0
- benchkit_cli-0.1.0.dist-info/entry_points.txt +3 -0
- benchkit_cli-0.1.0.dist-info/licenses/LICENSE +201 -0
- benchkit_cli-0.1.0.dist-info/top_level.txt +1 -0
benchkit/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Benchmark local LLMs with real eval suites."""
|
benchkit/_pi_proxy.py
ADDED
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
"""Restricted inference proxy used by the Pi task sandbox.
|
|
2
|
+
|
|
3
|
+
The Pi container lives on an internal Docker network with no direct egress.
|
|
4
|
+
This small process runs in a separate container attached to both that network
|
|
5
|
+
and Docker's bridge network, forwards only OpenAI-compatible inference routes,
|
|
6
|
+
and keeps the upstream API key out of the agent's filesystem and environment.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import hashlib
|
|
12
|
+
import http.client
|
|
13
|
+
import json
|
|
14
|
+
import os
|
|
15
|
+
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
|
|
16
|
+
from urllib.parse import urlsplit
|
|
17
|
+
|
|
18
|
+
ALLOWED_PATHS = ("/v1/chat/completions", "/v1/models")
|
|
19
|
+
MAX_BODY_BYTES = 32 * 1024 * 1024
|
|
20
|
+
SCAFFOLD_PATH = "/tmp/benchkit_pi_scaffold.json"
|
|
21
|
+
HOP_BY_HOP = {
|
|
22
|
+
"connection",
|
|
23
|
+
"keep-alive",
|
|
24
|
+
"proxy-authenticate",
|
|
25
|
+
"proxy-authorization",
|
|
26
|
+
"te",
|
|
27
|
+
"trailers",
|
|
28
|
+
"transfer-encoding",
|
|
29
|
+
"upgrade",
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def _message_text(content: object) -> str:
|
|
34
|
+
if isinstance(content, str):
|
|
35
|
+
return content
|
|
36
|
+
if not isinstance(content, list):
|
|
37
|
+
return ""
|
|
38
|
+
return "".join(
|
|
39
|
+
str(part.get("text") or "")
|
|
40
|
+
for part in content
|
|
41
|
+
if isinstance(part, dict) and part.get("type") in {"text", "input_text"}
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def _capture_scaffold(request_data: dict) -> None:
|
|
46
|
+
"""Persist the exact scaffold sent by Pi on the first model request."""
|
|
47
|
+
if os.path.exists(SCAFFOLD_PATH):
|
|
48
|
+
return
|
|
49
|
+
messages = request_data.get("messages") or []
|
|
50
|
+
system_prompt = "\n\n".join(
|
|
51
|
+
_message_text(message.get("content"))
|
|
52
|
+
for message in messages
|
|
53
|
+
if isinstance(message, dict) and message.get("role") in {"system", "developer"}
|
|
54
|
+
)
|
|
55
|
+
tools = request_data.get("tools") or []
|
|
56
|
+
tool_names = [
|
|
57
|
+
str(tool.get("function", {}).get("name") or "")
|
|
58
|
+
for tool in tools
|
|
59
|
+
if isinstance(tool, dict) and isinstance(tool.get("function"), dict)
|
|
60
|
+
]
|
|
61
|
+
tool_names = [name for name in tool_names if name]
|
|
62
|
+
tool_schema = json.dumps(tools, ensure_ascii=False, sort_keys=True)
|
|
63
|
+
payload = {
|
|
64
|
+
"system_prompt": system_prompt,
|
|
65
|
+
"system_prompt_sha256": hashlib.sha256(system_prompt.encode()).hexdigest(),
|
|
66
|
+
"system_prompt_chars": len(system_prompt),
|
|
67
|
+
"tools_available": tool_names,
|
|
68
|
+
"tool_schema_sha256": hashlib.sha256(tool_schema.encode()).hexdigest(),
|
|
69
|
+
}
|
|
70
|
+
for field in ("max_completion_tokens", "max_tokens"):
|
|
71
|
+
value = request_data.get(field)
|
|
72
|
+
if isinstance(value, int) and value > 0:
|
|
73
|
+
payload["max_output_tokens"] = value
|
|
74
|
+
payload["max_output_tokens_field"] = field
|
|
75
|
+
break
|
|
76
|
+
try:
|
|
77
|
+
with open(SCAFFOLD_PATH, "x", encoding="utf-8") as handle:
|
|
78
|
+
json.dump(payload, handle, ensure_ascii=False)
|
|
79
|
+
except FileExistsError:
|
|
80
|
+
pass
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def _upstream_target(path: str) -> tuple[str, str, int, str]:
|
|
84
|
+
base = os.environ["BENCHKIT_UPSTREAM"].rstrip("/")
|
|
85
|
+
parsed = urlsplit(base)
|
|
86
|
+
request = urlsplit(path)
|
|
87
|
+
suffix = request.path[3:] if request.path.startswith("/v1") else request.path
|
|
88
|
+
prefix = parsed.path.rstrip("/")
|
|
89
|
+
target = f"{prefix}{suffix}" or "/"
|
|
90
|
+
query = "&".join(part for part in (parsed.query, request.query) if part)
|
|
91
|
+
if query:
|
|
92
|
+
target += f"?{query}"
|
|
93
|
+
port = parsed.port or (443 if parsed.scheme == "https" else 80)
|
|
94
|
+
return parsed.scheme, parsed.hostname or "", port, target
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def _models_payload() -> bytes:
|
|
98
|
+
model = os.environ["BENCHKIT_MODEL"]
|
|
99
|
+
return json.dumps(
|
|
100
|
+
{"object": "list", "data": [{"id": model, "object": "model"}]}
|
|
101
|
+
).encode()
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def _upstream_timeout() -> float:
|
|
105
|
+
try:
|
|
106
|
+
return max(0.1, float(os.environ.get("BENCHKIT_UPSTREAM_TIMEOUT", "600")))
|
|
107
|
+
except ValueError:
|
|
108
|
+
return 600.0
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
class ProxyHandler(BaseHTTPRequestHandler):
|
|
112
|
+
"""Stream a deliberately tiny subset of an OpenAI-compatible API."""
|
|
113
|
+
|
|
114
|
+
protocol_version = "HTTP/1.1"
|
|
115
|
+
|
|
116
|
+
def do_GET(self) -> None:
|
|
117
|
+
if self.path == "/health":
|
|
118
|
+
self._plain(200, b"ok")
|
|
119
|
+
return
|
|
120
|
+
self._forward()
|
|
121
|
+
|
|
122
|
+
def do_POST(self) -> None:
|
|
123
|
+
self._forward()
|
|
124
|
+
|
|
125
|
+
def _forward(self) -> None:
|
|
126
|
+
route = self.path.split("?", 1)[0]
|
|
127
|
+
if route not in ALLOWED_PATHS:
|
|
128
|
+
self._plain(404, b"route unavailable")
|
|
129
|
+
return
|
|
130
|
+
if route == "/v1/models":
|
|
131
|
+
if self.command != "GET":
|
|
132
|
+
self._plain(405, b"method unavailable")
|
|
133
|
+
return
|
|
134
|
+
self._json(200, _models_payload())
|
|
135
|
+
return
|
|
136
|
+
if self.command != "POST":
|
|
137
|
+
self._plain(405, b"method unavailable")
|
|
138
|
+
return
|
|
139
|
+
|
|
140
|
+
length = int(self.headers.get("content-length", "0") or 0)
|
|
141
|
+
if length > MAX_BODY_BYTES:
|
|
142
|
+
self._plain(413, b"request too large")
|
|
143
|
+
return
|
|
144
|
+
body = self.rfile.read(length) if length else None
|
|
145
|
+
try:
|
|
146
|
+
request_data = json.loads(body or b"{}")
|
|
147
|
+
except (json.JSONDecodeError, UnicodeDecodeError):
|
|
148
|
+
self._plain(400, b"invalid JSON")
|
|
149
|
+
return
|
|
150
|
+
_capture_scaffold(request_data)
|
|
151
|
+
if request_data.get("model") != os.environ["BENCHKIT_MODEL"]:
|
|
152
|
+
self._plain(403, b"model unavailable")
|
|
153
|
+
return
|
|
154
|
+
|
|
155
|
+
scheme, host, port, target = _upstream_target(self.path)
|
|
156
|
+
connection_type = (
|
|
157
|
+
http.client.HTTPSConnection
|
|
158
|
+
if scheme == "https"
|
|
159
|
+
else http.client.HTTPConnection
|
|
160
|
+
)
|
|
161
|
+
connection = connection_type(host, port, timeout=_upstream_timeout())
|
|
162
|
+
headers = {
|
|
163
|
+
key: value
|
|
164
|
+
for key, value in self.headers.items()
|
|
165
|
+
if key.lower() not in HOP_BY_HOP | {"host", "authorization"}
|
|
166
|
+
}
|
|
167
|
+
if api_key := os.environ.get("BENCHKIT_UPSTREAM_API_KEY"):
|
|
168
|
+
headers["Authorization"] = f"Bearer {api_key}"
|
|
169
|
+
|
|
170
|
+
response_started = False
|
|
171
|
+
try:
|
|
172
|
+
connection.request(self.command, target, body=body, headers=headers)
|
|
173
|
+
response = connection.getresponse()
|
|
174
|
+
self.send_response(response.status, response.reason)
|
|
175
|
+
for key, value in response.getheaders():
|
|
176
|
+
if key.lower() not in HOP_BY_HOP | {"content-length"}:
|
|
177
|
+
self.send_header(key, value)
|
|
178
|
+
self.send_header("Connection", "close")
|
|
179
|
+
self.end_headers()
|
|
180
|
+
response_started = True
|
|
181
|
+
while chunk := response.read(64 * 1024):
|
|
182
|
+
self.wfile.write(chunk)
|
|
183
|
+
self.wfile.flush()
|
|
184
|
+
except (OSError, http.client.HTTPException) as exc:
|
|
185
|
+
if not response_started and not self.wfile.closed:
|
|
186
|
+
self._plain(502, f"upstream error: {exc}".encode())
|
|
187
|
+
finally:
|
|
188
|
+
connection.close()
|
|
189
|
+
self.close_connection = True
|
|
190
|
+
|
|
191
|
+
def _plain(self, status: int, body: bytes) -> None:
|
|
192
|
+
self.send_response(status)
|
|
193
|
+
self.send_header("Content-Type", "text/plain; charset=utf-8")
|
|
194
|
+
self.send_header("Content-Length", str(len(body)))
|
|
195
|
+
self.send_header("Connection", "close")
|
|
196
|
+
self.end_headers()
|
|
197
|
+
self.wfile.write(body)
|
|
198
|
+
self.close_connection = True
|
|
199
|
+
|
|
200
|
+
def _json(self, status: int, body: bytes) -> None:
|
|
201
|
+
self.send_response(status)
|
|
202
|
+
self.send_header("Content-Type", "application/json")
|
|
203
|
+
self.send_header("Content-Length", str(len(body)))
|
|
204
|
+
self.send_header("Connection", "close")
|
|
205
|
+
self.end_headers()
|
|
206
|
+
self.wfile.write(body)
|
|
207
|
+
self.close_connection = True
|
|
208
|
+
|
|
209
|
+
def log_message(self, _format: str, *_args: object) -> None:
|
|
210
|
+
return
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
def main() -> None:
|
|
214
|
+
server = ThreadingHTTPServer(("0.0.0.0", 8080), ProxyHandler)
|
|
215
|
+
server.serve_forever()
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
if __name__ == "__main__":
|
|
219
|
+
main()
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
import { appendFileSync, realpathSync } from "node:fs";
|
|
3
|
+
import { isAbsolute, normalize, resolve } from "node:path";
|
|
4
|
+
|
|
5
|
+
const AUDIT_LOG = "/tmp/benchkit-answer-key-guard.jsonl";
|
|
6
|
+
const ANSWER_COMPONENT = /(^|[\\/])\.(meta|approaches)([\\/]|$)/i;
|
|
7
|
+
const ANSWER_NAME = /(^|[\\/])[^\\/]*(example|reference|proof)[^\\/]*([\\/]|$)/i;
|
|
8
|
+
|
|
9
|
+
function forbidden(value: unknown, cwd: string): boolean {
|
|
10
|
+
if (typeof value !== "string" || !value.trim()) return false;
|
|
11
|
+
const candidate = value.trim();
|
|
12
|
+
const normalized = normalize(isAbsolute(candidate) ? candidate : resolve(cwd, candidate));
|
|
13
|
+
let resolved = normalized;
|
|
14
|
+
try { resolved = realpathSync(normalized); } catch { /* missing/glob path */ }
|
|
15
|
+
return ANSWER_COMPONENT.test(candidate) || ANSWER_NAME.test(candidate)
|
|
16
|
+
|| ANSWER_COMPONENT.test(normalized) || ANSWER_NAME.test(normalized)
|
|
17
|
+
|| ANSWER_COMPONENT.test(resolved) || ANSWER_NAME.test(resolved);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function inspectedValues(toolName: string, input: Record<string, unknown>): string[] {
|
|
21
|
+
if (toolName === "bash") return [String(input.command ?? "")];
|
|
22
|
+
if (["read", "grep", "find", "ls"].includes(toolName)) {
|
|
23
|
+
return ["path", "pattern", "glob", "query"]
|
|
24
|
+
.map((key) => input[key])
|
|
25
|
+
.filter((value): value is string => typeof value === "string");
|
|
26
|
+
}
|
|
27
|
+
return [];
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export default function answerKeyGuard(pi: ExtensionAPI) {
|
|
31
|
+
pi.on("tool_call", async (event, ctx) => {
|
|
32
|
+
const input = event.input as Record<string, unknown>;
|
|
33
|
+
const matches = inspectedValues(event.toolName, input).filter((value) =>
|
|
34
|
+
forbidden(value, ctx.cwd),
|
|
35
|
+
);
|
|
36
|
+
if (!matches.length) return undefined;
|
|
37
|
+
|
|
38
|
+
appendFileSync(AUDIT_LOG, JSON.stringify({
|
|
39
|
+
tool: event.toolName,
|
|
40
|
+
arguments: input,
|
|
41
|
+
matches,
|
|
42
|
+
cwd: ctx.cwd,
|
|
43
|
+
}) + "\n", { encoding: "utf8", mode: 0o600 });
|
|
44
|
+
return {
|
|
45
|
+
block: true,
|
|
46
|
+
reason: "BenchKit denied access to an answer-key path",
|
|
47
|
+
};
|
|
48
|
+
});
|
|
49
|
+
}
|
benchkit/artifacts.py
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
"""Run-scoped staging for per-task binary artifacts.
|
|
2
|
+
|
|
3
|
+
Benchmarks produce screenshots and generated pages while a run is still in
|
|
4
|
+
flight, but the results directory only exists once :func:`benchkit.report.save`
|
|
5
|
+
stamps it. Artifacts are therefore written to a staging directory keyed by the
|
|
6
|
+
process, and collected into the results directory at save time with the paths
|
|
7
|
+
in each task record rewritten to be relative to the report.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
import contextlib
|
|
13
|
+
import re
|
|
14
|
+
import shutil
|
|
15
|
+
import uuid
|
|
16
|
+
from pathlib import Path
|
|
17
|
+
|
|
18
|
+
STAGING_ROOT = Path("results") / ".artifacts"
|
|
19
|
+
_RUN_ID = uuid.uuid4().hex[:12]
|
|
20
|
+
_COUNTER = 0
|
|
21
|
+
|
|
22
|
+
# Artifact keys in a task's ``workspace`` payload, and where they are collected.
|
|
23
|
+
COLLECTED = (
|
|
24
|
+
("screenshot", "screenshots", ".png"),
|
|
25
|
+
("screenshot_thumbnail", "screenshots", ".jpg"),
|
|
26
|
+
# mc-arena photographs one build from three fixed cameras; the plain
|
|
27
|
+
# ``screenshot`` above is the three of them side by side.
|
|
28
|
+
("screenshot_iso", "screenshots", "-iso.png"),
|
|
29
|
+
("screenshot_side", "screenshots", "-side.png"),
|
|
30
|
+
("screenshot_top", "screenshots", "-top.png"),
|
|
31
|
+
("page_html", "pages", ".html"),
|
|
32
|
+
("response_text", "pages", ".txt"),
|
|
33
|
+
("script_py", "builds", ".py"),
|
|
34
|
+
("blocks_json", "builds", ".json"),
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def staging_root() -> Path:
|
|
39
|
+
return STAGING_ROOT / _RUN_ID
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def task_dir(prefix: str) -> Path:
|
|
43
|
+
"""Create a fresh directory for one evaluation's artifacts."""
|
|
44
|
+
global _COUNTER
|
|
45
|
+
_COUNTER += 1
|
|
46
|
+
directory = staging_root() / f"{_slug(prefix)}-{_COUNTER:04d}"
|
|
47
|
+
directory.mkdir(parents=True, exist_ok=True)
|
|
48
|
+
# Absolute, so a path recorded mid-run still resolves at report time.
|
|
49
|
+
return directory.resolve()
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def _slug(value: str) -> str:
|
|
53
|
+
text = re.sub(r"[^A-Za-z0-9._-]+", "-", str(value)).strip("-")
|
|
54
|
+
return text[:80] or "artifact"
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def collect(out: Path, results: list[dict]) -> None:
|
|
58
|
+
"""Move staged artifacts into the report and relativize their paths."""
|
|
59
|
+
root = staging_root().resolve()
|
|
60
|
+
for result in results:
|
|
61
|
+
model = _slug(str(result.get("model") or "model"))
|
|
62
|
+
for task in result.get("tasks") or []:
|
|
63
|
+
workspace = task.get("workspace")
|
|
64
|
+
if not isinstance(workspace, dict):
|
|
65
|
+
continue
|
|
66
|
+
for key, subdir, suffix in COLLECTED:
|
|
67
|
+
source = workspace.get(key)
|
|
68
|
+
if not isinstance(source, str) or not source:
|
|
69
|
+
continue
|
|
70
|
+
path = Path(source)
|
|
71
|
+
if not path.is_absolute() or not path.is_file():
|
|
72
|
+
workspace.pop(key, None)
|
|
73
|
+
continue
|
|
74
|
+
if root not in path.resolve().parents:
|
|
75
|
+
continue
|
|
76
|
+
name = f"{model}__{_slug(str(task.get('task_id') or 'task'))}{suffix}"
|
|
77
|
+
target = out / subdir / name
|
|
78
|
+
target.parent.mkdir(parents=True, exist_ok=True)
|
|
79
|
+
shutil.copyfile(path, target)
|
|
80
|
+
workspace[key] = f"{subdir}/{name}"
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def cleanup() -> None:
|
|
84
|
+
"""Remove this process's staging directory, and the root when it empties."""
|
|
85
|
+
shutil.rmtree(staging_root(), ignore_errors=True)
|
|
86
|
+
with contextlib.suppress(OSError):
|
|
87
|
+
STAGING_ROOT.rmdir()
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
"""Available benchmarks."""
|
|
2
|
+
|
|
3
|
+
from benchkit.benchmarks.aider_polyglot import AiderPolyglot
|
|
4
|
+
from benchkit.benchmarks.arc import ARC
|
|
5
|
+
from benchkit.benchmarks.banking77 import Banking77
|
|
6
|
+
from benchkit.benchmarks.boolq import BoolQ
|
|
7
|
+
from benchkit.benchmarks.chartqa import ChartQA
|
|
8
|
+
from benchkit.benchmarks.code_verifier import CodeVerifier
|
|
9
|
+
from benchkit.benchmarks.evalplus import HumanEvalPlus, MBPPPlus
|
|
10
|
+
from benchkit.benchmarks.git_surgery import GitSurgery
|
|
11
|
+
from benchkit.benchmarks.gpqa import GPQA
|
|
12
|
+
from benchkit.benchmarks.gsm8k import GSM8K
|
|
13
|
+
from benchkit.benchmarks.hellaswag import HellaSwag
|
|
14
|
+
from benchkit.benchmarks.humaneval import HumanEval
|
|
15
|
+
from benchkit.benchmarks.ifeval import IFEval
|
|
16
|
+
from benchkit.benchmarks.mbpp import MBPP
|
|
17
|
+
from benchkit.benchmarks.mc_arena import MCArena
|
|
18
|
+
from benchkit.benchmarks.medfailbench import MedFailBench
|
|
19
|
+
from benchkit.benchmarks.mmlu import MMLU
|
|
20
|
+
from benchkit.benchmarks.mmlu_pro import MMLUPro
|
|
21
|
+
from benchkit.benchmarks.mmvp import MMVP
|
|
22
|
+
from benchkit.benchmarks.ocrbench import OCRBench
|
|
23
|
+
from benchkit.benchmarks.openbookqa import OpenBookQA
|
|
24
|
+
from benchkit.benchmarks.patcheval import PatchEval
|
|
25
|
+
from benchkit.benchmarks.piqa import PIQA
|
|
26
|
+
from benchkit.benchmarks.ruler import RULER, RULERFull
|
|
27
|
+
from benchkit.benchmarks.sanity import Sanity
|
|
28
|
+
from benchkit.benchmarks.treejs_arena import TreeJSArena
|
|
29
|
+
from benchkit.benchmarks.truthfulqa import TruthfulQA
|
|
30
|
+
from benchkit.benchmarks.vstar import VStar
|
|
31
|
+
from benchkit.benchmarks.winogrande import WinoGrande
|
|
32
|
+
from benchkit.benchmarks.xstest import XSTest
|
|
33
|
+
|
|
34
|
+
REGISTRY: dict[str, type] = {
|
|
35
|
+
# Generative suites first, then the multiple-choice ones.
|
|
36
|
+
"aider-polyglot": AiderPolyglot,
|
|
37
|
+
"git-surgery": GitSurgery,
|
|
38
|
+
"patcheval": PatchEval,
|
|
39
|
+
"sanity": Sanity,
|
|
40
|
+
"mc-arena": MCArena,
|
|
41
|
+
"treejs-arena": TreeJSArena,
|
|
42
|
+
"humaneval": HumanEval,
|
|
43
|
+
"humaneval-plus": HumanEvalPlus,
|
|
44
|
+
"mbpp": MBPP,
|
|
45
|
+
"mbpp-plus": MBPPPlus,
|
|
46
|
+
"gsm8k": GSM8K,
|
|
47
|
+
"ifeval": IFEval,
|
|
48
|
+
"ruler": RULER,
|
|
49
|
+
"ruler-full": RULERFull,
|
|
50
|
+
"xstest": XSTest,
|
|
51
|
+
"medfailbench": MedFailBench,
|
|
52
|
+
"gpqa": GPQA,
|
|
53
|
+
"mmlu-pro": MMLUPro,
|
|
54
|
+
"banking77": Banking77,
|
|
55
|
+
"code-verifier": CodeVerifier,
|
|
56
|
+
# Vision suites send images and run on the direct harness only.
|
|
57
|
+
"ocrbench": OCRBench,
|
|
58
|
+
"chartqa": ChartQA,
|
|
59
|
+
"mmvp": MMVP,
|
|
60
|
+
"vstar": VStar,
|
|
61
|
+
# Multiple-choice suites.
|
|
62
|
+
"mmlu": MMLU,
|
|
63
|
+
"arc": ARC,
|
|
64
|
+
"openbookqa": OpenBookQA,
|
|
65
|
+
"winogrande": WinoGrande,
|
|
66
|
+
"piqa": PIQA,
|
|
67
|
+
"boolq": BoolQ,
|
|
68
|
+
"truthfulqa": TruthfulQA,
|
|
69
|
+
"hellaswag": HellaSwag,
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
DESCRIPTIONS: dict[str, str] = {
|
|
73
|
+
"aider-polyglot": "repository editing across six languages with the Pi agent",
|
|
74
|
+
"git-surgery": "stateful Git operations in isolated repositories with Pi",
|
|
75
|
+
"patcheval": "real Python bug fixes with externally isolated hidden tests",
|
|
76
|
+
"mc-arena": "frozen Minecraft build prompts, scored on the block list a generated script prints",
|
|
77
|
+
"treejs-arena": "frozen creative three.js scenes, scored only on whether they render",
|
|
78
|
+
"sanity": "25 curated checks across code, math, instructions, science, and commonsense",
|
|
79
|
+
"humaneval": "Python function completions with the original unit tests",
|
|
80
|
+
"humaneval-plus": "HumanEval with 122k+ tougher EvalPlus test inputs",
|
|
81
|
+
"mbpp": "short Python functions from natural-language specifications",
|
|
82
|
+
"mbpp-plus": "sanitized MBPP tasks with 39k+ EvalPlus test inputs",
|
|
83
|
+
"gsm8k": "multi-step grade-school math problems with exact numeric answers",
|
|
84
|
+
"ifeval": "prompts with code-checkable instruction-following constraints",
|
|
85
|
+
"ruler": "practical long-context retrieval across all 13 RULER task families",
|
|
86
|
+
"ruler-full": "research-scale RULER with 500 samples per task and context",
|
|
87
|
+
"xstest": "safe compliance and unsafe refusal using an offline string matcher",
|
|
88
|
+
"medfailbench": "clinical safety screening with deterministic offline rules",
|
|
89
|
+
"arc": "challenging grade-school science questions with four choices",
|
|
90
|
+
"gpqa": "expert-written graduate science questions designed to resist search",
|
|
91
|
+
"mmlu-pro": "harder MMLU successor: ten options and reasoning-heavy questions",
|
|
92
|
+
"banking77": "route a banking customer message to one of 77 intents",
|
|
93
|
+
"code-verifier": "judge whether a model-written HumanEval+ solution passes its tests",
|
|
94
|
+
"ocrbench": "text recognition and text-centric VQA across ten image categories",
|
|
95
|
+
"chartqa": "questions about real charts, scored with 5% relaxed numeric accuracy",
|
|
96
|
+
"mmvp": "pairs of look-alike images that differ in one detail, two options",
|
|
97
|
+
"vstar": "small details in high-resolution images, two or four options",
|
|
98
|
+
"mmlu": "zero-shot coverage of 57 academic and professional subjects",
|
|
99
|
+
"openbookqa": "elementary science questions requiring facts plus reasoning",
|
|
100
|
+
"winogrande": "commonsense pronoun resolution in ambiguous sentences",
|
|
101
|
+
"piqa": "choose the most physically plausible solution to everyday tasks",
|
|
102
|
+
"boolq": "answer yes/no questions using evidence from a passage",
|
|
103
|
+
"truthfulqa": "avoid common misconceptions and select the truthful answer",
|
|
104
|
+
"hellaswag": "choose the most plausible continuation of a real-world scenario",
|
|
105
|
+
}
|