@wendellresearch-ai/benchwarmers 0.2.2 → 0.3.1
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.
- package/AGENTS.md +11 -8
- package/GATEWAY.md +169 -12
- package/PRIVACY.md +7 -5
- package/README.md +81 -37
- package/SECURITY.md +5 -4
- package/USAGE.md +37 -5
- package/VERSION +1 -1
- package/cli.py +1 -1
- package/gateway.py +352 -10
- package/metrics/bench_collections.py +7 -3
- package/metrics/client.py +312 -88
- package/metrics/endpoints.py +25 -2
- package/metrics/formats.py +658 -95
- package/package.json +1 -1
- package/scripts/check.py +2 -0
- package/scripts/test_auth.py +2 -1
- package/scripts/test_formats.py +21 -12
- package/scripts/test_merged.py +2 -2
- package/scripts/test_native.py +188 -0
- package/scripts/test_route.py +429 -0
- package/scripts/test_serve_edit.py +2 -2
- package/serve.py +66 -8
- package/ui/bench.js +3 -1
package/AGENTS.md
CHANGED
|
@@ -25,12 +25,11 @@ Python 3.11 or later; nothing else is required.
|
|
|
25
25
|
git clone https://github.com/WendellResearch-AI/benchwarmers
|
|
26
26
|
cd benchwarmers
|
|
27
27
|
python -m venv .venv
|
|
28
|
-
.venv/bin/pip install llm # optional; Windows: .venv\Scripts\pip install llm
|
|
29
28
|
|
|
30
|
-
The
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
`BENCH_CLIENT=
|
|
29
|
+
The built-in model client (`metrics/client.py`) makes every call and needs
|
|
30
|
+
nothing installed. The `llm` CLI is an optional alternative, for its plugins:
|
|
31
|
+
`pip install llm` in the venv, then choose it on Settings, Model client, or
|
|
32
|
+
with `BENCH_CLIENT=llm` (which wins over Settings).
|
|
34
33
|
|
|
35
34
|
Check: `python scripts/check.py --quick` ends with `all checks pass`.
|
|
36
35
|
|
|
@@ -67,8 +66,9 @@ API) or a local runtime (Ollama). Three ways, best first:
|
|
|
67
66
|
| `openrouter-2` | `BENCHWARMERS_KEY_OPENROUTER_2` |
|
|
68
67
|
| `my-hub` | `BENCHWARMERS_KEY_MY_HUB` |
|
|
69
68
|
|
|
70
|
-
Without the variable, keys come from
|
|
71
|
-
|
|
69
|
+
Without the variable, keys come from llm's key file (`keys.json` in llm's
|
|
70
|
+
user folder, which the dashboard and `init --key` write; the `llm` CLI need
|
|
71
|
+
not be installed). The variable wins when both exist.
|
|
72
72
|
|
|
73
73
|
One provider can be several endpoints (a pasted key and a sign-in, or two
|
|
74
74
|
accounts); each needs its own name, and Benchwarmers suggests a free one.
|
|
@@ -107,7 +107,10 @@ Any OpenAI-compatible client asks for the model `bench/<collection>`:
|
|
|
107
107
|
Every seat's answer comes back, plus a `bench` field with each seat's call id.
|
|
108
108
|
Clients of the Anthropic, Ollama, Gemini and Responses APIs work too, with
|
|
109
109
|
the server's address as their base URL (GATEWAY.md, "Other API formats");
|
|
110
|
-
|
|
110
|
+
a panel answers with text, so requests carrying tools are refused there.
|
|
111
|
+
An agent that needs tools (Claude Code, Codex, opencode) runs on a collection
|
|
112
|
+
in route mode instead, `bench/<collection>:route`: one member per turn, with
|
|
113
|
+
its tools, kept for the conversation (GATEWAY.md, "Route mode").
|
|
111
114
|
For a chat window that shows one answer, ask for `bench/<collection>:merged`
|
|
112
115
|
(or set the collection's reply to merged): a merger model writes one answer
|
|
113
116
|
saying where the seats agreed and disagreed, and every seat is still in
|
package/GATEWAY.md
CHANGED
|
@@ -205,7 +205,7 @@ request becomes OpenAI-style messages, the panel runs exactly as on
|
|
|
205
205
|
scope, rate caps, CORS, the call log), and the reply goes back in the
|
|
206
206
|
format's own shape. The model field is the collection: `bench/core`, or
|
|
207
207
|
`bench/core:merged` for one merged answer. Clients that pick from a list
|
|
208
|
-
(`/api/tags`, `/v1beta/models`, `/v1/models`) see every collection in
|
|
208
|
+
(`/api/tags`, `/v1beta/models`, `/v1/models`) see every collection in all three
|
|
209
209
|
reply modes.
|
|
210
210
|
|
|
211
211
|
| Format | Routes | Streams with | `bench` field | Known clients |
|
|
@@ -269,12 +269,15 @@ bytes), Gemini `inlineData` and `fileData` at an http(s) URL.
|
|
|
269
269
|
**Refused, not dropped** (400, with why), because a client waiting for what
|
|
270
270
|
it asked would hang or loop on a panel's text:
|
|
271
271
|
|
|
272
|
-
- **Tools
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
272
|
+
- **Tools, in a panel.** A request carrying tools, or a conversation
|
|
273
|
+
holding tool calls or tool results, in any format, is refused by a panel
|
|
274
|
+
(`all` or `merged`) with a message pointing at route mode (next section),
|
|
275
|
+
which takes them. An empty `tools` list is no tools.
|
|
276
|
+
- a tool only its own vendor runs (Anthropic's server tools such as
|
|
277
|
+
`web_search_20250305` and their result blocks, OpenAI's hosted tools such
|
|
278
|
+
as `web_search`, Gemini's Google Search, URL context and code execution),
|
|
279
|
+
in route mode too: no other model can run them;
|
|
280
|
+
- the legacy `functions` / `function_call` fields: send `tools`;
|
|
278
281
|
- a PDF, audio or other non-image part; an image as an uploaded file id
|
|
279
282
|
(Anthropic `file`, Responses `file_id`, Gemini's Files API);
|
|
280
283
|
- `previous_response_id` or a stored `conversation` on Responses: nothing is
|
|
@@ -282,12 +285,166 @@ it asked would hang or loop on a panel's text:
|
|
|
282
285
|
- Ollama's `context` (a model's own token state): use `/api/chat`;
|
|
283
286
|
- more than one prompt on legacy completions.
|
|
284
287
|
|
|
285
|
-
**Ignored**: sampling options (`max_tokens`, `temperature`,
|
|
286
|
-
and their native names) are read but each seat keeps its own
|
|
287
|
-
structured-output settings (Ollama `format`, Gemini
|
|
288
|
-
Responses `text.format`), since a panel answers in
|
|
288
|
+
**Ignored by a panel**: sampling options (`max_tokens`, `temperature`,
|
|
289
|
+
`top_p`, `stop` and their native names) are read but each seat keeps its own
|
|
290
|
+
call options; structured-output settings (Ollama `format`, Gemini
|
|
291
|
+
`responseSchema`, Responses `text.format`), since a panel answers in
|
|
292
|
+
markdown. Route mode passes both on. **Ignored everywhere**: a model's own
|
|
289
293
|
reasoning from an earlier turn (Anthropic thinking blocks, Responses
|
|
290
|
-
reasoning items, Gemini thought parts)
|
|
294
|
+
reasoning items, Gemini thought parts), and native parameters with no
|
|
295
|
+
OpenAI counterpart (Anthropic's `top_k`, `thinking`, `metadata`).
|
|
296
|
+
|
|
297
|
+
## Route mode (agent clients)
|
|
298
|
+
|
|
299
|
+
A panel answers with several models' text; an agent needs one model that
|
|
300
|
+
calls its tools. **Route mode** is that: no panel, one member of the
|
|
301
|
+
collection gets the request as sent — messages, tool calls and results,
|
|
302
|
+
`tools`, `tool_choice`, `parallel_tool_calls`, `response_format`,
|
|
303
|
+
temperature and the rest, images — and its answer is relayed in the
|
|
304
|
+
request's own format, streamed or not. Ask for it with `:route` on the
|
|
305
|
+
model (`bench/core:route`), `"bench": {"reply": "route"}`, or set a
|
|
306
|
+
collection's reply to *route* in the Collections dialog. `GET /v1/models`
|
|
307
|
+
(and `/api/tags`, `/v1beta/models`) lists every collection in all three
|
|
308
|
+
modes, so an agent's model picker can choose `bench/core:route`.
|
|
309
|
+
|
|
310
|
+
**Who answers.** Members only, no explorers: an agent's working session is
|
|
311
|
+
not the place to try an unknown model. Excluded, and named in
|
|
312
|
+
`bench.skipped` with why, as a panel excludes them: retired and ad hoc
|
|
313
|
+
members, members whose endpoint speaks another API than chat completions,
|
|
314
|
+
members the catalog does not know to call tools (when the request has
|
|
315
|
+
tools; the model popup's *Tool calling* is the override, like *Vision /
|
|
316
|
+
OCR*; the catalog's `tools` comes from OpenRouter's listed parameters,
|
|
317
|
+
models.dev, the endpoint's own listing or a capability probe that saw a
|
|
318
|
+
call), that cannot see (when it has images) or whose context window is
|
|
319
|
+
under the request (tools and tool calls counted). No member left: 400
|
|
320
|
+
(413 when size is why). Of those left, the best record goes first: useful
|
|
321
|
+
verdicts counted, then verified suite findings, then a connection already
|
|
322
|
+
paid for; members cooling down after a rate limit go last. One new
|
|
323
|
+
conversation in four goes instead to the member whose record is oldest, so
|
|
324
|
+
the record does not narrow to one model.
|
|
325
|
+
|
|
326
|
+
**Sticky.** A conversation stays on its model across turns: switching
|
|
327
|
+
mid-task breaks the agent's coherence and the provider's prompt cache. The
|
|
328
|
+
key is a hash of the collection, the gateway key, the system text and the
|
|
329
|
+
first user message, which an agent resends unchanged every turn; it lives
|
|
330
|
+
in memory and lapses after an hour idle (or a restart). A side request with
|
|
331
|
+
its own system prompt (Claude Code's command checks, a subagent, a title) is
|
|
332
|
+
its own conversation. If the conversation's model cannot take a turn (an
|
|
333
|
+
image arrives), the turn is re-ranked; the model that answers becomes the
|
|
334
|
+
conversation's.
|
|
335
|
+
|
|
336
|
+
**Failover.** If the member fails before the first piece of its answer
|
|
337
|
+
reaches the caller — a 429, a 5xx, a pacing refusal, nothing at all in 90
|
|
338
|
+
seconds, an empty reply (even after reasoning) — the next member in order
|
|
339
|
+
gets the turn, transparently, up to three; the caller sees only the answer,
|
|
340
|
+
and `bench.failed_over` names who failed and why. Reasoning streamed before
|
|
341
|
+
the answer is held back with it. A streamed turn's 200 waits up to 15
|
|
342
|
+
seconds for that first piece, so a turn that fails everywhere quickly keeps
|
|
343
|
+
its status (429 when every member was rate limited, else 502), then
|
|
344
|
+
keep-alives cover the wait. After the first piece, an error goes into the
|
|
345
|
+
stream in the format's error shape: no other model can continue a
|
|
346
|
+
half-written answer.
|
|
347
|
+
|
|
348
|
+
**Streams.** OpenAI chat completions relays the member's chunks as they
|
|
349
|
+
came, tool-call deltas exactly, under the collection's name, then a closing
|
|
350
|
+
chunk with no choice that carries `bench`. The other formats stream the
|
|
351
|
+
text as it comes and each tool call **whole when the turn ends** (an
|
|
352
|
+
Anthropic `tool_use` block with one `input_json_delta`, a Responses
|
|
353
|
+
`function_call` item, a Gemini `functionCall` part, an Ollama `tool_calls`
|
|
354
|
+
line), so calls whose deltas interleave still arrive intact. Usage is the
|
|
355
|
+
member's own count. An output budget over the model's known maximum is cut
|
|
356
|
+
to it (Claude Code asks every model for 32,000). Headers name the
|
|
357
|
+
conversation (`X-Bench-Conversation`) and, when known before the 200, the
|
|
358
|
+
model (`X-Bench-Model`).
|
|
359
|
+
|
|
360
|
+
**Translations.** Anthropic `tool_use` / `tool_result` blocks, Responses
|
|
361
|
+
`function_call` / `function_call_output` items, Gemini `functionCall` /
|
|
362
|
+
`functionResponse` parts and Ollama `tool_calls` / `tool` messages become
|
|
363
|
+
OpenAI tool calls and tool messages and back; tool definitions and
|
|
364
|
+
`tool_choice` likewise (Gemini's upper-case schema types lowered). Tool call
|
|
365
|
+
ids pass through both ways; Gemini and Ollama usually send none, so they
|
|
366
|
+
are numbered per request and a result matched to the oldest open call of
|
|
367
|
+
its name. A Responses **custom tool** (Codex's free-form `apply_patch`)
|
|
368
|
+
becomes a function taking one string, `input`, its grammar in the
|
|
369
|
+
description, and the call goes back as a `custom_tool_call`. An image in an
|
|
370
|
+
Anthropic tool result goes in a user message after it (a tool message
|
|
371
|
+
carries text only); `is_error` prefixes the result with `Error: `.
|
|
372
|
+
|
|
373
|
+
**Recording (the decision).** Every call is in the usage ledger (purpose
|
|
374
|
+
`route`), and every turn a line in `runs/gateway/routes.jsonl`: model,
|
|
375
|
+
status, seconds, tool calls, tokens, who failed over — never the prompt,
|
|
376
|
+
which an agent resends whole every turn. For the router, a conversation is
|
|
377
|
+
**one unjudged offload per model that answered it** (call id
|
|
378
|
+
`<conversation>.1`, `.2` after a failover to another model), not one per
|
|
379
|
+
turn: an agent session is dozens of turns, and a seat per turn would bury
|
|
380
|
+
the panels' verdicts. A verdict on a conversation is optional
|
|
381
|
+
(`POST /bench/verdict` with that call id); the offload alone already counts
|
|
382
|
+
toward rotation. The Gateway card lists route conversations in their own
|
|
383
|
+
table (when, who asked, the first message, who answered with ✓/✗, turns,
|
|
384
|
+
tool calls, failovers) and none of them adds to **Owed**.
|
|
385
|
+
|
|
386
|
+
**Shadows (later, opt-in).** A shadow seat would answer the same turn in the
|
|
387
|
+
background for evaluation only, after the caller has its answer. The place
|
|
388
|
+
is marked in `route_turn` ("SHADOWS GO HERE"): each would be its own client
|
|
389
|
+
call with purpose `shadow`, recorded on the conversation's call id; nothing
|
|
390
|
+
the caller sees would wait for or change because of them. Not built.
|
|
391
|
+
|
|
392
|
+
**Limits, honestly.**
|
|
393
|
+
|
|
394
|
+
- Members reached through native Anthropic, Gemini or Responses endpoints
|
|
395
|
+
are not routed to: route mode speaks chat completions to the member.
|
|
396
|
+
- The member is a free or flat-rate model, not the model the client was
|
|
397
|
+
built for. Claude Code and Codex are tuned for their own vendors' models;
|
|
398
|
+
smaller models make more tool-call mistakes, and some providers reject a
|
|
399
|
+
tool schema another accepts (that request fails over).
|
|
400
|
+
- Prompt caching is the provider's own: stickiness keeps the prefix on one
|
|
401
|
+
provider, but `cache_control` marks are not passed on.
|
|
402
|
+
- Thinking is not shown to Anthropic, Responses, Gemini or Ollama clients
|
|
403
|
+
(a model's reasoning is relayed raw only in chat completions).
|
|
404
|
+
- Tool calls arrive whole, not as deltas, outside chat completions.
|
|
405
|
+
- A caller that hangs up does not stop the member; its answer runs out and
|
|
406
|
+
is recorded.
|
|
407
|
+
- Stickiness is per gateway process, in memory.
|
|
408
|
+
|
|
409
|
+
### Pointing agents at a collection
|
|
410
|
+
|
|
411
|
+
The base URL is the server: `http://127.0.0.1:8122` (the dashboard's port)
|
|
412
|
+
or `:8123` (`gateway.py`). On this machine any key text works; elsewhere,
|
|
413
|
+
or where keys are required, use a gateway key from Settings.
|
|
414
|
+
|
|
415
|
+
**Claude Code** (Anthropic Messages). It sends the model named by
|
|
416
|
+
`ANTHROPIC_MODEL` (or `--model`) for its main loop and a smaller "haiku"
|
|
417
|
+
model for side tasks (command checks, titles), so set both; a name it does
|
|
418
|
+
not know prints a harmless `unrecognized_model` note:
|
|
419
|
+
|
|
420
|
+
```bash
|
|
421
|
+
export ANTHROPIC_BASE_URL=http://127.0.0.1:8122
|
|
422
|
+
export ANTHROPIC_API_KEY=<a gateway key, or any text on this machine>
|
|
423
|
+
export ANTHROPIC_MODEL=bench/core:route
|
|
424
|
+
export ANTHROPIC_DEFAULT_HAIKU_MODEL=bench/fast:route
|
|
425
|
+
export ANTHROPIC_DEFAULT_SONNET_MODEL=bench/core:route ANTHROPIC_DEFAULT_OPUS_MODEL=bench/core:route
|
|
426
|
+
export CLAUDE_CODE_SUBAGENT_MODEL=bench/core:route
|
|
427
|
+
claude
|
|
428
|
+
```
|
|
429
|
+
|
|
430
|
+
Checked end to end (2026-10-23): Claude Code 2.1.299 ran Read, Write and
|
|
431
|
+
Bash tool loops through `bench/core:route` and `bench/fast:route`, failing
|
|
432
|
+
over past a member's 429 on the first turn and staying on one model after.
|
|
433
|
+
|
|
434
|
+
**Codex and other Responses clients**: a provider with `base_url =
|
|
435
|
+
"http://127.0.0.1:8122/v1"`, `wire_api = "responses"`, the model
|
|
436
|
+
`bench/core:route`, and the gateway key as its API key. Function and custom
|
|
437
|
+
tools work; hosted tools (`web_search`, `local_shell`) are refused, so turn
|
|
438
|
+
them off. Nothing is stored: `previous_response_id` is refused, so the
|
|
439
|
+
client must send the whole conversation every turn.
|
|
440
|
+
Not checked against a real Codex yet.
|
|
441
|
+
|
|
442
|
+
**opencode, Cline, Continue, Roo, Aider and other OpenAI-compatible agents**:
|
|
443
|
+
an OpenAI-compatible provider at `http://127.0.0.1:8122/v1`, model
|
|
444
|
+
`bench/core:route` (it is in `/v1/models`). Agents that speak Ollama
|
|
445
|
+
(`http://127.0.0.1:8122`, model `bench/core:route`) work the same way. A
|
|
446
|
+
real `curl` with tools streamed a model's tool-call deltas through
|
|
447
|
+
unchanged; these clients themselves were not run.
|
|
291
448
|
|
|
292
449
|
**Counts and durations.** `usage` and the token-count routes
|
|
293
450
|
(`/v1/messages/count_tokens`, Gemini `:countTokens`) are the size check's
|
package/PRIVACY.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Privacy policy
|
|
2
2
|
|
|
3
|
-
*Effective
|
|
3
|
+
*Effective 23 October 2026.*
|
|
4
4
|
|
|
5
5
|
Benchwarmers runs on your own machine or server. It has no account, no
|
|
6
6
|
telemetry and no server of its own, and nothing you do with it reaches
|
|
@@ -22,13 +22,15 @@ Wendell Research.
|
|
|
22
22
|
- **Real-bug task seeding**, only when you run `suite/rebench.py`: dataset rows
|
|
23
23
|
from Hugging Face and source files from GitHub.
|
|
24
24
|
|
|
25
|
-
Keys and sign-in tokens stay in your environment or
|
|
26
|
-
|
|
25
|
+
Keys and sign-in tokens stay in your environment or llm's key file
|
|
26
|
+
(`keys.json` in llm's user folder, used whether or not the `llm` CLI is
|
|
27
|
+
installed). [SECURITY.md](SECURITY.md) has the full detail.
|
|
27
28
|
|
|
28
29
|
## What stays on your machine
|
|
29
30
|
|
|
30
|
-
Everything else: your endpoints, collections, verdicts, call logs,
|
|
31
|
-
|
|
31
|
+
Everything else: your endpoints, collections, verdicts, call logs, the usage
|
|
32
|
+
ledger (tokens per call, never the prompt), probe history and task answers
|
|
33
|
+
live in your data folder, which is yours to back up,
|
|
32
34
|
move or delete.
|
|
33
35
|
|
|
34
36
|
## Third parties
|
package/README.md
CHANGED
|
@@ -10,8 +10,14 @@
|
|
|
10
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
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
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
|
-
<
|
|
13
|
+
<a href="#docker-and-hosting"><img src="https://img.shields.io/badge/Docker-compose-2496ED?logo=docker&logoColor=white" alt="Runs in Docker"></a>
|
|
14
|
+
<br>
|
|
15
|
+
<a href="GATEWAY.md#other-api-formats"><img src="https://img.shields.io/badge/API-OpenAI_%7C_Responses-111111" alt="OpenAI chat completions and Responses API"></a>
|
|
16
|
+
<a href="GATEWAY.md#other-api-formats"><img src="https://img.shields.io/badge/API-Anthropic_Messages-111111" alt="Anthropic Messages API"></a>
|
|
17
|
+
<a href="GATEWAY.md#other-api-formats"><img src="https://img.shields.io/badge/API-Gemini-111111" alt="Gemini API"></a>
|
|
18
|
+
<a href="GATEWAY.md#other-api-formats"><img src="https://img.shields.io/badge/API-Ollama-111111" alt="Ollama API"></a>
|
|
19
|
+
<a href="GATEWAY.md#route-mode-agent-clients"><img src="https://img.shields.io/badge/agents-tool_calling-111111" alt="Agent clients with tool calling, through route mode"></a>
|
|
20
|
+
<a href="https://registry.modelcontextprotocol.io/v0/servers?search=io.github.WendellResearch-AI/benchwarmers"><img src="https://img.shields.io/badge/MCP-Registry-111111" alt="MCP server in the official MCP Registry"></a>
|
|
15
21
|
<br>
|
|
16
22
|
<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
23
|
<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>
|
|
@@ -30,8 +36,12 @@ verdicts decide who sits on the next panel.
|
|
|
30
36
|
|
|
31
37
|
**What it is best at:**
|
|
32
38
|
|
|
33
|
-
- **Asking several models at once.** One
|
|
34
|
-
answers. Where they disagree is where
|
|
39
|
+
- **Asking several models at once.** One request, in the OpenAI, Anthropic,
|
|
40
|
+
Gemini or Ollama format, a panel of answers. Where they disagree is where
|
|
41
|
+
to look.
|
|
42
|
+
- **Agents too.** Claude Code and other tool-calling clients use a collection
|
|
43
|
+
through route mode: one member per turn, kept for the conversation, with
|
|
44
|
+
failover, and every call on the record.
|
|
35
45
|
- **Reviewing.** Code review, claims about code, designs: different models
|
|
36
46
|
catch different things, and two models agreeing is not proof.
|
|
37
47
|
- **Playing to each model's strengths.** A collection per kind of work
|
|
@@ -43,12 +53,13 @@ verdicts decide who sits on the next panel.
|
|
|
43
53
|
repositories, plus your verdicts on real work, instead of public leaderboards.
|
|
44
54
|
- **Knowing who to bench.** Who is confidently wrong, who has stopped
|
|
45
55
|
answering (retired automatically), and who is resting after a rate limit.
|
|
46
|
-
- **Keeping free and cheap models useful.** Health probes,
|
|
47
|
-
and one endpoint for every app, over
|
|
56
|
+
- **Keeping free and cheap models useful.** Health probes, rate limits taken
|
|
57
|
+
from what providers say and enforced, and one endpoint for every app, over
|
|
58
|
+
any OpenAI-compatible provider or hub, or Anthropic's and Gemini's own APIs.
|
|
48
59
|
|
|
49
60
|
| Related tools | Examples | What they do | What Benchwarmers does differently |
|
|
50
61
|
|---|---|---|---|
|
|
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 |
|
|
62
|
+
| Gateways and routers | LiteLLM, OpenRouter, Portkey | One request to one model, with fallback | Fans one prompt out to a panel and returns every answer; for agents, routes to one member by its record |
|
|
52
63
|
| 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
64
|
| 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
65
|
| 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 |
|
|
@@ -68,7 +79,7 @@ verdicts decide who sits on the next panel.
|
|
|
68
79
|
- [From the command line](#from-the-command-line)
|
|
69
80
|
- [From Claude Code and other agents](#from-claude-code-and-other-agents)
|
|
70
81
|
- [The suite](#the-suite)
|
|
71
|
-
- [Docker](#docker)
|
|
82
|
+
- [Docker and hosting](#docker-and-hosting)
|
|
72
83
|
- [What leaves your machine](#what-leaves-your-machine)
|
|
73
84
|
- [Limits](#limits)
|
|
74
85
|
- [Security](#security)
|
|
@@ -82,7 +93,7 @@ with [AGENTS.md](AGENTS.md).
|
|
|
82
93
|
|
|
83
94
|
## Try it
|
|
84
95
|
|
|
85
|
-
**v0.
|
|
96
|
+
**v0.3, early.** It runs with Python 3.11 or later on Windows, macOS and
|
|
86
97
|
Linux, from PyPI, npm, a checkout or Docker. Expect rough edges, and read the
|
|
87
98
|
code where these pages are thin.
|
|
88
99
|
|
|
@@ -108,12 +119,12 @@ dependencies either way: the app is the Python standard library.
|
|
|
108
119
|
|
|
109
120
|
git clone https://github.com/WendellResearch-AI/benchwarmers
|
|
110
121
|
cd benchwarmers
|
|
111
|
-
python -m venv .venv
|
|
112
|
-
.venv/bin/pip install llm # Windows: .venv\Scripts\pip install llm
|
|
122
|
+
python -m venv .venv # nothing to pip install: the standard library is enough
|
|
113
123
|
|
|
114
|
-
The
|
|
115
|
-
|
|
116
|
-
|
|
124
|
+
The built-in model client (`metrics/client.py`) makes every call. The
|
|
125
|
+
[`llm`](https://llm.datasette.io/) CLI is an alternative for its plugins:
|
|
126
|
+
install it in the venv and choose it on Settings (or `BENCH_CLIENT=llm`,
|
|
127
|
+
which wins).
|
|
117
128
|
|
|
118
129
|
2. **Make a data folder.** Everything that is yours (endpoints, model
|
|
119
130
|
registrations, collections, verdicts, probe history, task answers) lives
|
|
@@ -125,14 +136,16 @@ dependencies either way: the app is the Python standard library.
|
|
|
125
136
|
unless you name one) and points the checkout at it with a
|
|
126
137
|
`.benchwarmers-home` file; `BENCHWARMERS_HOME` overrides that. With
|
|
127
138
|
`--endpoint NAME BASE_URL --key` it also adds a first endpoint and asks for
|
|
128
|
-
its key, which goes to the
|
|
129
|
-
the
|
|
139
|
+
its key, which goes to the key file in llm's user folder (`keys.json`,
|
|
140
|
+
shared with the `llm` CLI if you have it, not needed otherwise), never into
|
|
141
|
+
the data folder. `BENCHWARMERS_KEY_<NAME>` in the environment works too.
|
|
130
142
|
|
|
131
143
|
3. **Start the dashboard.** With no endpoint yet it opens on adding one: sign
|
|
132
144
|
in with a provider on its own page (OpenRouter, NanoGPT, Hugging Face,
|
|
133
145
|
Pollinations, your ChatGPT plan, GitHub Models, or a local Ollama), or paste
|
|
134
146
|
an API key for any OpenAI-compatible API, a hub such as Open WebUI or
|
|
135
|
-
LiteLLM included.
|
|
147
|
+
LiteLLM included. Anthropic and Gemini keys can also be called through
|
|
148
|
+
those providers' own APIs (the API choice in the same dialog). Only providers that publish a sign-in for other apps are
|
|
136
149
|
offered; adding one is an entry in `metrics/connect.py` (its docstring says
|
|
137
150
|
how).
|
|
138
151
|
|
|
@@ -140,7 +153,9 @@ dependencies either way: the app is the Python standard library.
|
|
|
140
153
|
|
|
141
154
|
4. **Onboard a few models** from it ("Onboard a model"): tick one or several;
|
|
142
155
|
each is registered, probed and run through the suite, one at a time, with
|
|
143
|
-
the progress beside the button.
|
|
156
|
+
the progress beside the button. **Quick** probes now and leaves the suite
|
|
157
|
+
to the scheduled passes; **Quick + ad hoc** also keeps the bench from ever
|
|
158
|
+
calling the model on its own, for a metered or trial key.
|
|
144
159
|
|
|
145
160
|
5. **Make a collection** on the Collections page (the models a panel always
|
|
146
161
|
includes, and what it is for). The dashboard answers the gateway's routes on
|
|
@@ -163,10 +178,16 @@ dependencies either way: the app is the Python standard library.
|
|
|
163
178
|
- **Health.** Every morning each model gets a one-word prompt; once a week, a
|
|
164
179
|
short and a long one for speed. A model that stops answering, or that its
|
|
165
180
|
endpoint no longer serves, is retired automatically after one confirming
|
|
166
|
-
call, with the evidence kept, and can be restored in one click.
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
181
|
+
call, with the evidence kept, and can be restored in one click. The
|
|
182
|
+
built-in scheduler runs all of this once Settings switches it on; a fresh
|
|
183
|
+
install spends no calls until then.
|
|
184
|
+
- **Rate limits and usage.** Limits come from providers' rate-limit headers
|
|
185
|
+
and error messages, and from what you set per connection or model
|
|
186
|
+
(requests a minute, day or month; tokens a minute or day). They are kept per
|
|
187
|
+
connection, since models behind one key share them, and enforced: a call
|
|
188
|
+
waits when the wait fits, or is refused before it goes out. Every call lands
|
|
189
|
+
in a monthly usage ledger with its tokens, and the Quotas page shows what is
|
|
190
|
+
left and when it resets.
|
|
170
191
|
- **Discover.** models.dev and FreeLLMAPI, merged into one list of models
|
|
171
192
|
worth adding, each with the routes this install has to it.
|
|
172
193
|
|
|
@@ -206,10 +227,11 @@ that speaks one of them works with the server's address as its base URL:
|
|
|
206
227
|
-d '{"model": "bench/<collection>", "max_tokens": 1024, "messages": [{"role": "user", "content": "..."}]}'
|
|
207
228
|
|
|
208
229
|
Each streams when asked: the reply starts at once, and each seat's answer
|
|
209
|
-
arrives as that seat finishes.
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
230
|
+
arrives as that seat finishes. A panel answers with text, so a request
|
|
231
|
+
carrying tools is refused with a 400 that points to **route mode**
|
|
232
|
+
(`bench/<collection>:route`, one member with its tools; see
|
|
233
|
+
[below](#from-claude-code-and-other-agents)). GATEWAY.md, "Other API formats",
|
|
234
|
+
has a curl for each and what is refused or ignored.
|
|
213
235
|
|
|
214
236
|
## From the command line
|
|
215
237
|
|
|
@@ -294,6 +316,16 @@ On native Windows, a client that cannot start `npx` itself needs
|
|
|
294
316
|
| `BENCHWARMERS_KEY` | a gateway key from Settings, sent as `Authorization: Bearer`; needed for a server on another machine, or where keys are required |
|
|
295
317
|
| `BENCHWARMERS_TIMEOUT` | seconds to wait for a panel, default 900 |
|
|
296
318
|
|
|
319
|
+
**An agent running on a collection.** The MCP tools ask a panel for a
|
|
320
|
+
second opinion; an agent can also *be driven by* a collection, in **route
|
|
321
|
+
mode** (`bench/<collection>:route`): one member, picked by its record, gets
|
|
322
|
+
each turn with its tools, stays on for the whole conversation, and is
|
|
323
|
+
replaced by the next member if it fails before answering. Claude Code
|
|
324
|
+
points at it with `ANTHROPIC_BASE_URL=http://127.0.0.1:8122` and
|
|
325
|
+
`ANTHROPIC_MODEL=bench/core:route`; Codex, opencode, Cline and other
|
|
326
|
+
OpenAI-, Responses- or Ollama-speaking agents with the same base URL.
|
|
327
|
+
[GATEWAY.md](GATEWAY.md), "Route mode", has the set-up and the limits.
|
|
328
|
+
|
|
297
329
|
<!-- The MCP Registry's proof that the PyPI package is this server (server.json):
|
|
298
330
|
mcp-name: io.github.WendellResearch-AI/benchwarmers -->
|
|
299
331
|
|
|
@@ -314,7 +346,7 @@ code makes the best tasks, because no model has trained on it. See
|
|
|
314
346
|
[suite/README.md](suite/README.md), [suite/REBENCH.md](suite/REBENCH.md) and
|
|
315
347
|
[BENCHMARKS.md](BENCHMARKS.md).
|
|
316
348
|
|
|
317
|
-
## Docker
|
|
349
|
+
## Docker and hosting
|
|
318
350
|
|
|
319
351
|
One container runs the dashboard, the gateway (on the same port, at `/v1`)
|
|
320
352
|
and the scheduler, with the data folder as a volume and no `llm` CLI (the
|
|
@@ -356,21 +388,33 @@ reaches Wendell Research. What it sends, and where:
|
|
|
356
388
|
- **Seeding real-bug tasks**, only when you run `suite/rebench.py`: the
|
|
357
389
|
SWE-rebench-V2 rows from Hugging Face and the files from GitHub.
|
|
358
390
|
|
|
359
|
-
Keys stay in the environment or
|
|
360
|
-
folder. [PRIVACY.md](PRIVACY.md) and [SECURITY.md](SECURITY.md) have the
|
|
391
|
+
Keys stay in the environment or llm's key file (`keys.json` in its user
|
|
392
|
+
folder), never in the data folder. [PRIVACY.md](PRIVACY.md) and [SECURITY.md](SECURITY.md) have the
|
|
361
393
|
detail.
|
|
362
394
|
|
|
363
395
|
## Limits
|
|
364
396
|
|
|
365
|
-
- **
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
397
|
+
- **Tools go through route mode only.** A panel answers with text; a request
|
|
398
|
+
with tools is refused there, not trimmed. Route mode passes turns only to
|
|
399
|
+
members on chat-completions endpoints, keeps its sticky conversations in
|
|
400
|
+
memory (a restart starts each on a fresh pick), and has been run end to end
|
|
401
|
+
with Claude Code; the Codex, opencode and Cline set-ups are written from
|
|
402
|
+
their docs. Shadow seats (other members answering an agent's turn for
|
|
403
|
+
evaluation) are planned, not built.
|
|
404
|
+
- **Some parts are checked against fakes only.** The native Anthropic and
|
|
405
|
+
Gemini endpoints, and the LiteLLM and CLI Proxy API hub adapters, are built
|
|
406
|
+
from those APIs' documentation and source and tested against fake servers,
|
|
407
|
+
not yet against the real services.
|
|
408
|
+
- **A merged reply arrives in one piece** once the merger has finished; the
|
|
409
|
+
seats before it stream as they finish.
|
|
410
|
+
- **Pacing and the ledger cover the built-in client.** Calls made through the
|
|
411
|
+
`llm` CLI, when you choose it, are neither paced nor counted.
|
|
371
412
|
- **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
|
|
373
|
-
paced, not burst.
|
|
413
|
+
tiers. Onboarding runs one model at a time for that reason (Quick spreads
|
|
414
|
+
the suite over the scheduled passes); a suite pass is paced, not burst.
|
|
415
|
+
- **The scheduler lives in the server.** Its jobs run while the dashboard (or
|
|
416
|
+
the container, or the service) is up; a missed run is made up once, within
|
|
417
|
+
seven days, when it comes back.
|
|
374
418
|
- **One install, one person** at heart. Logins and gateway keys exist for a
|
|
375
419
|
LAN or a server, but there are no per-user views or roles.
|
|
376
420
|
- **The onboarding queue lives in the dashboard's memory.** Restarting it
|
package/SECURITY.md
CHANGED
|
@@ -58,7 +58,8 @@ open itself to anyone.
|
|
|
58
58
|
## What Benchwarmers touches
|
|
59
59
|
|
|
60
60
|
- **Keys.** Endpoint keys come from `BENCHWARMERS_KEY_<NAME>` (the key name
|
|
61
|
-
upper-cased, `-` as `_`) first, then from
|
|
61
|
+
upper-cased, `-` as `_`) first, then from llm's key file (`keys.json` in
|
|
62
|
+
its user folder, used whether or not the `llm` CLI is installed). They are
|
|
62
63
|
never written to the data folder, never shown back, and never put in a log.
|
|
63
64
|
Sign-ins through a provider's page store their token the same way.
|
|
64
65
|
- **The network.** Your prompts go only to the endpoints you add; the probes,
|
|
@@ -68,9 +69,9 @@ open itself to anyone.
|
|
|
68
69
|
dashboard's page, and Hugging Face and GitHub only when you run
|
|
69
70
|
`suite/rebench.py`. [PRIVACY.md](PRIVACY.md) lists them.
|
|
70
71
|
- **Files.** It writes inside its data folder (`BENCHWARMERS_HOME`, or the
|
|
71
|
-
folder `.benchwarmers-home` names) and, in
|
|
72
|
-
|
|
73
|
-
|
|
72
|
+
folder `.benchwarmers-home` names) and, in llm's user folder, the key file
|
|
73
|
+
(when you paste a key or sign in) and the model list (when it registers a
|
|
74
|
+
model). Images sent to the gateway are never written to a log.
|
|
74
75
|
- **Processes.** Onboarding, the probes and the suite run this repository's
|
|
75
76
|
own scripts with the same Python. Model ids that reach a command line are
|
|
76
77
|
checked against a strict pattern first.
|
package/USAGE.md
CHANGED
|
@@ -47,8 +47,19 @@ Dismiss hides one you never want offered (an image model, say); the dismissed
|
|
|
47
47
|
list brings it back.
|
|
48
48
|
|
|
49
49
|
Each model is registered, probed with four short calls, and, if it answers,
|
|
50
|
-
run through the suite.
|
|
51
|
-
|
|
50
|
+
run through the suite. **Onboard as**, under the list, chooses how much that
|
|
51
|
+
spends:
|
|
52
|
+
|
|
53
|
+
- **Full**: the probe, then the suite now.
|
|
54
|
+
- **Quick**: the probe now; the scheduled suite passes fill the suite in, six
|
|
55
|
+
tasks a run. The same calls, spread out.
|
|
56
|
+
- **Quick + ad hoc**: the probe only, and the bench never calls the model on
|
|
57
|
+
its own (no explorer seat, no scheduled runs). The one that saves calls, for
|
|
58
|
+
a metered, capped or trial key. A collection that lists it still calls it.
|
|
59
|
+
|
|
60
|
+
`bench onboard <served> --quick [--ad-hoc]` does the same from a terminal.
|
|
61
|
+
Onboarding takes minutes and spends calls, so models run **one at a time**,
|
|
62
|
+
in a queue the dashboard keeps:
|
|
52
63
|
|
|
53
64
|
- One model with nothing ahead of it is followed step by step in the popup.
|
|
54
65
|
- Otherwise the popup shows the queue, and a chip beside the button shows the
|
|
@@ -62,6 +73,12 @@ at a time**, in a queue the dashboard keeps:
|
|
|
62
73
|
A model that registered but did not answer stays on the bench as
|
|
63
74
|
unavailable, so the daily probe keeps watching it.
|
|
64
75
|
|
|
76
|
+
**Roles.** A model's popup sets what it is for: `general` (the default),
|
|
77
|
+
`code`, `light` (small and fast), `vision` (reads images well, weak at text:
|
|
78
|
+
OCR and document models; seated only when a request carries images) or
|
|
79
|
+
`image` (makes images; never seated). A model that reads images and does
|
|
80
|
+
everything else well stays `general`.
|
|
81
|
+
|
|
65
82
|
## Collections
|
|
66
83
|
|
|
67
84
|
A collection is a named panel: the models it always includes, the shape of
|
|
@@ -116,9 +133,24 @@ its base URL and a collection as its model:
|
|
|
116
133
|
| Older tools on `/v1/completions` | `http://127.0.0.1:8122/v1` | one `prompt` |
|
|
117
134
|
|
|
118
135
|
Asked to stream, each answers at once and sends each seat's answer as it
|
|
119
|
-
finishes.
|
|
120
|
-
|
|
121
|
-
|
|
136
|
+
finishes. GATEWAY.md, "Other API formats", has a curl for each.
|
|
137
|
+
|
|
138
|
+
**An agent that calls tools** (Claude Code, Codex, opencode, Cline...) uses
|
|
139
|
+
a collection's **route mode**: model `bench/<collection>:route`. No panel:
|
|
140
|
+
one member, picked by its record, gets the request with its tools and
|
|
141
|
+
streams its answer back in the agent's own format; the conversation stays
|
|
142
|
+
on that model, and a member that fails before answering is replaced by the
|
|
143
|
+
next. A panel refuses tools with a 400 saying so. For Claude Code:
|
|
144
|
+
|
|
145
|
+
```bash
|
|
146
|
+
export ANTHROPIC_BASE_URL=http://127.0.0.1:8122 ANTHROPIC_API_KEY=local
|
|
147
|
+
export ANTHROPIC_MODEL=bench/core:route ANTHROPIC_DEFAULT_HAIKU_MODEL=bench/fast:route
|
|
148
|
+
claude
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
Each conversation is one offload per model that answered it; a verdict is
|
|
152
|
+
optional, and the Gateway card lists route conversations apart from the
|
|
153
|
+
panels. GATEWAY.md, "Route mode", has the other clients and the limits.
|
|
122
154
|
|
|
123
155
|
**A coding agent** connects over MCP instead: the Claude Code plugin
|
|
124
156
|
(`/plugin marketplace add WendellResearch-AI/benchwarmers`, then
|
package/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
0.
|
|
1
|
+
0.3.1
|
package/cli.py
CHANGED
|
@@ -615,7 +615,7 @@ def parser() -> argparse.ArgumentParser:
|
|
|
615
615
|
c.add_argument("--shape", help="the task shape explorers are picked for; empty for automatic")
|
|
616
616
|
c.add_argument("--description", help="what it is for: callers read it to choose")
|
|
617
617
|
c.add_argument("--tags", help="comma-separated")
|
|
618
|
-
c.add_argument("--reply", choices=["all", "merged"])
|
|
618
|
+
c.add_argument("--reply", choices=["all", "merged", "route"])
|
|
619
619
|
c.add_argument("--merger", help="the alias that merges, for --reply merged")
|
|
620
620
|
|
|
621
621
|
cmd("endpoints", cmd_endpoints, "where models are served from")
|