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/retry.py
ADDED
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
"""Retry policy for transient inference-server failures.
|
|
2
|
+
|
|
3
|
+
Inference servers behind proxies or model swappers routinely answer with
|
|
4
|
+
``502``/``503`` while a model loads or a worker restarts, and busy servers such
|
|
5
|
+
as ``llama-server`` answer with ``429`` when their slots are full. Those
|
|
6
|
+
failures are short-lived, so BenchKit retries them with exponential backoff
|
|
7
|
+
instead of marking a task as errored. When the server says how long to wait via
|
|
8
|
+
``Retry-After``, that hint wins over the computed backoff.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
import os
|
|
14
|
+
import random
|
|
15
|
+
import threading
|
|
16
|
+
import time
|
|
17
|
+
from collections.abc import Callable
|
|
18
|
+
from dataclasses import dataclass
|
|
19
|
+
from datetime import UTC, datetime
|
|
20
|
+
from email.utils import parsedate_to_datetime
|
|
21
|
+
from typing import TypeVar
|
|
22
|
+
|
|
23
|
+
import httpx
|
|
24
|
+
|
|
25
|
+
T = TypeVar("T")
|
|
26
|
+
RetryNotice = Callable[[int, float, Exception], None]
|
|
27
|
+
|
|
28
|
+
# Gateway, overload, and rate-limit responses are worth another attempt;
|
|
29
|
+
# 4xx client errors such as 400/404/422 are not.
|
|
30
|
+
RETRYABLE_STATUS = frozenset({408, 425, 429, 500, 502, 503, 504, 522, 524, 529})
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def _env_float(name: str, default: float) -> float:
|
|
34
|
+
raw = os.environ.get(name)
|
|
35
|
+
if not raw:
|
|
36
|
+
return default
|
|
37
|
+
try:
|
|
38
|
+
return float(raw)
|
|
39
|
+
except ValueError:
|
|
40
|
+
return default
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def _env_int(name: str, default: int) -> int:
|
|
44
|
+
raw = os.environ.get(name)
|
|
45
|
+
if not raw:
|
|
46
|
+
return default
|
|
47
|
+
try:
|
|
48
|
+
return int(raw)
|
|
49
|
+
except ValueError:
|
|
50
|
+
return default
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def is_retryable(exc: Exception) -> bool:
|
|
54
|
+
"""Report whether a request failure is likely transient."""
|
|
55
|
+
if isinstance(exc, httpx.HTTPStatusError):
|
|
56
|
+
return exc.response.status_code in RETRYABLE_STATUS
|
|
57
|
+
# Connection resets, DNS hiccups, and read timeouts against a busy server.
|
|
58
|
+
return isinstance(exc, httpx.TransportError)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def retry_after_seconds(exc: Exception) -> float | None:
|
|
62
|
+
"""Read the server's ``Retry-After`` hint, in seconds, when it sent one.
|
|
63
|
+
|
|
64
|
+
Rate limiters answer ``429`` with either a delta-seconds count or an
|
|
65
|
+
HTTP-date. Anything unparsable, negative, or already in the past is ignored
|
|
66
|
+
so the caller falls back to its own backoff.
|
|
67
|
+
"""
|
|
68
|
+
if not isinstance(exc, httpx.HTTPStatusError):
|
|
69
|
+
return None
|
|
70
|
+
raw = exc.response.headers.get("retry-after")
|
|
71
|
+
if not raw:
|
|
72
|
+
return None
|
|
73
|
+
raw = raw.strip()
|
|
74
|
+
try:
|
|
75
|
+
return max(0.0, float(raw))
|
|
76
|
+
except ValueError:
|
|
77
|
+
pass
|
|
78
|
+
try:
|
|
79
|
+
deadline = parsedate_to_datetime(raw)
|
|
80
|
+
except (TypeError, ValueError):
|
|
81
|
+
return None
|
|
82
|
+
if deadline is None:
|
|
83
|
+
return None
|
|
84
|
+
if deadline.tzinfo is None:
|
|
85
|
+
deadline = deadline.replace(tzinfo=UTC)
|
|
86
|
+
return max(0.0, (deadline - datetime.now(UTC)).total_seconds())
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
@dataclass(frozen=True)
|
|
90
|
+
class RetryPolicy:
|
|
91
|
+
"""Exponential backoff with jitter, capped at ``max_delay``."""
|
|
92
|
+
|
|
93
|
+
attempts: int = 3
|
|
94
|
+
# DNS failures and refused/reset connections often outlive the short retry
|
|
95
|
+
# window used for an overloaded server response. Give failures that happen
|
|
96
|
+
# before a connection is established a separate, longer budget.
|
|
97
|
+
connect_attempts: int = 6
|
|
98
|
+
base_delay: float = 0.5
|
|
99
|
+
max_delay: float = 8.0
|
|
100
|
+
jitter: float = 0.25
|
|
101
|
+
# A rate limiter may ask for a longer pause than our own backoff cap, so
|
|
102
|
+
# ``Retry-After`` gets a separate, more generous ceiling.
|
|
103
|
+
max_retry_after: float = 60.0
|
|
104
|
+
|
|
105
|
+
@classmethod
|
|
106
|
+
def from_env(cls) -> RetryPolicy:
|
|
107
|
+
return cls(
|
|
108
|
+
attempts=max(1, _env_int("BENCHKIT_RETRIES", 3)),
|
|
109
|
+
connect_attempts=max(1, _env_int("BENCHKIT_CONNECT_RETRIES", 6)),
|
|
110
|
+
base_delay=max(0.0, _env_float("BENCHKIT_RETRY_BASE_DELAY", 0.5)),
|
|
111
|
+
max_delay=max(0.0, _env_float("BENCHKIT_RETRY_MAX_DELAY", 8.0)),
|
|
112
|
+
max_retry_after=max(0.0, _env_float("BENCHKIT_RETRY_MAX_WAIT", 60.0)),
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
def delay_for(self, attempt: int, retry_after: float | None = None) -> float:
|
|
116
|
+
"""Backoff before retrying, where ``attempt`` is the 1-based try.
|
|
117
|
+
|
|
118
|
+
``retry_after`` is the server's own hint; when present it replaces the
|
|
119
|
+
computed backoff (clamped to ``max_retry_after``) rather than racing it,
|
|
120
|
+
since retrying sooner than asked just earns another rate-limit answer.
|
|
121
|
+
"""
|
|
122
|
+
if retry_after is not None:
|
|
123
|
+
return min(self.max_retry_after, retry_after)
|
|
124
|
+
delay = min(self.max_delay, self.base_delay * (2 ** (attempt - 1)))
|
|
125
|
+
return delay + random.uniform(0.0, self.jitter * delay)
|
|
126
|
+
|
|
127
|
+
def sleep(self, seconds: float, cancel_event: threading.Event | None) -> bool:
|
|
128
|
+
"""Wait out the backoff; return False when the user stopped the run."""
|
|
129
|
+
if cancel_event is not None:
|
|
130
|
+
return not cancel_event.wait(seconds)
|
|
131
|
+
time.sleep(seconds)
|
|
132
|
+
return True
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def run_with_retries(
|
|
136
|
+
policy: RetryPolicy,
|
|
137
|
+
operation: Callable[[], T],
|
|
138
|
+
*,
|
|
139
|
+
cancel_event: threading.Event | None = None,
|
|
140
|
+
retryable: Callable[[Exception], bool] = is_retryable,
|
|
141
|
+
on_retry: RetryNotice | None = None,
|
|
142
|
+
) -> T:
|
|
143
|
+
"""Call ``operation`` until it succeeds or its failure stops being transient."""
|
|
144
|
+
max_attempts = max(policy.attempts, policy.connect_attempts)
|
|
145
|
+
for attempt in range(1, max_attempts + 1):
|
|
146
|
+
try:
|
|
147
|
+
return operation()
|
|
148
|
+
except Exception as exc: # re-raised below when not retryable
|
|
149
|
+
attempt_limit = (
|
|
150
|
+
policy.connect_attempts
|
|
151
|
+
if isinstance(exc, httpx.ConnectError)
|
|
152
|
+
else policy.attempts
|
|
153
|
+
)
|
|
154
|
+
if attempt >= attempt_limit or not retryable(exc):
|
|
155
|
+
raise
|
|
156
|
+
if cancel_event is not None and cancel_event.is_set():
|
|
157
|
+
raise
|
|
158
|
+
delay = policy.delay_for(attempt, retry_after_seconds(exc))
|
|
159
|
+
if on_retry is not None:
|
|
160
|
+
on_retry(attempt, delay, exc)
|
|
161
|
+
if not policy.sleep(delay, cancel_event):
|
|
162
|
+
raise
|
|
163
|
+
raise AssertionError("unreachable: retry loop exhausted without a result")
|