@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.
- package/AGENTS.md +152 -147
- package/BENCHMARKS.md +262 -262
- package/GATEWAY.md +415 -272
- package/NOTICE +28 -28
- package/PRIVACY.md +46 -46
- package/README.md +408 -314
- package/SECURITY.md +83 -83
- package/TERMS.md +22 -22
- package/USAGE.md +186 -163
- package/VERSION +1 -1
- package/bin/benchwarmers.js +16 -74
- package/cli.py +674 -0
- package/examples/config/gateway.json +5 -5
- package/examples/config/model-roles.json +11 -11
- package/gateway.py +939 -755
- package/launcher.py +120 -0
- package/mcp_server.py +266 -266
- package/metrics/alerts.py +116 -116
- package/metrics/aliases.py +41 -41
- package/metrics/bench_collections.py +141 -141
- package/metrics/catalog.py +304 -295
- package/metrics/client.py +804 -394
- package/metrics/cliproxy.py +191 -191
- package/metrics/connect.py +691 -691
- package/metrics/cost.py +171 -152
- package/metrics/decide.py +227 -227
- package/metrics/discover.py +318 -318
- package/metrics/endpoints.py +436 -436
- package/metrics/formats.py +1021 -0
- package/metrics/guard.py +647 -592
- package/metrics/learn.py +234 -221
- package/metrics/ledger.py +148 -0
- package/metrics/litellm_hub.py +206 -206
- package/metrics/model_roles.py +94 -84
- package/metrics/offload.py +166 -166
- package/metrics/owui.py +205 -205
- package/metrics/pacing.py +248 -0
- package/metrics/paths.py +60 -60
- package/metrics/quota.py +383 -0
- package/metrics/route.py +581 -579
- package/metrics/scheduler.py +275 -275
- package/metrics/sources.py +167 -167
- package/metrics/wrong.py +144 -144
- package/package.json +69 -65
- package/perf/README.md +89 -89
- package/perf/run.py +284 -261
- package/scripts/auto_retire.py +367 -367
- package/scripts/check.py +113 -106
- package/scripts/check_deprecations.py +378 -378
- package/scripts/check_themes.py +103 -103
- package/scripts/init.py +124 -124
- package/scripts/list_served.py +78 -78
- package/scripts/notice.py +78 -78
- package/scripts/onboard.py +410 -331
- package/scripts/provider_tags.py +129 -129
- package/scripts/reprefix.py +136 -136
- package/scripts/sync_config.py +105 -105
- package/scripts/test_auth.py +255 -255
- package/scripts/test_cli.py +189 -0
- package/scripts/test_client.py +277 -233
- package/scripts/test_connect.py +222 -222
- package/scripts/test_first_run.py +180 -180
- package/scripts/test_formats.py +549 -0
- package/scripts/test_hubs.py +328 -328
- package/scripts/test_launcher.py +67 -0
- package/scripts/test_llm_log.py +85 -81
- package/scripts/test_mcp.py +178 -178
- package/scripts/test_merged.py +215 -214
- package/scripts/test_no_owui.py +291 -291
- package/scripts/test_owui.py +90 -90
- package/scripts/test_quota.py +334 -0
- package/scripts/test_serve_edit.py +453 -438
- package/scripts/test_server_json.py +79 -0
- package/scripts/test_verdict.py +102 -98
- package/scripts/test_vision.py +231 -228
- package/serve.py +3972 -3764
- package/suite/mine.py +154 -154
- package/suite/pace.py +145 -145
- package/suite/run.py +1046 -1042
- package/suite/seed.py +421 -421
- package/suite/tasks/claim-alias-unbound/fixtures/onboard.py +286 -286
- package/suite/tasks/claim-error-text-string/fixtures/decide.py +187 -187
- package/suite/tasks/claim-list-body/fixtures/gateway.py +235 -235
- package/suite/tasks/claim-member-nominated-twice/fixtures/gateway.py +284 -284
- package/suite/tasks/claim-nominee-uses-slot/fixtures/gateway.py +284 -284
- package/suite/tasks/claim-silent-exit/fixtures/gateway.py +235 -235
- package/suite/tasks/claim-sse-hang/fixtures/gateway.py +235 -235
- package/suite/tasks/claim-unknown-nominee-crash/fixtures/gateway.py +284 -284
- package/suite/tasks/rebench-matchms-809-bug/task.json +23 -23
- package/suite/tasks/rebench-matchms-809-control/task.json +27 -27
- package/suite/tasks/rebench-more-itertools-1028-bug/task.json +23 -23
- package/suite/tasks/rebench-more-itertools-1028-control/task.json +27 -27
- package/suite/tasks/rebench-narwhals-2864-bug/task.json +23 -23
- package/suite/tasks/rebench-narwhals-2864-control/task.json +27 -27
- package/suite/tasks/rebench-quantecon-py-769-bug/task.json +23 -23
- package/suite/tasks/rebench-quantecon-py-769-control/task.json +27 -27
- package/suite/tasks/rebench-scrapy-6867-bug/task.json +23 -23
- package/suite/tasks/rebench-scrapy-6867-control/task.json +27 -27
- package/ui/bench.css +430 -425
- package/ui/bench.js +1757 -1576
- package/ui/themes/dark.css +35 -35
- package/ui/themes/hardwood.css +52 -52
- package/ui/themes/rainforest.css +32 -32
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
"""The bench CLI (cli.py) against the real dashboard handler. No real files.
|
|
2
|
+
|
|
3
|
+
python scripts/test_cli.py
|
|
4
|
+
|
|
5
|
+
Read commands, write commands on a temp collections file and key store,
|
|
6
|
+
asking a collection (the panel stubbed, as in test_auth), a prompt built from
|
|
7
|
+
a file, the exit codes, and the admin token: the CLI sends it as a bearer,
|
|
8
|
+
the dashboard takes it and nothing else, and a gateway key never opens a
|
|
9
|
+
dashboard route.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
import contextlib
|
|
15
|
+
import io
|
|
16
|
+
import json
|
|
17
|
+
import os
|
|
18
|
+
import pathlib
|
|
19
|
+
import sys
|
|
20
|
+
import tempfile
|
|
21
|
+
import threading
|
|
22
|
+
from http.server import ThreadingHTTPServer
|
|
23
|
+
|
|
24
|
+
ROOT = pathlib.Path(__file__).resolve().parent.parent
|
|
25
|
+
sys.path.insert(0, str(ROOT))
|
|
26
|
+
import serve # noqa: E402
|
|
27
|
+
import gateway # noqa: E402
|
|
28
|
+
import guard # noqa: E402
|
|
29
|
+
import cli # noqa: E402
|
|
30
|
+
|
|
31
|
+
TOKEN = "test-admin-token-0123456789"
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def bench(*argv: str, stdin: str = "") -> tuple[int, str, str]:
|
|
35
|
+
"""(exit code, stdout, stderr) of one `bench` command, in process."""
|
|
36
|
+
out, err = io.StringIO(), io.StringIO()
|
|
37
|
+
real_in = sys.stdin
|
|
38
|
+
sys.stdin = io.StringIO(stdin)
|
|
39
|
+
try:
|
|
40
|
+
with contextlib.redirect_stdout(out), contextlib.redirect_stderr(err):
|
|
41
|
+
try:
|
|
42
|
+
code = cli.main(list(argv))
|
|
43
|
+
except SystemExit as e: # argparse's usage errors
|
|
44
|
+
code = e.code if isinstance(e.code, int) else 2
|
|
45
|
+
finally:
|
|
46
|
+
sys.stdin = real_in
|
|
47
|
+
return code, out.getvalue(), err.getvalue()
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def ok(*argv: str, stdin: str = "") -> str:
|
|
51
|
+
code, out, err = bench(*argv, stdin=stdin)
|
|
52
|
+
assert code == 0, (argv, code, out, err)
|
|
53
|
+
return out
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def main() -> int:
|
|
57
|
+
server = ThreadingHTTPServer(("127.0.0.1", 0), serve.Handler)
|
|
58
|
+
threading.Thread(target=server.serve_forever, daemon=True).start()
|
|
59
|
+
port = server.server_port
|
|
60
|
+
env_keys = ("BENCHWARMERS_ADMIN_TOKEN", "BENCHWARMERS_REQUIRE_KEYS", "BENCHWARMERS_KEY", "BENCHWARMERS_URL")
|
|
61
|
+
saved_env = {k: os.environ.pop(k, None) for k in env_keys}
|
|
62
|
+
saved = (guard.KEYS, serve.CHANGES_PATH, serve.PORT, serve.bench_collections.FILE, gateway.run_collection)
|
|
63
|
+
asked, real_env = [], cli.env
|
|
64
|
+
with tempfile.TemporaryDirectory() as tmp:
|
|
65
|
+
t = pathlib.Path(tmp)
|
|
66
|
+
try:
|
|
67
|
+
guard.KEYS, serve.CHANGES_PATH, serve.PORT = t / "gateway-keys.json", t / "changes.jsonl", port
|
|
68
|
+
serve.bench_collections.FILE = t / "collections.json"
|
|
69
|
+
serve.bench_collections.FILE.write_text(json.dumps({"core": {"members": [], "description": "x"}}),
|
|
70
|
+
encoding="utf-8")
|
|
71
|
+
gateway.run_collection = lambda name, messages, nominated, reply, who: (
|
|
72
|
+
asked.append((name, messages, nominated, reply)) or "## member — m\nthe answer", {"seats": []})
|
|
73
|
+
os.environ["BENCHWARMERS_URL"] = f"http://127.0.0.1:{port}/v1" # a /v1 on the end is fine
|
|
74
|
+
|
|
75
|
+
# Reading.
|
|
76
|
+
assert "Benchwarmers" in ok("status")
|
|
77
|
+
status = json.loads(ok("status", "--json"))
|
|
78
|
+
assert status["gateway"].endswith(f":{port}/v1") and status["collections"] == 1, status
|
|
79
|
+
assert set(status["scheduler_jobs"]) and "auth" in status, status
|
|
80
|
+
assert "core" in ok("collections")
|
|
81
|
+
json.loads(ok("models", "--json"))
|
|
82
|
+
json.loads(ok("endpoints", "--json"))
|
|
83
|
+
assert "no gateway keys" in ok("keys")
|
|
84
|
+
|
|
85
|
+
# Collections: save what is given, keep the rest, add and remove one at a time.
|
|
86
|
+
# Members must be chat models on the bench: three from this install's (or the fixtures').
|
|
87
|
+
chat = [m["alias"] for m in json.loads(ok("models", "--json"))["models"] if m["kind"] == "chat"]
|
|
88
|
+
assert len(chat) >= 3, chat
|
|
89
|
+
a, b, c = chat[:3]
|
|
90
|
+
ok("collection", "save", "bench/pair", "--members", f"{a},{b}", "--description", "two",
|
|
91
|
+
"--tags", "fast")
|
|
92
|
+
show = json.loads(ok("collection", "show", "pair", "--json"))
|
|
93
|
+
assert show["members"] == [a, b] and show["description"] == "two", show
|
|
94
|
+
data = json.loads(serve.bench_collections.FILE.read_text(encoding="utf-8"))
|
|
95
|
+
assert data["pair"]["members"] == [a, b] and data["pair"]["tags"] == ["fast"], data
|
|
96
|
+
ok("collection", "save", "pair", "--add", c, "--remove", a)
|
|
97
|
+
data = json.loads(serve.bench_collections.FILE.read_text(encoding="utf-8"))
|
|
98
|
+
assert data["pair"]["members"] == [b, c] and data["pair"]["description"] == "two", data
|
|
99
|
+
# Deleting asks; with no terminal to ask in, only --yes does it.
|
|
100
|
+
code, _, err = bench("collection", "rm", "pair")
|
|
101
|
+
assert code == 2 and "--yes" in err, (code, err)
|
|
102
|
+
assert "pair" in json.loads(serve.bench_collections.FILE.read_text(encoding="utf-8"))
|
|
103
|
+
ok("collection", "rm", "pair", "--yes")
|
|
104
|
+
assert "pair" not in json.loads(serve.bench_collections.FILE.read_text(encoding="utf-8"))
|
|
105
|
+
code, _, err = bench("collection", "show", "pair")
|
|
106
|
+
assert code == 1 and "no collection" in err, (code, err)
|
|
107
|
+
|
|
108
|
+
# Gateway keys: printed once, listed by name and hint only, revoked.
|
|
109
|
+
code, out, err = bench("key", "create", "cli-test", "--collections", "core", "--rpm", "3")
|
|
110
|
+
key = out.strip()
|
|
111
|
+
assert code == 0 and key.startswith(guard.PREFIX) and "shown this once" in err, (code, out, err)
|
|
112
|
+
listed = ok("keys", "--json")
|
|
113
|
+
assert "cli-test" in listed and key not in listed and "sha256" not in listed, listed
|
|
114
|
+
assert bench("key", "revoke", "cli-test")[0] == 2 # asks first
|
|
115
|
+
ok("key", "revoke", "cli-test", "-y")
|
|
116
|
+
|
|
117
|
+
# --json and --url before the command or after it; --url over BENCHWARMERS_URL.
|
|
118
|
+
assert json.loads(ok("--json", "status"))["ok"] and json.loads(ok("status", "--json"))["ok"]
|
|
119
|
+
url = os.environ["BENCHWARMERS_URL"]
|
|
120
|
+
os.environ["BENCHWARMERS_URL"] = "http://127.0.0.1:9" # nothing there
|
|
121
|
+
assert bench("status")[0] == 3
|
|
122
|
+
for argv in (("--url", url, "status", "--json"), ("status", "--url", url, "--json")):
|
|
123
|
+
assert json.loads(ok(*argv))["gateway"].endswith(f":{port}/v1"), argv
|
|
124
|
+
os.environ["BENCHWARMERS_URL"] = url
|
|
125
|
+
|
|
126
|
+
# A route this server lacks (an older server): said so, GET (the page) or POST (a 404).
|
|
127
|
+
for method in ("GET", "POST"):
|
|
128
|
+
try:
|
|
129
|
+
cli.call(method, "/api/not-yet", {} if method == "POST" else None)
|
|
130
|
+
raise AssertionError(f"{method} of a missing route passed")
|
|
131
|
+
except cli.Failed as e:
|
|
132
|
+
assert "older than this bench" in str(e), (method, str(e))
|
|
133
|
+
|
|
134
|
+
# Asking: the prompt from words, a file and stdin; the options on the wire.
|
|
135
|
+
f = t / "diff.txt"
|
|
136
|
+
f.write_text("- old\n+ new\n", encoding="utf-8")
|
|
137
|
+
out = ok("ask", "core", "review", "this", "-f", str(f), "--merged", "--with", "x1")
|
|
138
|
+
assert "the answer" in out, out
|
|
139
|
+
name, messages, nominated, reply = asked[-1]
|
|
140
|
+
assert name == "core" and reply == "merged" and nominated == ["x1"], asked[-1]
|
|
141
|
+
assert "review this" in messages[-1]["content"] and "+ new" in messages[-1]["content"], messages
|
|
142
|
+
ok("ask", "bench/core", "-", "--system", "be brief", stdin="from stdin")
|
|
143
|
+
assert asked[-1][1][0] == {"role": "system", "content": "be brief"}, asked[-1]
|
|
144
|
+
assert asked[-1][1][-1]["content"] == "from stdin", asked[-1]
|
|
145
|
+
code, _, err = bench("ask", "core")
|
|
146
|
+
assert code == 2 and "nothing to ask" in err, (code, err)
|
|
147
|
+
code, _, err = bench("verdict", "nope.member1", "--useful")
|
|
148
|
+
assert code == 1 and "nope.member1" in err, (code, err)
|
|
149
|
+
|
|
150
|
+
# The admin token: the server has one; what the CLI sends decides. The
|
|
151
|
+
# server reads os.environ, the CLI its own settings (cli.env), here apart.
|
|
152
|
+
os.environ["BENCHWARMERS_ADMIN_TOKEN"] = TOKEN
|
|
153
|
+
gw_key = guard.make_key("agent")
|
|
154
|
+
mine = {"BENCHWARMERS_URL": os.environ["BENCHWARMERS_URL"]}
|
|
155
|
+
cli.env = lambda name: mine.get(name, "")
|
|
156
|
+
code, _, err = bench("status") # none sent
|
|
157
|
+
assert code == 1 and "BENCHWARMERS_ADMIN_TOKEN" in err, (code, err)
|
|
158
|
+
mine["BENCHWARMERS_ADMIN_TOKEN"] = gw_key # a gateway key is not the admin token
|
|
159
|
+
code, _, err = bench("status")
|
|
160
|
+
assert code == 1 and "not the admin token" in err, (code, err)
|
|
161
|
+
mine["BENCHWARMERS_ADMIN_TOKEN"] = TOKEN
|
|
162
|
+
assert "Benchwarmers" in ok("status")
|
|
163
|
+
assert "admin token set" in ok("status")
|
|
164
|
+
code, _, err = bench("collections") # the gateway's routes take a key
|
|
165
|
+
assert code == 1 and "BENCHWARMERS_KEY" in err, (code, err)
|
|
166
|
+
mine["BENCHWARMERS_KEY"] = gw_key
|
|
167
|
+
assert "core" in ok("collections")
|
|
168
|
+
cli.env = real_env
|
|
169
|
+
del os.environ["BENCHWARMERS_ADMIN_TOKEN"]
|
|
170
|
+
|
|
171
|
+
# No server: exit 3, saying where it looked.
|
|
172
|
+
server.shutdown()
|
|
173
|
+
server.server_close()
|
|
174
|
+
code, _, err = bench("status")
|
|
175
|
+
assert code == 3 and "not running" in err and str(port) in err, (code, err)
|
|
176
|
+
finally:
|
|
177
|
+
(guard.KEYS, serve.CHANGES_PATH, serve.PORT, serve.bench_collections.FILE,
|
|
178
|
+
gateway.run_collection) = saved
|
|
179
|
+
cli.env = real_env
|
|
180
|
+
for k, v in saved_env.items():
|
|
181
|
+
os.environ.pop(k, None)
|
|
182
|
+
if v is not None:
|
|
183
|
+
os.environ[k] = v
|
|
184
|
+
print("ok")
|
|
185
|
+
return 0
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
if __name__ == "__main__":
|
|
189
|
+
sys.exit(main())
|