@wendellresearch-ai/benchwarmers 0.2.0 → 0.2.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 +147 -147
- package/BENCHMARKS.md +262 -262
- package/GATEWAY.md +272 -272
- package/NOTICE +28 -28
- package/PRIVACY.md +46 -46
- package/README.md +368 -314
- package/SECURITY.md +83 -83
- package/TERMS.md +22 -22
- package/USAGE.md +170 -163
- package/VERSION +1 -1
- package/bin/benchwarmers.js +16 -74
- package/cli.py +666 -0
- package/examples/config/gateway.json +5 -5
- package/examples/config/model-roles.json +11 -11
- package/gateway.py +755 -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 +295 -295
- package/metrics/client.py +394 -394
- package/metrics/cliproxy.py +191 -191
- package/metrics/connect.py +691 -691
- package/metrics/cost.py +152 -152
- package/metrics/decide.py +227 -227
- package/metrics/discover.py +318 -318
- package/metrics/endpoints.py +436 -436
- package/metrics/guard.py +617 -592
- package/metrics/learn.py +221 -221
- package/metrics/litellm_hub.py +206 -206
- package/metrics/model_roles.py +84 -84
- package/metrics/offload.py +166 -166
- package/metrics/owui.py +205 -205
- package/metrics/paths.py +60 -60
- package/metrics/route.py +579 -579
- package/metrics/scheduler.py +275 -275
- package/metrics/sources.py +167 -167
- package/metrics/wrong.py +144 -144
- package/package.json +68 -65
- package/perf/README.md +89 -89
- package/perf/run.py +261 -261
- package/scripts/auto_retire.py +367 -367
- package/scripts/check.py +108 -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 +401 -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 +233 -233
- package/scripts/test_connect.py +222 -222
- package/scripts/test_first_run.py +180 -180
- 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 +214 -214
- package/scripts/test_no_owui.py +291 -291
- package/scripts/test_owui.py +90 -90
- package/scripts/test_serve_edit.py +448 -438
- package/scripts/test_verdict.py +98 -98
- package/scripts/test_vision.py +228 -228
- package/serve.py +3904 -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 +1756 -1576
- package/ui/themes/dark.css +35 -35
- package/ui/themes/hardwood.css +52 -52
- package/ui/themes/rainforest.css +32 -32
package/scripts/test_verdict.py
CHANGED
|
@@ -1,98 +1,98 @@
|
|
|
1
|
-
"""Verdicts and the gateway's `bench` field, against a throwaway log. No model or
|
|
2
|
-
endpoint is called and the real offloads.jsonl and runs/gateway/ are never
|
|
3
|
-
written. The collection comes from the data folder (in CI, tests/fixtures/data).
|
|
4
|
-
|
|
5
|
-
python scripts/test_verdict.py
|
|
6
|
-
"""
|
|
7
|
-
|
|
8
|
-
from __future__ import annotations
|
|
9
|
-
|
|
10
|
-
import json
|
|
11
|
-
import pathlib
|
|
12
|
-
import sys
|
|
13
|
-
import tempfile
|
|
14
|
-
import threading
|
|
15
|
-
import urllib.request
|
|
16
|
-
from http.server import ThreadingHTTPServer
|
|
17
|
-
|
|
18
|
-
ROOT = pathlib.Path(__file__).resolve().parent.parent
|
|
19
|
-
sys.path[:0] = [str(ROOT), str(ROOT / "metrics"), str(ROOT / "suite")]
|
|
20
|
-
import gateway # noqa: E402
|
|
21
|
-
import offload # noqa: E402
|
|
22
|
-
import run # noqa: E402
|
|
23
|
-
import serve # noqa: E402
|
|
24
|
-
|
|
25
|
-
tmp = pathlib.Path(tempfile.mkdtemp(prefix="bench-verdict-"))
|
|
26
|
-
offload.LOG = tmp / "offloads.jsonl"
|
|
27
|
-
gateway.LOG_DIR = tmp
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
def verdicts() -> None:
|
|
31
|
-
offload.add("m1", "task", None, "member; ok in 3s", "pX.member1")
|
|
32
|
-
assert not serve.record_verdict("pX.member1", None, "", [], [])["ok"] # no pick
|
|
33
|
-
assert not serve.record_verdict("pX.member1", True, "", "x", [])["ok"] # claims not a list
|
|
34
|
-
assert not serve.record_verdict("nope.member1", True, "", [], [])["ok"] # unknown call
|
|
35
|
-
assert serve.record_verdict("pX.member1", True, " found it ", ["real one", " "], ["wrong one"])["ok"]
|
|
36
|
-
e = offload.entries()[-1]
|
|
37
|
-
assert (e["useful"], e["note"], e["real"], e["false"]) == (True, "found it", ["real one"], ["wrong one"]), e
|
|
38
|
-
n = len(offload.LOG.read_text(encoding="utf-8").splitlines())
|
|
39
|
-
assert serve.record_verdict("pX.member1", True, "found it", ["real one"], ["wrong one"])["ok"]
|
|
40
|
-
assert len(offload.LOG.read_text(encoding="utf-8").splitlines()) == n, "the same verdict again wrote a line"
|
|
41
|
-
# The dashboard form shows note and claims for editing: blank means cleared.
|
|
42
|
-
assert serve.record_verdict("pX.member1", False, "", [], [])["ok"]
|
|
43
|
-
e = offload.entries()[-1]
|
|
44
|
-
assert e["useful"] is False and e["note"] == "" and "real" not in e and "false" not in e, e
|
|
45
|
-
# The CLI: a blank note or claim list keeps the earlier one.
|
|
46
|
-
offload.verdict("pX.member1", False, "", ["cli claim"])
|
|
47
|
-
offload.verdict("pX.member1", True, "")
|
|
48
|
-
assert offload.entries()[-1]["real"] == ["cli claim"], offload.entries()[-1]
|
|
49
|
-
assert len(offload.entries()) == 1 # superseded by call id
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
def bench_field() -> None:
|
|
53
|
-
gateway.pick_shape = lambda text: ("general", "test")
|
|
54
|
-
gateway.ask_seat = lambda alias, system, prompt, messages=None: {
|
|
55
|
-
"status": "ok", "answer": f"answer from {alias}", "error": "", "seconds": 0.1}
|
|
56
|
-
# The explorer seats ask every endpoint's /models; None is its own "cannot
|
|
57
|
-
# be asked, do not filter", so no request leaves the machine.
|
|
58
|
-
run._served_model_names = lambda cache=None: None
|
|
59
|
-
srv = ThreadingHTTPServer(("127.0.0.1", 0), gateway.Handler)
|
|
60
|
-
gateway.PORT = srv.server_address[1] # the Host check expects the port it runs on
|
|
61
|
-
threading.Thread(target=srv.serve_forever, daemon=True).start()
|
|
62
|
-
url = f"http://127.0.0.1:{srv.server_address[1]}/v1/chat/completions"
|
|
63
|
-
|
|
64
|
-
def post(body, **headers):
|
|
65
|
-
req = urllib.request.Request(url, json.dumps(body).encode(),
|
|
66
|
-
{"Content-Type": "application/json", **headers})
|
|
67
|
-
with urllib.request.urlopen(req) as r:
|
|
68
|
-
return r.read().decode()
|
|
69
|
-
|
|
70
|
-
# metrics/guard.py, end to end: a rebinding Host, a page's text/plain
|
|
71
|
-
# form and a foreign Origin are refused before any seat is called.
|
|
72
|
-
for headers, code in (({"Host": "evil.example"}, 403), ({"Content-Type": "text/plain"}, 415),
|
|
73
|
-
({"Origin": "http://evil.example"}, 403)):
|
|
74
|
-
try:
|
|
75
|
-
post({}, **headers)
|
|
76
|
-
raise AssertionError(f"{headers} was served")
|
|
77
|
-
except urllib.error.HTTPError as exc:
|
|
78
|
-
assert exc.code == code, (headers, exc.code)
|
|
79
|
-
|
|
80
|
-
name = next(iter(gateway.bench_collections.load()))
|
|
81
|
-
msg = {"model": f"bench/{name}", "messages": [{"role": "user", "content": "say hi"}]}
|
|
82
|
-
r = json.loads(post(msg))
|
|
83
|
-
b = r["bench"]
|
|
84
|
-
assert b["collection"] == f"bench/{name}" and b["seats"], b
|
|
85
|
-
assert all(s["answer"] == f"answer from {s['alias']}" and s["call"].startswith(b["panel"] + ".")
|
|
86
|
-
for s in b["seats"])
|
|
87
|
-
assert all(s["call"] in r["choices"][0]["message"]["content"] for s in b["seats"])
|
|
88
|
-
assert "prompt" not in b and "system" not in b
|
|
89
|
-
chunks = [json.loads(line[6:]) for line in post({**msg, "stream": True}).splitlines()
|
|
90
|
-
if line.startswith("data: {")]
|
|
91
|
-
assert "bench" in chunks[0] and "bench" not in chunks[1], chunks
|
|
92
|
-
srv.shutdown()
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
if __name__ == "__main__":
|
|
96
|
-
verdicts()
|
|
97
|
-
bench_field()
|
|
98
|
-
print("verdict and bench-field checks pass")
|
|
1
|
+
"""Verdicts and the gateway's `bench` field, against a throwaway log. No model or
|
|
2
|
+
endpoint is called and the real offloads.jsonl and runs/gateway/ are never
|
|
3
|
+
written. The collection comes from the data folder (in CI, tests/fixtures/data).
|
|
4
|
+
|
|
5
|
+
python scripts/test_verdict.py
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import json
|
|
11
|
+
import pathlib
|
|
12
|
+
import sys
|
|
13
|
+
import tempfile
|
|
14
|
+
import threading
|
|
15
|
+
import urllib.request
|
|
16
|
+
from http.server import ThreadingHTTPServer
|
|
17
|
+
|
|
18
|
+
ROOT = pathlib.Path(__file__).resolve().parent.parent
|
|
19
|
+
sys.path[:0] = [str(ROOT), str(ROOT / "metrics"), str(ROOT / "suite")]
|
|
20
|
+
import gateway # noqa: E402
|
|
21
|
+
import offload # noqa: E402
|
|
22
|
+
import run # noqa: E402
|
|
23
|
+
import serve # noqa: E402
|
|
24
|
+
|
|
25
|
+
tmp = pathlib.Path(tempfile.mkdtemp(prefix="bench-verdict-"))
|
|
26
|
+
offload.LOG = tmp / "offloads.jsonl"
|
|
27
|
+
gateway.LOG_DIR = tmp
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def verdicts() -> None:
|
|
31
|
+
offload.add("m1", "task", None, "member; ok in 3s", "pX.member1")
|
|
32
|
+
assert not serve.record_verdict("pX.member1", None, "", [], [])["ok"] # no pick
|
|
33
|
+
assert not serve.record_verdict("pX.member1", True, "", "x", [])["ok"] # claims not a list
|
|
34
|
+
assert not serve.record_verdict("nope.member1", True, "", [], [])["ok"] # unknown call
|
|
35
|
+
assert serve.record_verdict("pX.member1", True, " found it ", ["real one", " "], ["wrong one"])["ok"]
|
|
36
|
+
e = offload.entries()[-1]
|
|
37
|
+
assert (e["useful"], e["note"], e["real"], e["false"]) == (True, "found it", ["real one"], ["wrong one"]), e
|
|
38
|
+
n = len(offload.LOG.read_text(encoding="utf-8").splitlines())
|
|
39
|
+
assert serve.record_verdict("pX.member1", True, "found it", ["real one"], ["wrong one"])["ok"]
|
|
40
|
+
assert len(offload.LOG.read_text(encoding="utf-8").splitlines()) == n, "the same verdict again wrote a line"
|
|
41
|
+
# The dashboard form shows note and claims for editing: blank means cleared.
|
|
42
|
+
assert serve.record_verdict("pX.member1", False, "", [], [])["ok"]
|
|
43
|
+
e = offload.entries()[-1]
|
|
44
|
+
assert e["useful"] is False and e["note"] == "" and "real" not in e and "false" not in e, e
|
|
45
|
+
# The CLI: a blank note or claim list keeps the earlier one.
|
|
46
|
+
offload.verdict("pX.member1", False, "", ["cli claim"])
|
|
47
|
+
offload.verdict("pX.member1", True, "")
|
|
48
|
+
assert offload.entries()[-1]["real"] == ["cli claim"], offload.entries()[-1]
|
|
49
|
+
assert len(offload.entries()) == 1 # superseded by call id
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def bench_field() -> None:
|
|
53
|
+
gateway.pick_shape = lambda text: ("general", "test")
|
|
54
|
+
gateway.ask_seat = lambda alias, system, prompt, messages=None: {
|
|
55
|
+
"status": "ok", "answer": f"answer from {alias}", "error": "", "seconds": 0.1}
|
|
56
|
+
# The explorer seats ask every endpoint's /models; None is its own "cannot
|
|
57
|
+
# be asked, do not filter", so no request leaves the machine.
|
|
58
|
+
run._served_model_names = lambda cache=None: None
|
|
59
|
+
srv = ThreadingHTTPServer(("127.0.0.1", 0), gateway.Handler)
|
|
60
|
+
gateway.PORT = srv.server_address[1] # the Host check expects the port it runs on
|
|
61
|
+
threading.Thread(target=srv.serve_forever, daemon=True).start()
|
|
62
|
+
url = f"http://127.0.0.1:{srv.server_address[1]}/v1/chat/completions"
|
|
63
|
+
|
|
64
|
+
def post(body, **headers):
|
|
65
|
+
req = urllib.request.Request(url, json.dumps(body).encode(),
|
|
66
|
+
{"Content-Type": "application/json", **headers})
|
|
67
|
+
with urllib.request.urlopen(req) as r:
|
|
68
|
+
return r.read().decode()
|
|
69
|
+
|
|
70
|
+
# metrics/guard.py, end to end: a rebinding Host, a page's text/plain
|
|
71
|
+
# form and a foreign Origin are refused before any seat is called.
|
|
72
|
+
for headers, code in (({"Host": "evil.example"}, 403), ({"Content-Type": "text/plain"}, 415),
|
|
73
|
+
({"Origin": "http://evil.example"}, 403)):
|
|
74
|
+
try:
|
|
75
|
+
post({}, **headers)
|
|
76
|
+
raise AssertionError(f"{headers} was served")
|
|
77
|
+
except urllib.error.HTTPError as exc:
|
|
78
|
+
assert exc.code == code, (headers, exc.code)
|
|
79
|
+
|
|
80
|
+
name = next(iter(gateway.bench_collections.load()))
|
|
81
|
+
msg = {"model": f"bench/{name}", "messages": [{"role": "user", "content": "say hi"}]}
|
|
82
|
+
r = json.loads(post(msg))
|
|
83
|
+
b = r["bench"]
|
|
84
|
+
assert b["collection"] == f"bench/{name}" and b["seats"], b
|
|
85
|
+
assert all(s["answer"] == f"answer from {s['alias']}" and s["call"].startswith(b["panel"] + ".")
|
|
86
|
+
for s in b["seats"])
|
|
87
|
+
assert all(s["call"] in r["choices"][0]["message"]["content"] for s in b["seats"])
|
|
88
|
+
assert "prompt" not in b and "system" not in b
|
|
89
|
+
chunks = [json.loads(line[6:]) for line in post({**msg, "stream": True}).splitlines()
|
|
90
|
+
if line.startswith("data: {")]
|
|
91
|
+
assert "bench" in chunks[0] and "bench" not in chunks[1], chunks
|
|
92
|
+
srv.shutdown()
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
if __name__ == "__main__":
|
|
96
|
+
verdicts()
|
|
97
|
+
bench_field()
|
|
98
|
+
print("verdict and bench-field checks pass")
|