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
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
"""Lightweight chat-template checks for llama.cpp-compatible servers."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import re
|
|
6
|
+
from dataclasses import dataclass, field
|
|
7
|
+
from typing import Literal
|
|
8
|
+
from urllib.parse import quote
|
|
9
|
+
|
|
10
|
+
import httpx
|
|
11
|
+
|
|
12
|
+
from benchkit.client import InferenceClient, _without_v1
|
|
13
|
+
|
|
14
|
+
Level = Literal["pass", "warn", "fail"]
|
|
15
|
+
|
|
16
|
+
MARKERS = {
|
|
17
|
+
"system": "BK_SYSTEM_7F3A",
|
|
18
|
+
"user_1": "BK_USER_11B2",
|
|
19
|
+
"assistant": "BK_ASSISTANT_22C4",
|
|
20
|
+
"user_2": "BK_USER_33D6",
|
|
21
|
+
}
|
|
22
|
+
PROBE = [
|
|
23
|
+
{"role": "system", "content": MARKERS["system"]},
|
|
24
|
+
{"role": "user", "content": MARKERS["user_1"]},
|
|
25
|
+
{"role": "assistant", "content": MARKERS["assistant"]},
|
|
26
|
+
{"role": "user", "content": MARKERS["user_2"]},
|
|
27
|
+
]
|
|
28
|
+
|
|
29
|
+
SPECIAL_TOKEN_RE = re.compile(
|
|
30
|
+
r"<\|[^|<>\s]{1,64}\|>|</?[a-z][a-z0-9_]{0,63}>|\[/?INST\]|\[/?SYSTEM_PROMPT\]",
|
|
31
|
+
re.IGNORECASE,
|
|
32
|
+
)
|
|
33
|
+
NONDETERMINISTIC_RE = re.compile(
|
|
34
|
+
r"strftime_now|\bnow\s*\(|datetime\.|random",
|
|
35
|
+
re.IGNORECASE,
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class NativeEndpointUnavailable(RuntimeError):
|
|
40
|
+
"""Raised when the server does not expose llama.cpp native endpoints."""
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
@dataclass(frozen=True)
|
|
44
|
+
class TemplateCheck:
|
|
45
|
+
name: str
|
|
46
|
+
level: Level
|
|
47
|
+
detail: str = ""
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
@dataclass
|
|
51
|
+
class TemplateReport:
|
|
52
|
+
model: str
|
|
53
|
+
checks: list[TemplateCheck] = field(default_factory=list)
|
|
54
|
+
unavailable: bool = False
|
|
55
|
+
|
|
56
|
+
def add(self, name: str, level: Level, detail: str = "") -> None:
|
|
57
|
+
self.checks.append(TemplateCheck(name, level, detail))
|
|
58
|
+
|
|
59
|
+
@property
|
|
60
|
+
def status(self) -> str:
|
|
61
|
+
if self.unavailable:
|
|
62
|
+
return "unavailable"
|
|
63
|
+
if any(check.level == "fail" for check in self.checks):
|
|
64
|
+
return "fail"
|
|
65
|
+
if any(check.level == "warn" for check in self.checks):
|
|
66
|
+
return "warn"
|
|
67
|
+
return "pass"
|
|
68
|
+
|
|
69
|
+
@property
|
|
70
|
+
def detail(self) -> str:
|
|
71
|
+
problems = [
|
|
72
|
+
check.detail or check.name
|
|
73
|
+
for check in self.checks
|
|
74
|
+
if check.level in {"warn", "fail"}
|
|
75
|
+
]
|
|
76
|
+
return "; ".join(problems) or "All checks passed"
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
class _NativeAPI:
|
|
80
|
+
"""Route native requests through llama-swap or a direct llama.cpp server."""
|
|
81
|
+
|
|
82
|
+
def __init__(self, client: InferenceClient, model: str) -> None:
|
|
83
|
+
self.client = client
|
|
84
|
+
self.model = model
|
|
85
|
+
self.base = _without_v1(client.host)
|
|
86
|
+
self.route: Literal["upstream", "direct"] | None = None
|
|
87
|
+
|
|
88
|
+
def _url(self, route: str, path: str) -> str:
|
|
89
|
+
if route == "upstream":
|
|
90
|
+
model = quote(self.model, safe="")
|
|
91
|
+
return f"{self.base}/upstream/{model}{path}"
|
|
92
|
+
return f"{self.base}{path}"
|
|
93
|
+
|
|
94
|
+
def request(self, method: str, path: str, body: dict | None = None) -> dict:
|
|
95
|
+
routes = [self.route] if self.route else ["upstream", "direct"]
|
|
96
|
+
last_response: httpx.Response | None = None
|
|
97
|
+
for route in routes:
|
|
98
|
+
payload = dict(body or {})
|
|
99
|
+
params = None
|
|
100
|
+
if route == "direct":
|
|
101
|
+
payload.setdefault("model", self.model)
|
|
102
|
+
if method == "GET":
|
|
103
|
+
params = {"model": self.model}
|
|
104
|
+
response = httpx.request(
|
|
105
|
+
method,
|
|
106
|
+
self._url(route, path),
|
|
107
|
+
headers=self.client._headers(),
|
|
108
|
+
params=params,
|
|
109
|
+
json=payload,
|
|
110
|
+
timeout=min(self.client.timeout, 120),
|
|
111
|
+
)
|
|
112
|
+
last_response = response
|
|
113
|
+
if response.status_code in {404, 405}:
|
|
114
|
+
if self.route is None:
|
|
115
|
+
continue
|
|
116
|
+
raise NativeEndpointUnavailable(f"{path} is not exposed by this server")
|
|
117
|
+
response.raise_for_status()
|
|
118
|
+
data = response.json()
|
|
119
|
+
if not isinstance(data, dict):
|
|
120
|
+
raise ValueError(f"{path} returned a non-object response")
|
|
121
|
+
self.route = route
|
|
122
|
+
return data
|
|
123
|
+
|
|
124
|
+
detail = "native llama.cpp endpoints are not exposed"
|
|
125
|
+
if last_response is not None and last_response.status_code not in {404, 405}:
|
|
126
|
+
detail = f"HTTP {last_response.status_code}"
|
|
127
|
+
raise NativeEndpointUnavailable(detail)
|
|
128
|
+
|
|
129
|
+
def props(self) -> dict:
|
|
130
|
+
return self.request("GET", "/props")
|
|
131
|
+
|
|
132
|
+
def render(self, messages: list[dict], *, generation_prompt: bool) -> str:
|
|
133
|
+
data = self.request(
|
|
134
|
+
"POST",
|
|
135
|
+
"/apply-template",
|
|
136
|
+
{
|
|
137
|
+
"messages": messages,
|
|
138
|
+
"add_generation_prompt": generation_prompt,
|
|
139
|
+
},
|
|
140
|
+
)
|
|
141
|
+
prompt = data.get("prompt")
|
|
142
|
+
if not isinstance(prompt, str):
|
|
143
|
+
raise ValueError("/apply-template response has no prompt")
|
|
144
|
+
return prompt
|
|
145
|
+
|
|
146
|
+
def tokenize(self, text: str, *, add_special: bool) -> list:
|
|
147
|
+
data = self.request(
|
|
148
|
+
"POST",
|
|
149
|
+
"/tokenize",
|
|
150
|
+
{
|
|
151
|
+
"content": text,
|
|
152
|
+
"add_special": add_special,
|
|
153
|
+
"parse_special": True,
|
|
154
|
+
"with_pieces": False,
|
|
155
|
+
},
|
|
156
|
+
)
|
|
157
|
+
tokens = data.get("tokens")
|
|
158
|
+
if not isinstance(tokens, list):
|
|
159
|
+
raise ValueError("/tokenize response has no token list")
|
|
160
|
+
return tokens
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
def _template_from_props(props: dict) -> str:
|
|
164
|
+
template = props.get("chat_template")
|
|
165
|
+
if isinstance(template, str):
|
|
166
|
+
return template
|
|
167
|
+
defaults = props.get("default_generation_settings")
|
|
168
|
+
if isinstance(defaults, dict):
|
|
169
|
+
template = defaults.get("chat_template")
|
|
170
|
+
if isinstance(template, str):
|
|
171
|
+
return template
|
|
172
|
+
return ""
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
def check_template(client: InferenceClient, model: str) -> TemplateReport:
|
|
176
|
+
"""Run reference-free structural checks for one served model."""
|
|
177
|
+
report = TemplateReport(model)
|
|
178
|
+
if client.provider != "openai":
|
|
179
|
+
report.unavailable = True
|
|
180
|
+
report.add("native endpoints", "warn", "requires llama.cpp or llama-swap")
|
|
181
|
+
return report
|
|
182
|
+
|
|
183
|
+
api = _NativeAPI(client, model)
|
|
184
|
+
template = ""
|
|
185
|
+
try:
|
|
186
|
+
template = _template_from_props(api.props())
|
|
187
|
+
except NativeEndpointUnavailable:
|
|
188
|
+
# Some builds expose /apply-template without /props. Rendering remains
|
|
189
|
+
# useful, so only declare the feature unavailable if that also fails.
|
|
190
|
+
pass
|
|
191
|
+
except Exception as exc:
|
|
192
|
+
report.add("template metadata", "warn", str(exc))
|
|
193
|
+
|
|
194
|
+
report.add(
|
|
195
|
+
"template exposed",
|
|
196
|
+
"pass" if template else "warn",
|
|
197
|
+
f"{len(template)} characters" if template else "template source not reported",
|
|
198
|
+
)
|
|
199
|
+
|
|
200
|
+
try:
|
|
201
|
+
rendered = api.render(PROBE, generation_prompt=True)
|
|
202
|
+
except httpx.HTTPStatusError as exc:
|
|
203
|
+
detail = exc.response.text.strip().replace("\n", " ")[:160]
|
|
204
|
+
report.add(
|
|
205
|
+
"render probe",
|
|
206
|
+
"fail",
|
|
207
|
+
detail or f"HTTP {exc.response.status_code}",
|
|
208
|
+
)
|
|
209
|
+
return report
|
|
210
|
+
except NativeEndpointUnavailable as exc:
|
|
211
|
+
report.unavailable = True
|
|
212
|
+
report.add("render probe", "warn", str(exc))
|
|
213
|
+
return report
|
|
214
|
+
except Exception as exc:
|
|
215
|
+
report.add("render probe", "fail", f"{type(exc).__name__}: {exc}")
|
|
216
|
+
return report
|
|
217
|
+
report.add("render probe", "pass", f"{len(rendered)} characters")
|
|
218
|
+
|
|
219
|
+
try:
|
|
220
|
+
repeated = api.render(PROBE, generation_prompt=True)
|
|
221
|
+
if repeated != rendered:
|
|
222
|
+
report.add(
|
|
223
|
+
"deterministic render",
|
|
224
|
+
"fail",
|
|
225
|
+
"same messages rendered differently",
|
|
226
|
+
)
|
|
227
|
+
elif template and NONDETERMINISTIC_RE.search(template):
|
|
228
|
+
report.add(
|
|
229
|
+
"deterministic render",
|
|
230
|
+
"warn",
|
|
231
|
+
"template contains time or random logic",
|
|
232
|
+
)
|
|
233
|
+
else:
|
|
234
|
+
report.add("deterministic render", "pass")
|
|
235
|
+
except Exception as exc:
|
|
236
|
+
report.add("deterministic render", "warn", str(exc))
|
|
237
|
+
|
|
238
|
+
positions = {name: rendered.find(marker) for name, marker in MARKERS.items()}
|
|
239
|
+
missing = [name for name, position in positions.items() if position < 0]
|
|
240
|
+
if missing:
|
|
241
|
+
report.add("messages preserved", "fail", f"missing: {', '.join(missing)}")
|
|
242
|
+
elif list(positions.values()) != sorted(positions.values()):
|
|
243
|
+
report.add("messages preserved", "fail", "message order changed")
|
|
244
|
+
elif any(rendered.count(marker) != 1 for marker in MARKERS.values()):
|
|
245
|
+
report.add("messages preserved", "fail", "a message was duplicated")
|
|
246
|
+
else:
|
|
247
|
+
report.add("messages preserved", "pass")
|
|
248
|
+
|
|
249
|
+
tail_start = positions["user_2"] + len(MARKERS["user_2"])
|
|
250
|
+
if positions["user_2"] >= 0 and rendered[tail_start:].strip():
|
|
251
|
+
report.add("generation prompt", "pass")
|
|
252
|
+
else:
|
|
253
|
+
report.add(
|
|
254
|
+
"generation prompt",
|
|
255
|
+
"fail",
|
|
256
|
+
"nothing follows the final user message",
|
|
257
|
+
)
|
|
258
|
+
|
|
259
|
+
specials = sorted(set(SPECIAL_TOKEN_RE.findall(rendered)))
|
|
260
|
+
try:
|
|
261
|
+
split = [
|
|
262
|
+
token
|
|
263
|
+
for token in specials
|
|
264
|
+
if len(api.tokenize(token, add_special=False)) != 1
|
|
265
|
+
]
|
|
266
|
+
if split:
|
|
267
|
+
report.add(
|
|
268
|
+
"special tokens",
|
|
269
|
+
"fail",
|
|
270
|
+
f"split by tokenizer: {', '.join(split[:3])}",
|
|
271
|
+
)
|
|
272
|
+
else:
|
|
273
|
+
report.add("special tokens", "pass", f"{len(specials)} checked")
|
|
274
|
+
|
|
275
|
+
empty = api.tokenize("", add_special=True)
|
|
276
|
+
prompt_tokens = api.tokenize(rendered, add_special=True)
|
|
277
|
+
double_bos = (
|
|
278
|
+
empty
|
|
279
|
+
and len(prompt_tokens) > 1
|
|
280
|
+
and prompt_tokens[:2] == [empty[0], empty[0]]
|
|
281
|
+
)
|
|
282
|
+
if double_bos:
|
|
283
|
+
report.add("single BOS", "fail", f"token {empty[0]} appears twice")
|
|
284
|
+
else:
|
|
285
|
+
report.add("single BOS", "pass")
|
|
286
|
+
except NativeEndpointUnavailable:
|
|
287
|
+
report.add("tokenization checks", "warn", "/tokenize is not exposed")
|
|
288
|
+
except Exception as exc:
|
|
289
|
+
report.add("tokenization checks", "warn", str(exc))
|
|
290
|
+
|
|
291
|
+
return report
|