@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.
Files changed (96) hide show
  1. package/AGENTS.md +147 -147
  2. package/BENCHMARKS.md +262 -262
  3. package/GATEWAY.md +272 -272
  4. package/NOTICE +28 -28
  5. package/PRIVACY.md +46 -46
  6. package/README.md +368 -314
  7. package/SECURITY.md +83 -83
  8. package/TERMS.md +22 -22
  9. package/USAGE.md +170 -163
  10. package/VERSION +1 -1
  11. package/bin/benchwarmers.js +16 -74
  12. package/cli.py +666 -0
  13. package/examples/config/gateway.json +5 -5
  14. package/examples/config/model-roles.json +11 -11
  15. package/gateway.py +755 -755
  16. package/launcher.py +120 -0
  17. package/mcp_server.py +266 -266
  18. package/metrics/alerts.py +116 -116
  19. package/metrics/aliases.py +41 -41
  20. package/metrics/bench_collections.py +141 -141
  21. package/metrics/catalog.py +295 -295
  22. package/metrics/client.py +394 -394
  23. package/metrics/cliproxy.py +191 -191
  24. package/metrics/connect.py +691 -691
  25. package/metrics/cost.py +152 -152
  26. package/metrics/decide.py +227 -227
  27. package/metrics/discover.py +318 -318
  28. package/metrics/endpoints.py +436 -436
  29. package/metrics/guard.py +617 -592
  30. package/metrics/learn.py +221 -221
  31. package/metrics/litellm_hub.py +206 -206
  32. package/metrics/model_roles.py +84 -84
  33. package/metrics/offload.py +166 -166
  34. package/metrics/owui.py +205 -205
  35. package/metrics/paths.py +60 -60
  36. package/metrics/route.py +579 -579
  37. package/metrics/scheduler.py +275 -275
  38. package/metrics/sources.py +167 -167
  39. package/metrics/wrong.py +144 -144
  40. package/package.json +68 -65
  41. package/perf/README.md +89 -89
  42. package/perf/run.py +261 -261
  43. package/scripts/auto_retire.py +367 -367
  44. package/scripts/check.py +108 -106
  45. package/scripts/check_deprecations.py +378 -378
  46. package/scripts/check_themes.py +103 -103
  47. package/scripts/init.py +124 -124
  48. package/scripts/list_served.py +78 -78
  49. package/scripts/notice.py +78 -78
  50. package/scripts/onboard.py +401 -331
  51. package/scripts/provider_tags.py +129 -129
  52. package/scripts/reprefix.py +136 -136
  53. package/scripts/sync_config.py +105 -105
  54. package/scripts/test_auth.py +255 -255
  55. package/scripts/test_cli.py +189 -0
  56. package/scripts/test_client.py +233 -233
  57. package/scripts/test_connect.py +222 -222
  58. package/scripts/test_first_run.py +180 -180
  59. package/scripts/test_hubs.py +328 -328
  60. package/scripts/test_launcher.py +67 -0
  61. package/scripts/test_llm_log.py +85 -81
  62. package/scripts/test_mcp.py +178 -178
  63. package/scripts/test_merged.py +214 -214
  64. package/scripts/test_no_owui.py +291 -291
  65. package/scripts/test_owui.py +90 -90
  66. package/scripts/test_serve_edit.py +448 -438
  67. package/scripts/test_verdict.py +98 -98
  68. package/scripts/test_vision.py +228 -228
  69. package/serve.py +3904 -3764
  70. package/suite/mine.py +154 -154
  71. package/suite/pace.py +145 -145
  72. package/suite/run.py +1046 -1042
  73. package/suite/seed.py +421 -421
  74. package/suite/tasks/claim-alias-unbound/fixtures/onboard.py +286 -286
  75. package/suite/tasks/claim-error-text-string/fixtures/decide.py +187 -187
  76. package/suite/tasks/claim-list-body/fixtures/gateway.py +235 -235
  77. package/suite/tasks/claim-member-nominated-twice/fixtures/gateway.py +284 -284
  78. package/suite/tasks/claim-nominee-uses-slot/fixtures/gateway.py +284 -284
  79. package/suite/tasks/claim-silent-exit/fixtures/gateway.py +235 -235
  80. package/suite/tasks/claim-sse-hang/fixtures/gateway.py +235 -235
  81. package/suite/tasks/claim-unknown-nominee-crash/fixtures/gateway.py +284 -284
  82. package/suite/tasks/rebench-matchms-809-bug/task.json +23 -23
  83. package/suite/tasks/rebench-matchms-809-control/task.json +27 -27
  84. package/suite/tasks/rebench-more-itertools-1028-bug/task.json +23 -23
  85. package/suite/tasks/rebench-more-itertools-1028-control/task.json +27 -27
  86. package/suite/tasks/rebench-narwhals-2864-bug/task.json +23 -23
  87. package/suite/tasks/rebench-narwhals-2864-control/task.json +27 -27
  88. package/suite/tasks/rebench-quantecon-py-769-bug/task.json +23 -23
  89. package/suite/tasks/rebench-quantecon-py-769-control/task.json +27 -27
  90. package/suite/tasks/rebench-scrapy-6867-bug/task.json +23 -23
  91. package/suite/tasks/rebench-scrapy-6867-control/task.json +27 -27
  92. package/ui/bench.css +430 -425
  93. package/ui/bench.js +1756 -1576
  94. package/ui/themes/dark.css +35 -35
  95. package/ui/themes/hardwood.css +52 -52
  96. package/ui/themes/rainforest.css +32 -32
@@ -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")