@wendellresearch-ai/benchwarmers 0.2.0 → 0.2.2

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 (103) hide show
  1. package/AGENTS.md +152 -147
  2. package/BENCHMARKS.md +262 -262
  3. package/GATEWAY.md +415 -272
  4. package/NOTICE +28 -28
  5. package/PRIVACY.md +46 -46
  6. package/README.md +408 -314
  7. package/SECURITY.md +83 -83
  8. package/TERMS.md +22 -22
  9. package/USAGE.md +186 -163
  10. package/VERSION +1 -1
  11. package/bin/benchwarmers.js +16 -74
  12. package/cli.py +674 -0
  13. package/examples/config/gateway.json +5 -5
  14. package/examples/config/model-roles.json +11 -11
  15. package/gateway.py +939 -755
  16. package/launcher.py +120 -0
  17. package/mcp_server.py +266 -266
  18. package/metrics/alerts.py +116 -116
  19. package/metrics/aliases.py +41 -41
  20. package/metrics/bench_collections.py +141 -141
  21. package/metrics/catalog.py +304 -295
  22. package/metrics/client.py +804 -394
  23. package/metrics/cliproxy.py +191 -191
  24. package/metrics/connect.py +691 -691
  25. package/metrics/cost.py +171 -152
  26. package/metrics/decide.py +227 -227
  27. package/metrics/discover.py +318 -318
  28. package/metrics/endpoints.py +436 -436
  29. package/metrics/formats.py +1021 -0
  30. package/metrics/guard.py +647 -592
  31. package/metrics/learn.py +234 -221
  32. package/metrics/ledger.py +148 -0
  33. package/metrics/litellm_hub.py +206 -206
  34. package/metrics/model_roles.py +94 -84
  35. package/metrics/offload.py +166 -166
  36. package/metrics/owui.py +205 -205
  37. package/metrics/pacing.py +248 -0
  38. package/metrics/paths.py +60 -60
  39. package/metrics/quota.py +383 -0
  40. package/metrics/route.py +581 -579
  41. package/metrics/scheduler.py +275 -275
  42. package/metrics/sources.py +167 -167
  43. package/metrics/wrong.py +144 -144
  44. package/package.json +69 -65
  45. package/perf/README.md +89 -89
  46. package/perf/run.py +284 -261
  47. package/scripts/auto_retire.py +367 -367
  48. package/scripts/check.py +113 -106
  49. package/scripts/check_deprecations.py +378 -378
  50. package/scripts/check_themes.py +103 -103
  51. package/scripts/init.py +124 -124
  52. package/scripts/list_served.py +78 -78
  53. package/scripts/notice.py +78 -78
  54. package/scripts/onboard.py +410 -331
  55. package/scripts/provider_tags.py +129 -129
  56. package/scripts/reprefix.py +136 -136
  57. package/scripts/sync_config.py +105 -105
  58. package/scripts/test_auth.py +255 -255
  59. package/scripts/test_cli.py +189 -0
  60. package/scripts/test_client.py +277 -233
  61. package/scripts/test_connect.py +222 -222
  62. package/scripts/test_first_run.py +180 -180
  63. package/scripts/test_formats.py +549 -0
  64. package/scripts/test_hubs.py +328 -328
  65. package/scripts/test_launcher.py +67 -0
  66. package/scripts/test_llm_log.py +85 -81
  67. package/scripts/test_mcp.py +178 -178
  68. package/scripts/test_merged.py +215 -214
  69. package/scripts/test_no_owui.py +291 -291
  70. package/scripts/test_owui.py +90 -90
  71. package/scripts/test_quota.py +334 -0
  72. package/scripts/test_serve_edit.py +453 -438
  73. package/scripts/test_server_json.py +79 -0
  74. package/scripts/test_verdict.py +102 -98
  75. package/scripts/test_vision.py +231 -228
  76. package/serve.py +3972 -3764
  77. package/suite/mine.py +154 -154
  78. package/suite/pace.py +145 -145
  79. package/suite/run.py +1046 -1042
  80. package/suite/seed.py +421 -421
  81. package/suite/tasks/claim-alias-unbound/fixtures/onboard.py +286 -286
  82. package/suite/tasks/claim-error-text-string/fixtures/decide.py +187 -187
  83. package/suite/tasks/claim-list-body/fixtures/gateway.py +235 -235
  84. package/suite/tasks/claim-member-nominated-twice/fixtures/gateway.py +284 -284
  85. package/suite/tasks/claim-nominee-uses-slot/fixtures/gateway.py +284 -284
  86. package/suite/tasks/claim-silent-exit/fixtures/gateway.py +235 -235
  87. package/suite/tasks/claim-sse-hang/fixtures/gateway.py +235 -235
  88. package/suite/tasks/claim-unknown-nominee-crash/fixtures/gateway.py +284 -284
  89. package/suite/tasks/rebench-matchms-809-bug/task.json +23 -23
  90. package/suite/tasks/rebench-matchms-809-control/task.json +27 -27
  91. package/suite/tasks/rebench-more-itertools-1028-bug/task.json +23 -23
  92. package/suite/tasks/rebench-more-itertools-1028-control/task.json +27 -27
  93. package/suite/tasks/rebench-narwhals-2864-bug/task.json +23 -23
  94. package/suite/tasks/rebench-narwhals-2864-control/task.json +27 -27
  95. package/suite/tasks/rebench-quantecon-py-769-bug/task.json +23 -23
  96. package/suite/tasks/rebench-quantecon-py-769-control/task.json +27 -27
  97. package/suite/tasks/rebench-scrapy-6867-bug/task.json +23 -23
  98. package/suite/tasks/rebench-scrapy-6867-control/task.json +27 -27
  99. package/ui/bench.css +430 -425
  100. package/ui/bench.js +1757 -1576
  101. package/ui/themes/dark.css +35 -35
  102. package/ui/themes/hardwood.css +52 -52
  103. package/ui/themes/rainforest.css +32 -32
