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.
Files changed (149) hide show
  1. benchkit/__init__.py +1 -0
  2. benchkit/_pi_proxy.py +219 -0
  3. benchkit/answer_key_guard.ts +49 -0
  4. benchkit/artifacts.py +87 -0
  5. benchkit/benchmarks/__init__.py +105 -0
  6. benchkit/benchmarks/aider_polyglot.py +405 -0
  7. benchkit/benchmarks/arc.py +52 -0
  8. benchkit/benchmarks/banking77.py +149 -0
  9. benchkit/benchmarks/base.py +19 -0
  10. benchkit/benchmarks/boolq.py +49 -0
  11. benchkit/benchmarks/chartqa.py +133 -0
  12. benchkit/benchmarks/code_verifier.py +77 -0
  13. benchkit/benchmarks/downloads.py +166 -0
  14. benchkit/benchmarks/evalplus.py +288 -0
  15. benchkit/benchmarks/git_surgery.py +485 -0
  16. benchkit/benchmarks/gpqa.py +56 -0
  17. benchkit/benchmarks/gsm8k.py +97 -0
  18. benchkit/benchmarks/hellaswag.py +49 -0
  19. benchkit/benchmarks/humaneval.py +103 -0
  20. benchkit/benchmarks/ifeval.py +56 -0
  21. benchkit/benchmarks/ifeval_instructions.py +477 -0
  22. benchkit/benchmarks/mbpp.py +60 -0
  23. benchkit/benchmarks/mc_arena.py +479 -0
  24. benchkit/benchmarks/mcq.py +94 -0
  25. benchkit/benchmarks/medfailbench.py +537 -0
  26. benchkit/benchmarks/mmlu.py +54 -0
  27. benchkit/benchmarks/mmlu_pro.py +62 -0
  28. benchkit/benchmarks/mmvp.py +115 -0
  29. benchkit/benchmarks/ocrbench.py +96 -0
  30. benchkit/benchmarks/openbookqa.py +52 -0
  31. benchkit/benchmarks/patcheval.py +929 -0
  32. benchkit/benchmarks/piqa.py +52 -0
  33. benchkit/benchmarks/ruler.py +172 -0
  34. benchkit/benchmarks/ruler_generator.py +535 -0
  35. benchkit/benchmarks/sanity.py +174 -0
  36. benchkit/benchmarks/treejs_arena.py +268 -0
  37. benchkit/benchmarks/truthfulqa.py +52 -0
  38. benchkit/benchmarks/utils.py +25 -0
  39. benchkit/benchmarks/vision.py +230 -0
  40. benchkit/benchmarks/vstar.py +108 -0
  41. benchkit/benchmarks/winogrande.py +52 -0
  42. benchkit/benchmarks/xstest.py +116 -0
  43. benchkit/browser.py +429 -0
  44. benchkit/catalog.py +121 -0
  45. benchkit/cli.py +1000 -0
  46. benchkit/client.py +1413 -0
  47. benchkit/controls.py +87 -0
  48. benchkit/datasets/MMLU_LICENSE.txt +21 -0
  49. benchkit/datasets/README.md +142 -0
  50. benchkit/datasets/arc_challenge.jsonl +1172 -0
  51. benchkit/datasets/banking77.jsonl +3080 -0
  52. benchkit/datasets/banking77_intents.json +79 -0
  53. benchkit/datasets/boolq.jsonl +3270 -0
  54. benchkit/datasets/code_verifier.jsonl +500 -0
  55. benchkit/datasets/gpqa_diamond.jsonl +198 -0
  56. benchkit/datasets/gsm8k.jsonl +1319 -0
  57. benchkit/datasets/hellaswag.jsonl +1000 -0
  58. benchkit/datasets/humaneval.jsonl +164 -0
  59. benchkit/datasets/ifeval.jsonl +541 -0
  60. benchkit/datasets/mbpp.jsonl +500 -0
  61. benchkit/datasets/mc_blocks_1_20_1.jsonl +1003 -0
  62. benchkit/datasets/medfailbench_hard30.jsonl +30 -0
  63. benchkit/datasets/medfailbench_hard30.md +13 -0
  64. benchkit/datasets/openbookqa.jsonl +500 -0
  65. benchkit/datasets/piqa.jsonl +1838 -0
  66. benchkit/datasets/sanity_code.jsonl +6 -0
  67. benchkit/datasets/truthfulqa.jsonl +817 -0
  68. benchkit/datasets/winogrande.jsonl +1267 -0
  69. benchkit/datasets/xstest.csv +451 -0
  70. benchkit/decision.py +194 -0
  71. benchkit/demo.py +451 -0
  72. benchkit/dockerfiles.py +247 -0
  73. benchkit/engine.py +1270 -0
  74. benchkit/evaluation.py +195 -0
  75. benchkit/events.py +141 -0
  76. benchkit/executor.py +96 -0
  77. benchkit/git_surgery/backport-release-stack/setup.sh +328 -0
  78. benchkit/git_surgery/backport-release-stack/verify.sh +119 -0
  79. benchkit/git_surgery/bisect-the-regression/setup.sh +49 -0
  80. benchkit/git_surgery/bisect-the-regression/verify.sh +34 -0
  81. benchkit/git_surgery/rebase-conflict-chain/setup.sh +65 -0
  82. benchkit/git_surgery/rebase-conflict-chain/verify.sh +43 -0
  83. benchkit/git_surgery/recover-complex-stash/setup.sh +152 -0
  84. benchkit/git_surgery/recover-complex-stash/verify.sh +95 -0
  85. benchkit/git_surgery/recover-lost-work/setup.sh +40 -0
  86. benchkit/git_surgery/recover-lost-work/verify.sh +36 -0
  87. benchkit/git_surgery/repair-force-pushed-remote/setup.sh +171 -0
  88. benchkit/git_surgery/repair-force-pushed-remote/verify.sh +83 -0
  89. benchkit/git_surgery/revert-merge-with-followups/setup.sh +198 -0
  90. benchkit/git_surgery/revert-merge-with-followups/verify.sh +98 -0
  91. benchkit/git_surgery/secret-in-history/README.md +27 -0
  92. benchkit/git_surgery/secret-in-history/setup.sh +107 -0
  93. benchkit/git_surgery/secret-in-history/verify.sh +106 -0
  94. benchkit/git_surgery/split-the-mega-commit/setup.sh +50 -0
  95. benchkit/git_surgery/split-the-mega-commit/verify.sh +48 -0
  96. benchkit/git_surgery/untangle-nested-submodules/setup.sh +193 -0
  97. benchkit/git_surgery/untangle-nested-submodules/verify.sh +90 -0
  98. benchkit/history.py +474 -0
  99. benchkit/jobs.py +332 -0
  100. benchkit/leakage.py +51 -0
  101. benchkit/looping.py +873 -0
  102. benchkit/mc_render.py +274 -0
  103. benchkit/mc_viewer/dist/atlas.png +0 -0
  104. benchkit/mc_viewer/dist/blockStates.json.gz +0 -0
  105. benchkit/mc_viewer/dist/version.json +4 -0
  106. benchkit/mc_viewer/dist/viewer.html +23 -0
  107. benchkit/mc_viewer/dist/viewer.js +2 -0
  108. benchkit/mc_viewer/dist/viewer.js.LICENSE.txt +25 -0
  109. benchkit/mc_viewer/dist/worker.js +2 -0
  110. benchkit/mc_viewer/dist/worker.js.LICENSE.txt +19 -0
  111. benchkit/metrics.py +100 -0
  112. benchkit/ollama.py +60 -0
  113. benchkit/perf.py +353 -0
  114. benchkit/perf_report.py +207 -0
  115. benchkit/perturbations.py +208 -0
  116. benchkit/pi_agent.py +476 -0
  117. benchkit/pi_package/package-lock.json +1827 -0
  118. benchkit/pi_package/package.json +8 -0
  119. benchkit/report.py +142 -0
  120. benchkit/report_markdown.py +645 -0
  121. benchkit/results.py +688 -0
  122. benchkit/retry.py +163 -0
  123. benchkit/runner.py +769 -0
  124. benchkit/sandbox.py +1167 -0
  125. benchkit/template_check.py +291 -0
  126. benchkit/templates/arena.html +504 -0
  127. benchkit/templates/history.html +58 -0
  128. benchkit/templates/perf.html +509 -0
  129. benchkit/templates/report.html +769 -0
  130. benchkit/tui/__init__.py +5 -0
  131. benchkit/tui/app.py +87 -0
  132. benchkit/tui/app.tcss +512 -0
  133. benchkit/tui/formatting.py +84 -0
  134. benchkit/tui/screens/__init__.py +1 -0
  135. benchkit/tui/screens/connect.py +191 -0
  136. benchkit/tui/screens/detail.py +288 -0
  137. benchkit/tui/screens/help.py +84 -0
  138. benchkit/tui/screens/modals.py +544 -0
  139. benchkit/tui/screens/results.py +361 -0
  140. benchkit/tui/screens/run.py +988 -0
  141. benchkit/tui/screens/setup.py +733 -0
  142. benchkit/tui/theme.py +135 -0
  143. benchkit/tui/widgets.py +120 -0
  144. benchkit_cli-0.1.0.dist-info/METADATA +294 -0
  145. benchkit_cli-0.1.0.dist-info/RECORD +149 -0
  146. benchkit_cli-0.1.0.dist-info/WHEEL +5 -0
  147. benchkit_cli-0.1.0.dist-info/entry_points.txt +3 -0
  148. benchkit_cli-0.1.0.dist-info/licenses/LICENSE +201 -0
  149. 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")