@wendellresearch-ai/benchwarmers 0.2.1 → 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.
- package/AGENTS.md +6 -1
- package/GATEWAY.md +149 -6
- package/README.md +42 -2
- package/USAGE.md +16 -0
- package/VERSION +1 -1
- package/cli.py +9 -1
- package/gateway.py +275 -91
- package/metrics/catalog.py +10 -1
- package/metrics/client.py +537 -127
- package/metrics/cost.py +38 -19
- package/metrics/formats.py +1021 -0
- package/metrics/guard.py +37 -7
- package/metrics/learn.py +19 -6
- package/metrics/ledger.py +148 -0
- package/metrics/model_roles.py +13 -3
- package/metrics/pacing.py +248 -0
- package/metrics/quota.py +383 -0
- package/metrics/route.py +5 -3
- package/package.json +2 -1
- package/perf/run.py +30 -7
- package/scripts/check.py +5 -0
- package/scripts/onboard.py +10 -1
- package/scripts/test_client.py +50 -6
- package/scripts/test_formats.py +549 -0
- package/scripts/test_merged.py +4 -3
- package/scripts/test_quota.py +334 -0
- package/scripts/test_serve_edit.py +10 -5
- package/scripts/test_server_json.py +79 -0
- package/scripts/test_verdict.py +5 -1
- package/scripts/test_vision.py +6 -3
- package/serve.py +104 -36
- package/ui/bench.js +3 -2
package/AGENTS.md
CHANGED
|
@@ -105,6 +105,9 @@ Any OpenAI-compatible client asks for the model `bench/<collection>`:
|
|
|
105
105
|
-d '{"model": "bench/<collection>", "messages": [{"role": "user", "content": "..."}]}'
|
|
106
106
|
|
|
107
107
|
Every seat's answer comes back, plus a `bench` field with each seat's call id.
|
|
108
|
+
Clients of the Anthropic, Ollama, Gemini and Responses APIs work too, with
|
|
109
|
+
the server's address as their base URL (GATEWAY.md, "Other API formats");
|
|
110
|
+
requests carrying tools are refused, since a panel answers with text.
|
|
108
111
|
For a chat window that shows one answer, ask for `bench/<collection>:merged`
|
|
109
112
|
(or set the collection's reply to merged): a merger model writes one answer
|
|
110
113
|
saying where the seats agreed and disagreed, and every seat is still in
|
|
@@ -133,7 +136,9 @@ browser app on another origin also needs `BENCH_ALLOWED_ORIGINS`. Check the
|
|
|
133
136
|
result on Settings, Server and access, which shows each of these as it stands
|
|
134
137
|
(never a secret's value). In Docker: put the token and the `BENCHWARMERS_KEY_<NAME>` variables in
|
|
135
138
|
a `.env` beside `compose.yaml`, then `docker compose up -d`; the dashboard and
|
|
136
|
-
gateway share one port and the data folder is a volume.
|
|
139
|
+
gateway share one port and the data folder is a volume. For a hosting platform
|
|
140
|
+
(Fly.io, Railway, Render) or a VPS with Caddy, follow [HOSTING.md](HOSTING.md)
|
|
141
|
+
and its templates under `deploy/`.
|
|
137
142
|
|
|
138
143
|
## When something looks wrong
|
|
139
144
|
|
package/GATEWAY.md
CHANGED
|
@@ -2,6 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
Status: **built** (2026-10-16): `python gateway.py`, then point an OpenAI client
|
|
4
4
|
at `http://127.0.0.1:8123/v1` with a collection as the model, e.g. `bench/core`.
|
|
5
|
+
Since 2026-10-23 it also speaks Anthropic Messages, OpenAI Responses, Ollama,
|
|
6
|
+
Gemini and OpenAI's legacy completions, and streams each seat as it finishes
|
|
7
|
+
(Other API formats, Streaming).
|
|
5
8
|
Diagram: `.archify/architecture-bench-gateway-20261016-182500/bench-gateway.html`.
|
|
6
9
|
|
|
7
10
|
## The idea in one paragraph
|
|
@@ -101,8 +104,15 @@ exactly as sent:
|
|
|
101
104
|
plan) gets no images yet.
|
|
102
105
|
- **A vision collection** is members that take images; `GET /v1/models` lists
|
|
103
106
|
`vision` in its `together.capabilities` when every member does. An image
|
|
104
|
-
request on a collection with no shape takes the shape `vision` (general
|
|
105
|
-
light models), so verdicts on image work are counted apart.
|
|
107
|
+
request on a collection with no shape takes the shape `vision` (general,
|
|
108
|
+
light and vision models), so verdicts on image work are counted apart.
|
|
109
|
+
- **Vision specialists.** A model whose role is `vision` (set in its popup) is
|
|
110
|
+
good at reading images and weak at text-only work, such as an OCR or
|
|
111
|
+
document model. Only the `vision` shape admits it, and as a collection
|
|
112
|
+
member it sits only when the request carries images; on a text request it
|
|
113
|
+
is listed in `bench.skipped` and its seat goes to the next member or an
|
|
114
|
+
explorer. Named in `with`, it sits anyway. A model that reads images and
|
|
115
|
+
does everything else well stays `general`.
|
|
106
116
|
- **Size.** The body cap is 20 MB: a photo as a data URL is 4/3 of its file,
|
|
107
117
|
and 20 MB is the tightest whole-request limit among the big providers
|
|
108
118
|
(Gemini's for inline data). Above it the gateway answers 413 and says to
|
|
@@ -126,7 +136,7 @@ exactly as sent:
|
|
|
126
136
|
`choices[0].message.content` is markdown: one section per seat with its call
|
|
127
137
|
id, status and time. Beside it, a `bench` field carries the same seats as data,
|
|
128
138
|
so a caller need not parse the markdown (OpenAI clients ignore unknown fields;
|
|
129
|
-
a streaming client gets it on the
|
|
139
|
+
a streaming client gets it on the last chunk, the one with the finish_reason):
|
|
130
140
|
|
|
131
141
|
```json
|
|
132
142
|
"bench": {"panel": "p20261017-...", "collection": "bench/core", "shape": "review",
|
|
@@ -149,14 +159,146 @@ fits. It gets its own call id (`<panel>.merger`, in `bench.merger`, the
|
|
|
149
159
|
offload log and the Gateway card) so a verdict can judge the merge; `bench`
|
|
150
160
|
still carries every seat. If no merger has the context window for the
|
|
151
161
|
request plus every answer, or it fails, the reply is every answer with a
|
|
152
|
-
one-line note (`bench.note`). A streaming client gets
|
|
153
|
-
|
|
162
|
+
one-line note (`bench.note`). A streaming client gets the merged answer in one
|
|
163
|
+
piece once the merge is done (Streaming).
|
|
154
164
|
|
|
155
165
|
A verdict is `POST /bench/verdict` with `{"call", "useful": true|false, "note",
|
|
156
166
|
"real": [...], "false": [...]}`, `offload.py verdict <call id>`, or the verdict
|
|
157
167
|
dialog in the dashboard's Collections tab, under Gateway (click a seat). Sending the same verdict
|
|
158
168
|
twice writes nothing.
|
|
159
169
|
|
|
170
|
+
## Streaming
|
|
171
|
+
|
|
172
|
+
With `"stream": true` (or the format's own way of asking, below):
|
|
173
|
+
|
|
174
|
+
- **The 200 goes at once**: as soon as the panel is seated, and at the
|
|
175
|
+
latest after a second (the shape picker can take longer). A refusal raised
|
|
176
|
+
before then (too large for a member, an image no seat can see, no seat
|
|
177
|
+
free) keeps its status and the format's error body, as without streaming.
|
|
178
|
+
- **Keep-alives** go every 10 seconds while the seats think, in the format's
|
|
179
|
+
own form: an SSE comment for the OpenAI formats, Anthropic's `ping`, a
|
|
180
|
+
chunk with no text for Ollama and Gemini (their readers have no comments).
|
|
181
|
+
A client or proxy with an idle timeout keeps the connection.
|
|
182
|
+
- **Every answer** (`reply` all): the heading, then each seat's section the
|
|
183
|
+
moment that seat finishes, then the verdict lines. The pieces are the same
|
|
184
|
+
as the non-streamed reply's, with the seats in the order they finished
|
|
185
|
+
rather than seat order.
|
|
186
|
+
- **Merged**: the merged answer in one piece when the merge is done (or
|
|
187
|
+
every answer with its note, if the merge fails). Token by token waits for
|
|
188
|
+
the built-in client's streaming.
|
|
189
|
+
- **An error after the 200** goes into the stream in the format's own error
|
|
190
|
+
shape (the OpenAI SDKs' `{"error": ...}` chunk, Anthropic's `error` event,
|
|
191
|
+
Responses' `error` and `response.failed`, Ollama's `{"error": ...}` line,
|
|
192
|
+
Gemini's error object), so a client raises rather than hangs.
|
|
193
|
+
- **The `bench` field** rides on the last event where the format has room
|
|
194
|
+
(the table below). It is in the JSON on the wire; an SDK that parses into
|
|
195
|
+
fixed types may drop it (the Anthropic SDK's accumulated final message and
|
|
196
|
+
the `ollama` library's replies do; read the raw event, or the call ids,
|
|
197
|
+
which every reply's text carries). A caller that hangs up does not stop
|
|
198
|
+
the panel: every seat still answers and is recorded.
|
|
199
|
+
|
|
200
|
+
## Other API formats
|
|
201
|
+
|
|
202
|
+
Each is a thin adapter around the same panel (`metrics/formats.py`): the
|
|
203
|
+
request becomes OpenAI-style messages, the panel runs exactly as on
|
|
204
|
+
`/v1/chat/completions` (collections, explorers, size check, images, keys,
|
|
205
|
+
scope, rate caps, CORS, the call log), and the reply goes back in the
|
|
206
|
+
format's own shape. The model field is the collection: `bench/core`, or
|
|
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 both
|
|
209
|
+
reply modes.
|
|
210
|
+
|
|
211
|
+
| Format | Routes | Streams with | `bench` field | Known clients |
|
|
212
|
+
|---|---|---|---|---|
|
|
213
|
+
| OpenAI chat completions | `POST /v1/chat/completions`, `GET /v1/models` | `"stream": true` (SSE) | in the reply; the last chunk | OpenAI SDKs, Open WebUI's OpenAI connection, most chat apps |
|
|
214
|
+
| OpenAI Responses | `POST /v1/responses` | `"stream": true` (typed events) | in the response; `response.completed` | OpenAI SDKs' `responses.create`, Responses-only clients |
|
|
215
|
+
| OpenAI legacy completions | `POST /v1/completions` | `"stream": true` (SSE) | in the reply; the last chunk | older tools that send a `prompt` |
|
|
216
|
+
| Anthropic Messages | `POST /v1/messages`, `POST /v1/messages/count_tokens` | `"stream": true` (SSE events) | in the message; `message_delta` | Anthropic SDKs with `base_url`, tools built on them |
|
|
217
|
+
| Ollama | `POST /api/chat`, `POST /api/generate`, `GET /api/tags`, `GET /api/version`, `POST /api/show` | on unless `"stream": false` (NDJSON) | in the reply; the `done` line | Open WebUI's Ollama connection, the `ollama` libraries, Ollama-only apps |
|
|
218
|
+
| Gemini | `POST /v1beta/models/<collection>:generateContent`, `:streamGenerateContent` (`?alt=sse`, else a JSON array), `:countTokens`, `GET /v1beta/models` | the stream route | **headers**: `X-Bench-Panel`, `X-Bench-Seats` (call, seat, alias, status); none when streamed | Google GenAI SDKs with `base_url`, Gemini REST |
|
|
219
|
+
|
|
220
|
+
The base URL is the server itself (`http://127.0.0.1:8122` on the dashboard's
|
|
221
|
+
port, `:8123` for `gateway.py`): the Anthropic SDK and Google's add their own
|
|
222
|
+
`/v1/...` and `/v1beta/...`, Ollama clients their `/api/...`; OpenAI clients
|
|
223
|
+
take `.../v1`.
|
|
224
|
+
|
|
225
|
+
```bash
|
|
226
|
+
curl http://127.0.0.1:8122/v1/messages -H "Content-Type: application/json" \
|
|
227
|
+
-d '{"model": "bench/core", "max_tokens": 1024, "messages": [{"role": "user", "content": "Where can this return a stale value? ..."}]}'
|
|
228
|
+
curl http://127.0.0.1:8122/v1/responses -H "Content-Type: application/json" \
|
|
229
|
+
-d '{"model": "bench/core", "input": "Where can this return a stale value? ..."}'
|
|
230
|
+
curl http://127.0.0.1:8122/v1/completions -H "Content-Type: application/json" \
|
|
231
|
+
-d '{"model": "bench/core", "prompt": "Where can this return a stale value? ..."}'
|
|
232
|
+
curl http://127.0.0.1:8122/api/chat -H "Content-Type: application/json" \
|
|
233
|
+
-d '{"model": "bench/core", "stream": false, "messages": [{"role": "user", "content": "Where can this return a stale value? ..."}]}'
|
|
234
|
+
curl "http://127.0.0.1:8122/v1beta/models/bench/core:streamGenerateContent?alt=sse" -H "Content-Type: application/json" \
|
|
235
|
+
-d '{"contents": [{"parts": [{"text": "Where can this return a stale value? ..."}]}]}'
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
Every POST needs `Content-Type: application/json`, as on chat completions
|
|
239
|
+
(it is what keeps a web page's plain form out).
|
|
240
|
+
|
|
241
|
+
```python
|
|
242
|
+
import anthropic
|
|
243
|
+
bench = anthropic.Anthropic(base_url="http://127.0.0.1:8122", api_key="local") # or a gateway key
|
|
244
|
+
with bench.messages.stream(model="bench/core", max_tokens=1024,
|
|
245
|
+
messages=[{"role": "user", "content": "..."}]) as s:
|
|
246
|
+
for text in s.text_stream:
|
|
247
|
+
print(text, end="") # each seat's section as that seat finishes
|
|
248
|
+
|
|
249
|
+
from google import genai
|
|
250
|
+
from google.genai import types
|
|
251
|
+
gem = genai.Client(api_key="local", http_options=types.HttpOptions(base_url="http://127.0.0.1:8122"))
|
|
252
|
+
print(gem.models.generate_content(model="bench/core:merged", contents="...").text)
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
In **Open WebUI**, add `http://127.0.0.1:8122` as an Ollama connection (or
|
|
256
|
+
`.../v1` as an OpenAI one): the collections appear as models.
|
|
257
|
+
|
|
258
|
+
**Keys.** Where keys are required, a gateway key goes as `Authorization:
|
|
259
|
+
Bearer`, or as `x-api-key` (the Anthropic SDK's `api_key`), `x-goog-api-key`
|
|
260
|
+
(the Google SDKs' `api_key`), or `?key=` on a Gemini route only (Google's REST
|
|
261
|
+
examples). The first one sent is judged. The access log never records a
|
|
262
|
+
query string or a key; neither does http.server's own error line.
|
|
263
|
+
|
|
264
|
+
**Images** in each format's shape become `image_url` parts, so only seats that
|
|
265
|
+
can see sit (Images): Anthropic `image` blocks (base64 or url), Responses
|
|
266
|
+
`input_image`, Ollama's `images` (base64, its type read from its first
|
|
267
|
+
bytes), Gemini `inlineData` and `fileData` at an http(s) URL.
|
|
268
|
+
|
|
269
|
+
**Refused, not dropped** (400, with why), because a client waiting for what
|
|
270
|
+
it asked would hang or loop on a panel's text:
|
|
271
|
+
|
|
272
|
+
- **Tools and agents are not supported yet.** A request carrying tools, or a
|
|
273
|
+
conversation holding tool calls or tool results, in any format (chat
|
|
274
|
+
completions included), is refused with a message saying a route mode is
|
|
275
|
+
coming: one member proxied with tools and streaming, for agent clients
|
|
276
|
+
such as Claude Code, Codex or opencode, with background shadow seats as an
|
|
277
|
+
opt-in after that. An empty `tools` list is no tools.
|
|
278
|
+
- a PDF, audio or other non-image part; an image as an uploaded file id
|
|
279
|
+
(Anthropic `file`, Responses `file_id`, Gemini's Files API);
|
|
280
|
+
- `previous_response_id` or a stored `conversation` on Responses: nothing is
|
|
281
|
+
stored (`store` is answered `false`), so send the whole conversation;
|
|
282
|
+
- Ollama's `context` (a model's own token state): use `/api/chat`;
|
|
283
|
+
- more than one prompt on legacy completions.
|
|
284
|
+
|
|
285
|
+
**Ignored**: sampling options (`max_tokens`, `temperature`, `top_p`, `stop`
|
|
286
|
+
and their native names) are read but each seat keeps its own call options;
|
|
287
|
+
structured-output settings (Ollama `format`, Gemini `responseSchema`,
|
|
288
|
+
Responses `text.format`), since a panel answers in markdown; a model's own
|
|
289
|
+
reasoning from an earlier turn (Anthropic thinking blocks, Responses
|
|
290
|
+
reasoning items, Gemini thought parts).
|
|
291
|
+
|
|
292
|
+
**Counts and durations.** `usage` and the token-count routes
|
|
293
|
+
(`/v1/messages/count_tokens`, Gemini `:countTokens`) are the size check's
|
|
294
|
+
estimate, characters / 4 plus 10% and 2,500 a picture, with no model called.
|
|
295
|
+
Ollama's durations are real. `/api/version` reports the Ollama API version
|
|
296
|
+
whose shapes these follow (0.12.0), so clients send what it reads.
|
|
297
|
+
|
|
298
|
+
**On the dashboard's port**, Ollama's five `/api/` routes are the gateway's
|
|
299
|
+
(a gateway key, never the login cookie); the dashboard's own `/api/...`
|
|
300
|
+
routes never use those names.
|
|
301
|
+
|
|
160
302
|
## The decision model's job (optional)
|
|
161
303
|
|
|
162
304
|
A collection's members are fixed; only the explorer seats are chosen per
|
|
@@ -201,7 +343,8 @@ key limited to its collection and a rate, or keep the key on the app's server.
|
|
|
201
343
|
|
|
202
344
|
| Part | Job |
|
|
203
345
|
|---|---|
|
|
204
|
-
| Gateway | `gateway.py`, stdlib server on 127.0.0.1. `GET /v1/models` lists the collections with description, tags and make-up; `POST /v1/chat/completions` runs one, images included
|
|
346
|
+
| Gateway | `gateway.py`, stdlib server on 127.0.0.1. `GET /v1/models` lists the collections with description, tags and make-up; `POST /v1/chat/completions` runs one, images included, streamed seat by seat when asked. |
|
|
347
|
+
| Formats | `metrics/formats.py`: Anthropic Messages, Responses, legacy completions, Ollama and Gemini, each translated to and from OpenAI messages (Other API formats); the outbound half is for the built-in client to call those APIs natively, not wired yet. |
|
|
205
348
|
| Collections | `metrics/bench_collections.py` reads the two config files and computes explorer seats. |
|
|
206
349
|
| Seat router | `route.panel(members=…, explorers=n)` fills explorer seats by staleness for the shape. |
|
|
207
350
|
| Shape picker | Optional decision model (above). |
|
package/README.md
CHANGED
|
@@ -76,7 +76,8 @@ verdicts decide who sits on the next panel.
|
|
|
76
76
|
- [Licence](#licence)
|
|
77
77
|
|
|
78
78
|
Day-to-day use (the dashboard's pages, connecting apps, onboarding in bulk) is
|
|
79
|
-
in [USAGE.md](USAGE.md).
|
|
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
|
|
80
81
|
with [AGENTS.md](AGENTS.md).
|
|
81
82
|
|
|
82
83
|
## Try it
|
|
@@ -189,6 +190,27 @@ seat is still in `bench`. Record a verdict per call once you have checked it:
|
|
|
189
190
|
or from the dashboard. See [GATEWAY.md](GATEWAY.md) for the full contract:
|
|
190
191
|
images, prompt sizes, who may call, and the reply's shape.
|
|
191
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
|
+
|
|
192
214
|
## From the command line
|
|
193
215
|
|
|
194
216
|
`bench` (or `benchwarmers`, the same program) does what the dashboard does,
|
|
@@ -272,6 +294,9 @@ On native Windows, a client that cannot start `npx` itself needs
|
|
|
272
294
|
| `BENCHWARMERS_KEY` | a gateway key from Settings, sent as `Authorization: Bearer`; needed for a server on another machine, or where keys are required |
|
|
273
295
|
| `BENCHWARMERS_TIMEOUT` | seconds to wait for a panel, default 900 |
|
|
274
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
|
+
|
|
275
300
|
## The suite
|
|
276
301
|
|
|
277
302
|
Models are scored on review work: find the faulty line, or say whether a
|
|
@@ -302,6 +327,18 @@ built-in client makes every call). Put the secrets in a `.env` file beside
|
|
|
302
327
|
then `docker compose up -d` and open http://127.0.0.1:8122. The compose file
|
|
303
328
|
publishes on this machine only; see its comments for a proxy.
|
|
304
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
|
+
[](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
|
+
|
|
305
342
|
## What leaves your machine
|
|
306
343
|
|
|
307
344
|
Benchwarmers has no account, no telemetry and no server of its own; nothing
|
|
@@ -325,7 +362,10 @@ detail.
|
|
|
325
362
|
|
|
326
363
|
## Limits
|
|
327
364
|
|
|
328
|
-
- **
|
|
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
|
|
329
369
|
CLI Proxy API hub adapters are built from those projects' source, not yet
|
|
330
370
|
run against a real hub.
|
|
331
371
|
- **Calls cost.** Probes, onboarding and the suite run on your keys and free
|
package/USAGE.md
CHANGED
|
@@ -104,6 +104,22 @@ Images go in as OpenAI `image_url` parts; only models that take images sit,
|
|
|
104
104
|
and the rest are listed in `bench.skipped`. [GATEWAY.md](GATEWAY.md) has the
|
|
105
105
|
limits and the reply's full shape.
|
|
106
106
|
|
|
107
|
+
**A client that speaks another API** works too, with the server's address as
|
|
108
|
+
its base URL and a collection as its model:
|
|
109
|
+
|
|
110
|
+
| Client | Base URL | Notes |
|
|
111
|
+
| --- | --- | --- |
|
|
112
|
+
| Open WebUI, as an **Ollama** connection | `http://127.0.0.1:8122` | collections listed from `/api/tags`; `bench/<collection>:merged` reads best in a chat window |
|
|
113
|
+
| **Anthropic** SDK (`anthropic.Anthropic(base_url=...)`) | `http://127.0.0.1:8122` | `/v1/messages`; a gateway key goes in `api_key` |
|
|
114
|
+
| **Google GenAI** SDK (`http_options=HttpOptions(base_url=...)`) | `http://127.0.0.1:8122` | `generate_content(model="bench/<collection>")`; a key in `api_key` |
|
|
115
|
+
| OpenAI **Responses** clients | `http://127.0.0.1:8122/v1` | `responses.create`; nothing is stored, so send the whole conversation |
|
|
116
|
+
| Older tools on `/v1/completions` | `http://127.0.0.1:8122/v1` | one `prompt` |
|
|
117
|
+
|
|
118
|
+
Asked to stream, each answers at once and sends each seat's answer as it
|
|
119
|
+
finishes. Agents that need tools (Claude Code, Codex, opencode as callers)
|
|
120
|
+
are not supported yet: a request with tools is refused with a 400 saying so.
|
|
121
|
+
GATEWAY.md, "Other API formats", has a curl for each.
|
|
122
|
+
|
|
107
123
|
**A coding agent** connects over MCP instead: the Claude Code plugin
|
|
108
124
|
(`/plugin marketplace add WendellResearch-AI/benchwarmers`, then
|
|
109
125
|
`/plugin install benchwarmers@benchwarmers`), or `benchwarmers mcp` in any MCP
|
package/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
0.2.
|
|
1
|
+
0.2.2
|
package/cli.py
CHANGED
|
@@ -258,7 +258,10 @@ def cmd_onboard(args) -> None:
|
|
|
258
258
|
reply = call("GET", "/api/onboard-queue") if verb == "status" else \
|
|
259
259
|
call("POST", f"/api/onboard-queue/{verb}", {})
|
|
260
260
|
else:
|
|
261
|
-
|
|
261
|
+
if args.ad_hoc and not args.quick:
|
|
262
|
+
sys.exit("--ad-hoc goes with --quick: an ad hoc model gets no suite run of its own")
|
|
263
|
+
mode = "quick-adhoc" if args.ad_hoc else "quick" if args.quick else "full"
|
|
264
|
+
items = [{"served": s, "endpoint": args.endpoint or "", "mode": mode} for s in args.served]
|
|
262
265
|
reply = call("POST", "/api/onboard-queue", {"items": items})
|
|
263
266
|
if args.wait:
|
|
264
267
|
while reply.get("running"):
|
|
@@ -482,6 +485,7 @@ keeps only the answers. Then judge each seat you checked:
|
|
|
482
485
|
bench candidates --new
|
|
483
486
|
bench onboard minimax/minimax-m3.1-flash:free --wait
|
|
484
487
|
bench onboard a/one:free b/two:free --endpoint openrouter
|
|
488
|
+
bench onboard command-a-plus-05-2026 --endpoint cohere --quick --ad-hoc # a trial key
|
|
485
489
|
bench onboard status | stop | clear""",
|
|
486
490
|
"collection": """examples:
|
|
487
491
|
bench collection save reviewers --members a,b,c --description "adversarial review" --tags review
|
|
@@ -590,6 +594,10 @@ def parser() -> argparse.ArgumentParser:
|
|
|
590
594
|
s.add_argument("served", nargs="+", help="served names, as `bench candidates` lists them")
|
|
591
595
|
s.add_argument("--endpoint", help="the endpoint they are on; the default if omitted")
|
|
592
596
|
s.add_argument("--wait", action="store_true", help="stay until the queue finishes")
|
|
597
|
+
s.add_argument("--quick", action="store_true",
|
|
598
|
+
help="probe only; the scheduled suite passes fill the suite in later")
|
|
599
|
+
s.add_argument("--ad-hoc", action="store_true",
|
|
600
|
+
help="with --quick: the bench never calls it on its own (metered, capped or trial keys)")
|
|
593
601
|
|
|
594
602
|
s = cmd("dismiss", cmd_dismiss, "stop offering served models as candidates")
|
|
595
603
|
s.add_argument("served", nargs="+"), s.add_argument("--endpoint"), s.add_argument("--undo", action="store_true")
|