package/README.md CHANGED
@@ -1,314 +1,408 @@
1
- <h1 align="center">
2
- <picture>
3
- <source media="(prefers-color-scheme: dark)" srcset=".github/logo-dark.svg">
4
- <img src=".github/logo.svg" width="480" alt="Benchwarmers, by Wendell Research: the name sitting on a crimson team bench">
5
- </picture>
6
- </h1>
7
-
8
- <p align="center">
9
- <a href="https://github.com/WendellResearch-AI/benchwarmers/releases"><img src="https://img.shields.io/github/v/release/WendellResearch-AI/benchwarmers?color=990000" alt="Latest release"></a>
10
- <a href="https://www.npmjs.com/package/@wendellresearch-ai/benchwarmers"><img src="https://img.shields.io/npm/v/@wendellresearch-ai/benchwarmers?color=cb3837&logo=npm" alt="npm"></a>
11
- <a href="https://github.com/WendellResearch-AI/benchwarmers/actions/workflows/check.yml"><img src="https://github.com/WendellResearch-AI/benchwarmers/actions/workflows/check.yml/badge.svg" alt="Checks"></a>
12
- <a href="#docker"><img src="https://img.shields.io/badge/Docker-compose-2496ED?logo=docker&logoColor=white" alt="Runs in Docker"></a>
13
- <a href="GATEWAY.md"><img src="https://img.shields.io/badge/OpenAI--compatible-gateway-111111" alt="OpenAI-compatible gateway"></a>
14
- <br>
15
- <a href="#try-it"><img src="https://img.shields.io/badge/Python-3.11_%7C_3.12_%7C_3.13-3776AB?logo=python&logoColor=white" alt="Python 3.11 to 3.13"></a>
16
- <a href="#try-it"><img src="https://img.shields.io/badge/core-stdlib_only-2ea44f" alt="The core needs only the Python standard library"></a>
17
- <a href="#try-it"><img src="https://img.shields.io/badge/runs_on-Windows_%7C_macOS_%7C_Linux-555555" alt="Windows, macOS and Linux"></a>
18
- <a href="LICENSE"><img src="https://img.shields.io/badge/license-MIT-blue" alt="MIT license"></a>
19
- </p>
20
-
21
- <p align="center"><strong><em>Put me in, coach.</em></strong><br>Every model on your bench gets its minutes.</p>
22
-
23
- Benchwarmers is a self-hosted **panel gateway** for LLMs. Your app asks for one
24
- of your **collections**, and the whole panel answers: the models you picked
25
- for that kind of work, plus **explorer** seats that rotate in the models you
26
- have used least. Every answer comes back side by side, labelled by model and
27
- seat. You check them against the real work and record a verdict, and those
28
- verdicts decide who sits on the next panel.
29
-
30
- **What it is best at:**
31
-
32
- - **Asking several models at once.** One OpenAI-compatible request, a panel of
33
- answers. Where they disagree is where to look.
34
- - **Reviewing.** Code review, claims about code, designs: different models
35
- catch different things, and two models agreeing is not proof.
36
- - **Playing to each model's strengths.** A collection per kind of work
37
- (reviewers, designers, enumerators), each with the models that do it best.
38
- - **Giving every model playing time.** Explorers cycle through the rest of
39
- the bench, so new and free models get tried beside the ones you trust,
40
- without you choosing them by hand.
41
- - **Benchmarking on your own projects.** A review suite seeded from your own
42
- repositories, plus your verdicts on real work, instead of public leaderboards.
43
- - **Knowing who to bench.** Who is confidently wrong, who has stopped
44
- answering (retired automatically), and who is resting after a rate limit.
45
- - **Keeping free and cheap models useful.** Health probes, learned rate limits
46
- and one endpoint for every app, over any OpenAI-compatible provider or hub.
47
-
48
- | Related tools | Examples | What they do | What Benchwarmers does differently |
49
- |---|---|---|---|
50
- | Gateways and routers | LiteLLM, OpenRouter, Portkey | One request to one model, with fallback | Fans one prompt out to a panel and returns every answer |
51
- | Councils and ensembles | LLM Council, Mixture-of-Agents | Several models merged into one answer | Keeps every answer, because who said what is the evidence; one merged summary only if you ask |
52
- | Side-by-side chat | Open WebUI, LMArena | Several answers shown together | Records a verdict per answer and uses them to pick the next panel |
53
- | Evaluation harnesses | promptfoo, Inspect, SWE-bench | Score models offline on fixed test sets | Scores them on your real work and your own code, and keeps rotating new models in |
54
-
55
- <p align="center">
56
- <picture>
57
- <source media="(prefers-color-scheme: dark)" srcset=".github/flow-dark.svg">
58
- <img src=".github/flow.svg" width="900" alt="Your app or agent asks the gateway for one of your collections. The gateway seats a panel: the collection's members, always seated, and an explorer that rotates in the model unused longest. Every answer comes back side by side, labelled by model and seat. You check them against the real work and record a verdict. Verdicts, daily health probes and the review suite make up the bench's record, which chooses who sits on the next panel.">
59
- </picture>
60
- </p>
61
-
62
- ## Contents
63
-
64
- - [Try it](#try-it)
65
- - [How it works](#how-it-works)
66
- - [Asking a panel](#asking-a-panel)
67
- - [From Claude Code and other agents](#from-claude-code-and-other-agents)
68
- - [The suite](#the-suite)
69
- - [Docker](#docker)
70
- - [What leaves your machine](#what-leaves-your-machine)
71
- - [Limits](#limits)
72
- - [Security](#security)
73
- - [Development](#development)
74
- - [Licence](#licence)
75
-
76
- Day-to-day use (the dashboard's pages, connecting apps, onboarding in bulk) is
77
- in [USAGE.md](USAGE.md). A coding agent setting it up for someone should start
78
- with [AGENTS.md](AGENTS.md).
79
-
80
- ## Try it
81
-
82
- **v0.1, early.** It runs with Python 3.11 or later on Windows, macOS and
83
- Linux, from npm, a checkout or Docker. Expect rough edges, and read the code
84
- where these pages are thin.
85
-
86
- **Quickest**, with Node 18+ and Python 3.11+ installed:
87
-
88
- npx @wendellresearch-ai/benchwarmers
89
-
90
- It keeps your data in your user profile (`BENCHWARMERS_HOME` to choose
91
- another folder), sets it up on the first run, and opens the dashboard on
92
- http://localhost:8122, where steps 3 to 5 below continue. The built-in model
93
- client makes every call, so nothing else needs installing.
94
-
95
- **From source:**
96
-
97
- 1. **Get the code** and a Python 3.11+ environment:
98
-
99
- git clone https://github.com/WendellResearch-AI/benchwarmers
100
- cd benchwarmers
101
- python -m venv .venv
102
- .venv/bin/pip install llm # Windows: .venv\Scripts\pip install llm
103
-
104
- The [`llm`](https://llm.datasette.io/) CLI is the default model client today.
105
- The built-in client (`metrics/client.py`) replaces it, chosen on Settings
106
- or with `BENCH_CLIENT=builtin` (which wins), and will become the default.
107
-
108
- 2. **Make a data folder.** Everything that is yours (endpoints, model
109
- registrations, collections, verdicts, probe history, task answers) lives
110
- there, never in the code:
111
-
112
- python scripts/init.py # or: python scripts/init.py <folder>
113
-
114
- It starts the folder from `examples/` (in your OS's per-user data folder
115
- unless you name one) and points the checkout at it with a
116
- `.benchwarmers-home` file; `BENCHWARMERS_HOME` overrides that. With
117
- `--endpoint NAME BASE_URL --key` it also adds a first endpoint and asks for
118
- its key, which goes to the `llm` CLI's key store (`llm keys set NAME` does
119
- the same), never into the data folder.
120
-
121
- 3. **Start the dashboard.** With no endpoint yet it opens on adding one: sign
122
- in with a provider on its own page (OpenRouter, NanoGPT, Hugging Face,
123
- Pollinations, your ChatGPT plan, GitHub Models, or a local Ollama), or paste
124
- an API key for any OpenAI-compatible API, a hub such as Open WebUI or
125
- LiteLLM included. Only providers that publish a sign-in for other apps are
126
- offered; adding one is an entry in `metrics/connect.py` (its docstring says
127
- how).
128
-
129
- python serve.py # http://localhost:8122
130
-
131
- 4. **Onboard a few models** from it ("Onboard a model"): tick one or several;
132
- each is registered, probed and run through the suite, one at a time, with
133
- the progress beside the button.
134
-
135
- 5. **Make a collection** on the Collections page (the models a panel always
136
- includes, and what it is for). The dashboard answers the gateway's routes on
137
- its own port (`http://127.0.0.1:8122/v1`); `python gateway.py` runs the
138
- gateway alone on `http://127.0.0.1:8123/v1`.
139
-
140
- ## How it works
141
-
142
- - **Collections and seats.** A collection names the models a panel always
143
- includes and the shape of work it is for (review, design, enumeration...).
144
- Each call adds explorer seats chosen by how long a model has gone unused, so
145
- the bench keeps learning about models a router would stop asking.
146
- - **Verdicts.** Each seat's answer carries a call id. Once you have checked an
147
- answer against the real work, record whether it was useful, and which of its
148
- claims were real or false. Verdicts, not vibes, decide the next panel; a
149
- model that is often confidently wrong shows as that.
150
- - **The catalog.** Context window, output limit, tools, vision and price come
151
- from models.dev for the provider that actually serves a model, then
152
- OpenRouter's public catalog and FreeLLMAPI, and anything you set by hand.
153
- - **Health.** Every morning each model gets a one-word prompt; once a week, a
154
- short and a long one for speed. A model that stops answering, or that its
155
- endpoint no longer serves, is retired automatically after one confirming
156
- call, with the evidence kept, and can be restored in one click. Rate limits
157
- are learned from providers' own error messages and kept per connection,
158
- since models behind one key share them. The built-in scheduler runs all of
159
- this once Settings switches it on; a fresh install spends no calls until then.
160
- - **Discover.** models.dev and FreeLLMAPI, merged into one list of models
161
- worth adding, each with the routes this install has to it.
162
-
163
- ## Asking a panel
164
-
165
- Any OpenAI-compatible client works: ask for the model `bench/<collection>`.
166
-
167
- curl http://127.0.0.1:8122/v1/chat/completions \
168
- -H "Content-Type: application/json" \
169
- -d '{"model": "bench/<collection>", "messages": [{"role": "user", "content": "Where can this function return a stale value? ..."}]}'
170
-
171
- The reply carries every seat's answer in the message, and the same seats as
172
- data in a `bench` field: model, seat, status, seconds and a call id. For a chat
173
- window that shows one answer, ask for `bench/<collection>:merged`: a merger
174
- model writes one answer saying where the seats agreed and disagreed, and every
175
- seat is still in `bench`. Record a verdict per call once you have checked it:
176
-
177
- curl http://127.0.0.1:8122/bench/verdict -H "Content-Type: application/json" \
178
- -d '{"call": "<call id>", "useful": true, "note": "found the real bug", "real": ["..."], "false": ["..."]}'
179
-
180
- or from the dashboard. See [GATEWAY.md](GATEWAY.md) for the full contract:
181
- images, prompt sizes, who may call, and the reply's shape.
182
-
183
- ## From Claude Code and other agents
184
-
185
- `benchwarmers mcp` is an MCP server on stdio with three tools:
186
- `list_collections`, `ask` (a collection, a prompt, optionally `merged`,
187
- `with` and `system`) and `verdict`. It is a client of a Benchwarmers that is
188
- already running, over the routes above, so start the dashboard (or the
189
- service) first. The tool descriptions carry the rules: ask a collection, not
190
- a favourite model; verify before acting; name every seat; send a verdict per
191
- seat you judged.
192
-
193
- **Claude Code plugin.** The plugin adds the MCP server, a `/benchwarmers:ask
194
- <collection> <what to ask>` command and a `benchwarmers` skill saying when a
195
- panel helps (adversarial review, exhaustive enumeration over real files) and
196
- how to judge it:
197
-
198
- /plugin marketplace add WendellResearch-AI/benchwarmers
199
- /plugin install benchwarmers@benchwarmers
200
-
201
- It needs Node 18 or later and Python 3.11 or later on PATH.
202
-
203
- **Any other MCP client** (or Claude Code without the plugin): run
204
- `npx -y @wendellresearch-ai/benchwarmers mcp`, or `python mcp_server.py` from a
205
- checkout. For example:
206
-
207
- ```json
208
- {"mcpServers": {"benchwarmers": {"command": "npx", "args": ["-y", "@wendellresearch-ai/benchwarmers", "mcp"],
209
- "env": {"BENCHWARMERS_URL": "http://127.0.0.1:8122"}}}}
210
- ```
211
-
212
- On native Windows, a client that cannot start `npx` itself needs
213
- `"command": "cmd", "args": ["/c", "npx", "-y", "@wendellresearch-ai/benchwarmers", "mcp"]`.
214
-
215
- | Variable | Meaning |
216
- | --- | --- |
217
- | `BENCHWARMERS_URL` | the server, default `http://127.0.0.1:8122` (a `/v1` on the end is fine) |
218
- | `BENCHWARMERS_KEY` | a gateway key from Settings, sent as `Authorization: Bearer`; needed for a server on another machine, or where keys are required |
219
- | `BENCHWARMERS_TIMEOUT` | seconds to wait for a panel, default 900 |
220
-
221
- ## The suite
222
-
223
- Models are scored on review work: find the faulty line, or say whether a
224
- claim about some code is true. The repository ships a starter pack:
225
-
226
- - **Real bugs** (`suite/tasks/rebench-*`): recent fixes from public projects,
227
- drawn from the SWE-rebench-V2 dataset, each paired with a control.
228
- - **Claims** (`suite/tasks/claim-*`): true and false statements about this
229
- project's own code, with the code attached.
230
-
231
- Add your own from your repositories (`config/repos.json` in the data folder
232
- maps a name to a folder): `python suite/seed.py --repo <name>` plants
233
- realistic bugs in your real functions, each with a clean control. Private
234
- code makes the best tasks, because no model has trained on it. See
235
- [suite/README.md](suite/README.md), [suite/REBENCH.md](suite/REBENCH.md) and
236
- [BENCHMARKS.md](BENCHMARKS.md).
237
-
238
- ## Docker
239
-
240
- One container runs the dashboard, the gateway (on the same port, at `/v1`)
241
- and the scheduler, with the data folder as a volume and no `llm` CLI (the
242
- built-in client makes every call). Put the secrets in a `.env` file beside
243
- `compose.yaml`:
244
-
245
- BENCHWARMERS_ADMIN_TOKEN=<16 or more characters>
246
- BENCHWARMERS_KEY_OPENROUTER=sk-or-...
247
-
248
- then `docker compose up -d` and open http://127.0.0.1:8122. The compose file
249
- publishes on this machine only; see its comments for a proxy.
250
-
251
- ## What leaves your machine
252
-
253
- Benchwarmers has no account, no telemetry and no server of its own; nothing
254
- reaches Wendell Research. What it sends, and where:
255
-
256
- - **Your prompts**, only to the endpoints you add: a provider, an aggregator,
257
- a hub such as Open WebUI, or a local runtime. That includes the probes, the
258
- suite and onboarding, which spend calls on your keys. Add only endpoints you
259
- are content to send that work to.
260
- - **Catalog reads**, no key and nothing of yours: models.dev and FreeLLMAPI
261
- once a day, and OpenRouter's public model list.
262
- - **Sign-ins**, only when you choose one: the provider's own sign-in page.
263
- - **Charts**: the dashboard's browser page loads Observable Plot from jsDelivr
264
- at a pinned version.
265
- - **Seeding real-bug tasks**, only when you run `suite/rebench.py`: the
266
- SWE-rebench-V2 rows from Hugging Face and the files from GitHub.
267
-
268
- Keys stay in the environment or the `llm` CLI's key store, never in the data
269
- folder. [PRIVACY.md](PRIVACY.md) and [SECURITY.md](SECURITY.md) have the
270
- detail.
271
-
272
- ## Limits
273
-
274
- - **Early.** v0.1: no installer yet, no streaming replies, and the LiteLLM and
275
- CLI Proxy API hub adapters are built from those projects' source, not yet
276
- run against a real hub.
277
- - **Calls cost.** Probes, onboarding and the suite run on your keys and free
278
- tiers. Onboarding runs one model at a time for that reason; a suite pass is
279
- paced, not burst.
280
- - **One install, one person** at heart. Logins and gateway keys exist for a
281
- LAN or a server, but there are no per-user views or roles.
282
- - **The onboarding queue lives in the dashboard's memory.** Restarting it
283
- drops the rest of a batch; each started model's log stays in `runs/`.
284
- - **Free tiers change without notice.** Discover's prices come from public
285
- catalogs, and some providers' free models work only in their own app
286
- (switched per provider on Settings).
287
-
288
- ## Security
289
-
290
- Both servers listen on 127.0.0.1 by default and accept only same-origin writes
291
- for their own host name, so a web page in your browser cannot drive them. To
292
- listen anywhere else, set `BENCHWARMERS_ADMIN_TOKEN` first; without it they
293
- refuse to start. With it, the dashboard asks for a login and the gateway takes
294
- named, revocable **gateway keys**. Settings, under Server and access, shows how
295
- this stands (whether a secret is set, never its value).
296
-
297
- [SECURITY.md](SECURITY.md) has the whole model and how to report a
298
- vulnerability.
299
-
300
- ## Development
301
-
302
- python scripts/check.py # every self-check and test (--quick skips the slow one)
303
-
304
- The core is the Python standard library only. CI runs the checks on Linux and
305
- Windows for Python 3.11 to 3.13, and on macOS.
306
-
307
- Benchwarmers isn't taking outside contributions yet; that opens once it is
308
- more established. A security problem goes through [SECURITY.md](SECURITY.md).
309
-
310
- ## Licence
311
-
312
- MIT, Copyright (c) 2026 Wendell Research ([LICENSE](LICENSE)). The starter
313
- tasks and bundled fonts carry their own licences, listed in [NOTICE](NOTICE).
314
- Use is also covered by [TERMS.md](TERMS.md).
1
+ <h1 align="center">
2
+ <picture>
3
+ <source media="(prefers-color-scheme: dark)" srcset=".github/logo-dark.svg">
4
+ <img src=".github/logo.svg" width="480" alt="Benchwarmers, by Wendell Research: the name sitting on a crimson team bench">
5
+ </picture>
6
+ </h1>
7
+
8
+ <p align="center">
9
+ <a href="https://github.com/WendellResearch-AI/benchwarmers/releases"><img src="https://img.shields.io/github/v/release/WendellResearch-AI/benchwarmers?color=990000" alt="Latest release"></a>
10
+ <a href="https://www.npmjs.com/package/@wendellresearch-ai/benchwarmers"><img src="https://img.shields.io/npm/v/@wendellresearch-ai/benchwarmers?color=cb3837&logo=npm" alt="npm"></a>
11
+ <a href="https://pypi.org/project/benchwarmers/"><img src="https://img.shields.io/pypi/v/benchwarmers?color=3775a9&logo=pypi&logoColor=white" alt="PyPI"></a>
12
+ <a href="https://github.com/WendellResearch-AI/benchwarmers/actions/workflows/check.yml"><img src="https://github.com/WendellResearch-AI/benchwarmers/actions/workflows/check.yml/badge.svg" alt="Checks"></a>
13
+ <a href="#docker"><img src="https://img.shields.io/badge/Docker-compose-2496ED?logo=docker&logoColor=white" alt="Runs in Docker"></a>
14
+ <a href="GATEWAY.md"><img src="https://img.shields.io/badge/OpenAI--compatible-gateway-111111" alt="OpenAI-compatible gateway"></a>
15
+ <br>
16
+ <a href="#try-it"><img src="https://img.shields.io/badge/Python-3.11_%7C_3.12_%7C_3.13-3776AB?logo=python&logoColor=white" alt="Python 3.11 to 3.13"></a>
17
+ <a href="#try-it"><img src="https://img.shields.io/badge/core-stdlib_only-2ea44f" alt="The core needs only the Python standard library"></a>
18
+ <a href="#try-it"><img src="https://img.shields.io/badge/runs_on-Windows_%7C_macOS_%7C_Linux-555555" alt="Windows, macOS and Linux"></a>
19
+ <a href="LICENSE"><img src="https://img.shields.io/badge/license-MIT-blue" alt="MIT license"></a>
20
+ </p>
21
+
22
+ <p align="center"><strong><em>Put me in, coach.</em></strong><br>Every model on your bench gets its minutes.</p>
23
+
24
+ Benchwarmers is a self-hosted **panel gateway** for LLMs. Your app asks for one
25
+ of your **collections**, and the whole panel answers: the models you picked
26
+ for that kind of work, plus **explorer** seats that rotate in the models you
27
+ have used least. Every answer comes back side by side, labelled by model and
28
+ seat. You check them against the real work and record a verdict, and those
29
+ verdicts decide who sits on the next panel.
30
+
31
+ **What it is best at:**
32
+
33
+ - **Asking several models at once.** One OpenAI-compatible request, a panel of
34
+ answers. Where they disagree is where to look.
35
+ - **Reviewing.** Code review, claims about code, designs: different models
36
+ catch different things, and two models agreeing is not proof.
37
+ - **Playing to each model's strengths.** A collection per kind of work
38
+ (reviewers, designers, enumerators), each with the models that do it best.
39
+ - **Giving every model playing time.** Explorers cycle through the rest of
40
+ the bench, so new and free models get tried beside the ones you trust,
41
+ without you choosing them by hand.
42
+ - **Benchmarking on your own projects.** A review suite seeded from your own
43
+ repositories, plus your verdicts on real work, instead of public leaderboards.
44
+ - **Knowing who to bench.** Who is confidently wrong, who has stopped
45
+ answering (retired automatically), and who is resting after a rate limit.
46
+ - **Keeping free and cheap models useful.** Health probes, learned rate limits
47
+ and one endpoint for every app, over any OpenAI-compatible provider or hub.
48
+
49
+ | Related tools | Examples | What they do | What Benchwarmers does differently |
50
+ |---|---|---|---|
51
+ | Gateways and routers | LiteLLM, OpenRouter, Portkey | One request to one model, with fallback | Fans one prompt out to a panel and returns every answer |
52
+ | Councils and ensembles | LLM Council, Mixture-of-Agents | Several models merged into one answer | Keeps every answer, because who said what is the evidence; one merged summary only if you ask |
53
+ | Side-by-side chat | Open WebUI, LMArena | Several answers shown together | Records a verdict per answer and uses them to pick the next panel |
54
+ | Evaluation harnesses | promptfoo, Inspect, SWE-bench | Score models offline on fixed test sets | Scores them on your real work and your own code, and keeps rotating new models in |
55
+
56
+ <p align="center">
57
+ <picture>
58
+ <source media="(prefers-color-scheme: dark)" srcset=".github/flow-dark.svg">
59
+ <img src=".github/flow.svg" width="900" alt="Your app or agent asks the gateway for one of your collections. The gateway seats a panel: the collection's members, always seated, and an explorer that rotates in the model unused longest. Every answer comes back side by side, labelled by model and seat. You check them against the real work and record a verdict. Verdicts, daily health probes and the review suite make up the bench's record, which chooses who sits on the next panel.">
60
+ </picture>
61
+ </p>
62
+
63
+ ## Contents
64
+
65
+ - [Try it](#try-it)
66
+ - [How it works](#how-it-works)
67
+ - [Asking a panel](#asking-a-panel)
68
+ - [From the command line](#from-the-command-line)
69
+ - [From Claude Code and other agents](#from-claude-code-and-other-agents)
70
+ - [The suite](#the-suite)
71
+ - [Docker](#docker)
72
+ - [What leaves your machine](#what-leaves-your-machine)
73
+ - [Limits](#limits)
74
+ - [Security](#security)
75
+ - [Development](#development)
76
+ - [Licence](#licence)
77
+
78
+ Day-to-day use (the dashboard's pages, connecting apps, onboarding in bulk) is
79
+ in [USAGE.md](USAGE.md). Running it on a server or a hosting platform is in
80
+ [HOSTING.md](HOSTING.md). A coding agent setting it up for someone should start
81
+ with [AGENTS.md](AGENTS.md).
82
+
83
+ ## Try it
84
+
85
+ **v0.2, early.** It runs with Python 3.11 or later on Windows, macOS and
86
+ Linux, from PyPI, npm, a checkout or Docker. Expect rough edges, and read the
87
+ code where these pages are thin.
88
+
89
+ **Quickest**, with Python 3.11+ installed, from PyPI:
90
+
91
+ pipx install benchwarmers # or: uv tool install benchwarmers
92
+ benchwarmers # or try it first, without installing: uvx benchwarmers
93
+
94
+ or from npm, with Node 18+ as well:
95
+
96
+ npx @wendellresearch-ai/benchwarmers
97
+
98
+ Either way you get the same two commands, `benchwarmers` and its short name
99
+ `bench`. It keeps your data in your user profile (`BENCHWARMERS_HOME` to
100
+ choose another folder), sets it up on the first run, and opens the dashboard
101
+ on http://localhost:8122, where steps 3 to 5 below continue. The built-in
102
+ model client makes every call, so nothing else needs installing. No
103
+ dependencies either way: the app is the Python standard library.
104
+
105
+ **From source:**
106
+
107
+ 1. **Get the code** and a Python 3.11+ environment:
108
+
109
+ git clone https://github.com/WendellResearch-AI/benchwarmers
110
+ cd benchwarmers
111
+ python -m venv .venv
112
+ .venv/bin/pip install llm # Windows: .venv\Scripts\pip install llm
113
+
114
+ The [`llm`](https://llm.datasette.io/) CLI is the default model client today.
115
+ The built-in client (`metrics/client.py`) replaces it, chosen on Settings
116
+ or with `BENCH_CLIENT=builtin` (which wins), and will become the default.
117
+
118
+ 2. **Make a data folder.** Everything that is yours (endpoints, model
119
+ registrations, collections, verdicts, probe history, task answers) lives
120
+ there, never in the code:
121
+
122
+ python scripts/init.py # or: python scripts/init.py <folder>
123
+
124
+ It starts the folder from `examples/` (in your OS's per-user data folder
125
+ unless you name one) and points the checkout at it with a
126
+ `.benchwarmers-home` file; `BENCHWARMERS_HOME` overrides that. With
127
+ `--endpoint NAME BASE_URL --key` it also adds a first endpoint and asks for
128
+ its key, which goes to the `llm` CLI's key store (`llm keys set NAME` does
129
+ the same), never into the data folder.
130
+
131
+ 3. **Start the dashboard.** With no endpoint yet it opens on adding one: sign
132
+ in with a provider on its own page (OpenRouter, NanoGPT, Hugging Face,
133
+ Pollinations, your ChatGPT plan, GitHub Models, or a local Ollama), or paste
134
+ an API key for any OpenAI-compatible API, a hub such as Open WebUI or
135
+ LiteLLM included. Only providers that publish a sign-in for other apps are
136
+ offered; adding one is an entry in `metrics/connect.py` (its docstring says
137
+ how).
138
+
139
+ python serve.py # http://localhost:8122
140
+
141
+ 4. **Onboard a few models** from it ("Onboard a model"): tick one or several;
142
+ each is registered, probed and run through the suite, one at a time, with
143
+ the progress beside the button.
144
+
145
+ 5. **Make a collection** on the Collections page (the models a panel always
146
+ includes, and what it is for). The dashboard answers the gateway's routes on
147
+ its own port (`http://127.0.0.1:8122/v1`); `python gateway.py` runs the
148
+ gateway alone on `http://127.0.0.1:8123/v1`.
149
+
150
+ ## How it works
151
+
152
+ - **Collections and seats.** A collection names the models a panel always
153
+ includes and the shape of work it is for (review, design, enumeration...).
154
+ Each call adds explorer seats chosen by how long a model has gone unused, so
155
+ the bench keeps learning about models a router would stop asking.
156
+ - **Verdicts.** Each seat's answer carries a call id. Once you have checked an
157
+ answer against the real work, record whether it was useful, and which of its
158
+ claims were real or false. Verdicts, not vibes, decide the next panel; a
159
+ model that is often confidently wrong shows as that.
160
+ - **The catalog.** Context window, output limit, tools, vision and price come
161
+ from models.dev for the provider that actually serves a model, then
162
+ OpenRouter's public catalog and FreeLLMAPI, and anything you set by hand.
163
+ - **Health.** Every morning each model gets a one-word prompt; once a week, a
164
+ short and a long one for speed. A model that stops answering, or that its
165
+ endpoint no longer serves, is retired automatically after one confirming
166
+ call, with the evidence kept, and can be restored in one click. Rate limits
167
+ are learned from providers' own error messages and kept per connection,
168
+ since models behind one key share them. The built-in scheduler runs all of
169
+ this once Settings switches it on; a fresh install spends no calls until then.
170
+ - **Discover.** models.dev and FreeLLMAPI, merged into one list of models
171
+ worth adding, each with the routes this install has to it.
172
+
173
+ ## Asking a panel
174
+
175
+ Any OpenAI-compatible client works: ask for the model `bench/<collection>`.
176
+
177
+ curl http://127.0.0.1:8122/v1/chat/completions \
178
+ -H "Content-Type: application/json" \
179
+ -d '{"model": "bench/<collection>", "messages": [{"role": "user", "content": "Where can this function return a stale value? ..."}]}'
180
+
181
+ The reply carries every seat's answer in the message, and the same seats as
182
+ data in a `bench` field: model, seat, status, seconds and a call id. For a chat
183
+ window that shows one answer, ask for `bench/<collection>:merged`: a merger
184
+ model writes one answer saying where the seats agreed and disagreed, and every
185
+ seat is still in `bench`. Record a verdict per call once you have checked it:
186
+
187
+ curl http://127.0.0.1:8122/bench/verdict -H "Content-Type: application/json" \
188
+ -d '{"call": "<call id>", "useful": true, "note": "found the real bug", "real": ["..."], "false": ["..."]}'
189
+
190
+ or from the dashboard. See [GATEWAY.md](GATEWAY.md) for the full contract:
191
+ images, prompt sizes, who may call, and the reply's shape.
192
+
193
+ **Other clients.** The same panel answers in other APIs' shapes, so a client
194
+ that speaks one of them works with the server's address as its base URL:
195
+
196
+ | Format | Route | For |
197
+ |---|---|---|
198
+ | OpenAI chat completions | `POST /v1/chat/completions` | OpenAI SDKs, most chat apps |
199
+ | OpenAI Responses | `POST /v1/responses` | `responses.create` clients (nothing is stored) |
200
+ | OpenAI legacy completions | `POST /v1/completions` | tools that send a `prompt` |
201
+ | Anthropic Messages | `POST /v1/messages` | Anthropic SDKs with `base_url` |
202
+ | Ollama | `POST /api/chat`, `/api/generate`, `GET /api/tags` | Open WebUI's Ollama connection, Ollama apps |
203
+ | Gemini | `POST /v1beta/models/bench/<collection>:generateContent` | Google GenAI SDKs with `base_url` |
204
+
205
+ curl http://127.0.0.1:8122/v1/messages -H "Content-Type: application/json" \
206
+ -d '{"model": "bench/<collection>", "max_tokens": 1024, "messages": [{"role": "user", "content": "..."}]}'
207
+
208
+ Each streams when asked: the reply starts at once, and each seat's answer
209
+ arrives as that seat finishes. Agent and tool use is not supported yet: a
210
+ request carrying tools is refused with a 400 rather than answered without
211
+ them. GATEWAY.md, "Other API formats", has a curl for each and what is
212
+ refused or ignored.
213
+
214
+ ## From the command line
215
+
216
+ `bench` (or `benchwarmers`, the same program) does what the dashboard does,
217
+ without it: ask and judge, and run the bench itself. It is a client of a
218
+ running server, so the same commands work against one on another machine.
219
+
220
+ bench status what is running, and where
221
+ bench collections what you can ask
222
+ bench ask core "Where can this return a stale value?" -f cache.py
223
+ git diff | bench ask core "Review this diff" a pipe is the prompt
224
+ bench verdict <call id> --useful --real "the race in refresh()" --false "the SQL injection"
225
+
226
+ bench models --find mercury the roster, with each model's status
227
+ bench candidates --new what your endpoints serve that is not on the bench
228
+ bench onboard <served name> --wait onboard it (registered, probed, scored)
229
+ bench model retire <alias> --reason "..." and back: bench model restore <alias>
230
+ bench collection save reviewers --members a,b,c --description "adversarial review"
231
+ bench collection save reviewers --add d --remove a
232
+ bench endpoint add openrouter --base https://openrouter.ai/api/v1 (asks for the key, hidden)
233
+ bench key create my-app --collections core --rpm 30
234
+ bench settings scheduler run --off perf
235
+
236
+ `--help` on any command lists its options with examples, and `--json` prints
237
+ what the server answered, for scripts. `--url` points one command at another
238
+ server. `ask` prints every seat's answer on stdout and the table of call ids to
239
+ judge them by on stderr, so `bench ask core "..." > review.md` keeps only the
240
+ answers. Retiring a model, deleting a collection or an endpoint and revoking a
241
+ key ask first; `--yes` answers for a script. A key is never an argument (others
242
+ on the machine can read those): `endpoint add` prompts for it, or reads it
243
+ from stdin with `--key-stdin`.
244
+
245
+ Another program called `bench` (the Frappe framework's) may come first on your
246
+ PATH; type `benchwarmers` instead, which takes the same commands.
247
+
248
+ | Variable | Meaning |
249
+ | --- | --- |
250
+ | `BENCHWARMERS_URL` | the server, default `http://127.0.0.1:8122` |
251
+ | `BENCHWARMERS_ADMIN_TOKEN` | the server's admin token, where it has one; sent as `Authorization: Bearer` for every command but `ask`, `collections` and `verdict` |
252
+ | `BENCHWARMERS_KEY` | a gateway key, for `ask`, `collections` and `verdict`, where keys are required |
253
+ | `BENCHWARMERS_TIMEOUT` | seconds to wait for a panel, default 900 |
254
+
255
+ Exit codes: 0 done, 1 refused or failed (the reason on stderr), 2 bad usage, 3
256
+ no server at `BENCHWARMERS_URL`.
257
+
258
+ ## From Claude Code and other agents
259
+
260
+ `benchwarmers mcp` is an MCP server on stdio with three tools:
261
+ `list_collections`, `ask` (a collection, a prompt, optionally `merged`,
262
+ `with` and `system`) and `verdict`. It is a client of a Benchwarmers that is
263
+ already running, over the routes above, so start the dashboard (or the
264
+ service) first. The tool descriptions carry the rules: ask a collection, not
265
+ a favourite model; verify before acting; name every seat; send a verdict per
266
+ seat you judged.
267
+
268
+ **Claude Code plugin.** The plugin adds the MCP server, a `/benchwarmers:ask
269
+ <collection> <what to ask>` command and a `benchwarmers` skill saying when a
270
+ panel helps (adversarial review, exhaustive enumeration over real files) and
271
+ how to judge it:
272
+
273
+ /plugin marketplace add WendellResearch-AI/benchwarmers
274
+ /plugin install benchwarmers@benchwarmers
275
+
276
+ It needs Node 18 or later and Python 3.11 or later on PATH.
277
+
278
+ **Any other MCP client** (or Claude Code without the plugin): run
279
+ `benchwarmers mcp` once installed, `uvx benchwarmers mcp` or
280
+ `npx -y @wendellresearch-ai/benchwarmers mcp` without installing, or `python mcp_server.py` from a
281
+ checkout. For example:
282
+
283
+ ```json
284
+ {"mcpServers": {"benchwarmers": {"command": "npx", "args": ["-y", "@wendellresearch-ai/benchwarmers", "mcp"],
285
+ "env": {"BENCHWARMERS_URL": "http://127.0.0.1:8122"}}}}
286
+ ```
287
+
288
+ On native Windows, a client that cannot start `npx` itself needs
289
+ `"command": "cmd", "args": ["/c", "npx", "-y", "@wendellresearch-ai/benchwarmers", "mcp"]`.
290
+
291
+ | Variable | Meaning |
292
+ | --- | --- |
293
+ | `BENCHWARMERS_URL` | the server, default `http://127.0.0.1:8122` (a `/v1` on the end is fine) |
294
+ | `BENCHWARMERS_KEY` | a gateway key from Settings, sent as `Authorization: Bearer`; needed for a server on another machine, or where keys are required |
295
+ | `BENCHWARMERS_TIMEOUT` | seconds to wait for a panel, default 900 |
296
+
297
+ <!-- The MCP Registry's proof that the PyPI package is this server (server.json):
298
+ mcp-name: io.github.WendellResearch-AI/benchwarmers -->
299
+
300
+ ## The suite
301
+
302
+ Models are scored on review work: find the faulty line, or say whether a
303
+ claim about some code is true. The repository ships a starter pack:
304
+
305
+ - **Real bugs** (`suite/tasks/rebench-*`): recent fixes from public projects,
306
+ drawn from the SWE-rebench-V2 dataset, each paired with a control.
307
+ - **Claims** (`suite/tasks/claim-*`): true and false statements about this
308
+ project's own code, with the code attached.
309
+
310
+ Add your own from your repositories (`config/repos.json` in the data folder
311
+ maps a name to a folder): `python suite/seed.py --repo <name>` plants
312
+ realistic bugs in your real functions, each with a clean control. Private
313
+ code makes the best tasks, because no model has trained on it. See
314
+ [suite/README.md](suite/README.md), [suite/REBENCH.md](suite/REBENCH.md) and
315
+ [BENCHMARKS.md](BENCHMARKS.md).
316
+
317
+ ## Docker
318
+
319
+ One container runs the dashboard, the gateway (on the same port, at `/v1`)
320
+ and the scheduler, with the data folder as a volume and no `llm` CLI (the
321
+ built-in client makes every call). Put the secrets in a `.env` file beside
322
+ `compose.yaml`:
323
+
324
+ BENCHWARMERS_ADMIN_TOKEN=<16 or more characters>
325
+ BENCHWARMERS_KEY_OPENROUTER=sk-or-...
326
+
327
+ then `docker compose up -d` and open http://127.0.0.1:8122. The compose file
328
+ publishes on this machine only; see its comments for a proxy.
329
+
330
+ **Hosted, always on:** [HOSTING.md](HOSTING.md) compares Fly.io, Railway,
331
+ Render and a VPS with Caddy (cost, sleep, disks, HTTPS), with a template for
332
+ each under [deploy/](deploy/).
333
+
334
+ [![Deploy to Render](https://render.com/images/deploy-to-render-button.svg)](https://render.com/deploy?repo=https://github.com/WendellResearch-AI/benchwarmers)
335
+
336
+ Render is one click: a paid instance with a 1 GB disk and a generated admin
337
+ token. Fly.io is four commands from a checkout (`cp deploy/fly.toml .`,
338
+ `fly launch --copy-config --no-deploy`, `fly secrets set
339
+ BENCHWARMERS_ADMIN_TOKEN=...`, `fly deploy`). Railway is a few steps in its CLI
340
+ or browser, in [HOSTING.md](HOSTING.md#railway).
341
+
342
+ ## What leaves your machine
343
+
344
+ Benchwarmers has no account, no telemetry and no server of its own; nothing
345
+ reaches Wendell Research. What it sends, and where:
346
+
347
+ - **Your prompts**, only to the endpoints you add: a provider, an aggregator,
348
+ a hub such as Open WebUI, or a local runtime. That includes the probes, the
349
+ suite and onboarding, which spend calls on your keys. Add only endpoints you
350
+ are content to send that work to.
351
+ - **Catalog reads**, no key and nothing of yours: models.dev and FreeLLMAPI
352
+ once a day, and OpenRouter's public model list.
353
+ - **Sign-ins**, only when you choose one: the provider's own sign-in page.
354
+ - **Charts**: the dashboard's browser page loads Observable Plot from jsDelivr
355
+ at a pinned version.
356
+ - **Seeding real-bug tasks**, only when you run `suite/rebench.py`: the
357
+ SWE-rebench-V2 rows from Hugging Face and the files from GitHub.
358
+
359
+ Keys stay in the environment or the `llm` CLI's key store, never in the data
360
+ folder. [PRIVACY.md](PRIVACY.md) and [SECURITY.md](SECURITY.md) have the
361
+ detail.
362
+
363
+ ## Limits
364
+
365
+ - **No tools yet.** A panel answers with text, not a tool call, so agents
366
+ that need tool use (Claude Code, Codex, opencode as callers) wait for a
367
+ route mode; requests with tools are refused, not trimmed.
368
+ - **Early.** v0.1: no installer yet, merged replies stream in one piece, and the LiteLLM and
369
+ CLI Proxy API hub adapters are built from those projects' source, not yet
370
+ run against a real hub.
371
+ - **Calls cost.** Probes, onboarding and the suite run on your keys and free
372
+ tiers. Onboarding runs one model at a time for that reason; a suite pass is
373
+ paced, not burst.
374
+ - **One install, one person** at heart. Logins and gateway keys exist for a
375
+ LAN or a server, but there are no per-user views or roles.
376
+ - **The onboarding queue lives in the dashboard's memory.** Restarting it
377
+ drops the rest of a batch; each started model's log stays in `runs/`.
378
+ - **Free tiers change without notice.** Discover's prices come from public
379
+ catalogs, and some providers' free models work only in their own app
380
+ (switched per provider on Settings).
381
+
382
+ ## Security
383
+
384
+ Both servers listen on 127.0.0.1 by default and accept only same-origin writes
385
+ for their own host name, so a web page in your browser cannot drive them. To
386
+ listen anywhere else, set `BENCHWARMERS_ADMIN_TOKEN` first; without it they
387
+ refuse to start. With it, the dashboard asks for a login and the gateway takes
388
+ named, revocable **gateway keys**. Settings, under Server and access, shows how
389
+ this stands (whether a secret is set, never its value).
390
+
391
+ [SECURITY.md](SECURITY.md) has the whole model and how to report a
392
+ vulnerability.
393
+
394
+ ## Development
395
+
396
+ python scripts/check.py # every self-check and test (--quick skips the slow one)
397
+
398
+ The core is the Python standard library only. CI runs the checks on Linux and
399
+ Windows for Python 3.11 to 3.13, and on macOS.
400
+
401
+ Benchwarmers isn't taking outside contributions yet; that opens once it is
402
+ more established. A security problem goes through [SECURITY.md](SECURITY.md).
403
+
404
+ ## Licence
405
+
406
+ MIT, Copyright (c) 2026 Wendell Research ([LICENSE](LICENSE)). The starter
407
+ tasks and bundled fonts carry their own licences, listed in [NOTICE](NOTICE).
408
+ Use is also covered by [TERMS.md](TERMS.md).