@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
package/scripts/test_no_owui.py
CHANGED
|
@@ -1,291 +1,291 @@
|
|
|
1
|
-
"""Benchwarmers without Open WebUI, and the owner's install with it, against a fake server.
|
|
2
|
-
|
|
3
|
-
python scripts/test_no_owui.py
|
|
4
|
-
|
|
5
|
-
Two installs in one temp data folder (BENCHWARMERS_HOME and LLM_USER_PATH are
|
|
6
|
-
set before anything is imported, so no real file is read or written):
|
|
7
|
-
|
|
8
|
-
1. A stranger whose only endpoint is an OpenRouter-like aggregator, the
|
|
9
|
-
default. Registry, connections, onboarding candidates, the served check
|
|
10
|
-
and auto-retire's plan all work, and no Open WebUI path is ever requested:
|
|
11
|
-
owui.py, provider_tags.py and reprefix.py refuse or skip cleanly.
|
|
12
|
-
2. The owner's shape: endpoints.json with no `kind` anywhere, an `openwebui`
|
|
13
|
-
default at a bare /api root with a `{path}` decisions passthrough, an
|
|
14
|
-
OpenRouter endpoint and a local Ollama. Every Open WebUI path still runs.
|
|
15
|
-
"""
|
|
16
|
-
|
|
17
|
-
from __future__ import annotations
|
|
18
|
-
|
|
19
|
-
import contextlib
|
|
20
|
-
import io
|
|
21
|
-
import json
|
|
22
|
-
import os
|
|
23
|
-
import pathlib
|
|
24
|
-
import shutil
|
|
25
|
-
import sys
|
|
26
|
-
import tempfile
|
|
27
|
-
import threading
|
|
28
|
-
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
|
|
29
|
-
|
|
30
|
-
ROOT = pathlib.Path(__file__).resolve().parent.parent
|
|
31
|
-
TMP = pathlib.Path(tempfile.mkdtemp(prefix="bench-no-owui-"))
|
|
32
|
-
os.environ["BENCHWARMERS_HOME"] = str(TMP)
|
|
33
|
-
os.environ["LLM_USER_PATH"] = str(TMP / "llm")
|
|
34
|
-
(TMP / "config").mkdir()
|
|
35
|
-
(TMP / "llm").mkdir()
|
|
36
|
-
sys.path[:0] = [str(ROOT), str(ROOT / "metrics"), str(ROOT / "suite"), str(ROOT / "scripts")]
|
|
37
|
-
|
|
38
|
-
SEEN: list[str] = []
|
|
39
|
-
LISTS = {
|
|
40
|
-
"/v1/models": ["vendor/a:free", "vendor/b:free", "vendor/c", "vendor/d", "vendor/e", "vendor/f"],
|
|
41
|
-
"/or/api/v1/models": ["vendor/x:free", "vendor/y"],
|
|
42
|
-
"/ol/v1/models": ["llama-local"],
|
|
43
|
-
}
|
|
44
|
-
OWUI_MODELS = [{"id": "fast-1", "urlIdx": 0}, {"id": "llm7.codestral", "urlIdx": 1},
|
|
45
|
-
{"id": "fresh-2", "urlIdx": 0}, {"id": "mercury-decide", "urlIdx": 1}]
|
|
46
|
-
OWUI_CONFIG = {"OPENAI_API_BASE_URLS": ["https://api.groq.com/openai/v1", "https://api.llm7.io/v1"],
|
|
47
|
-
"OPENAI_API_KEYS": ["never-read-1", "never-read-2"]}
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
class Fake(BaseHTTPRequestHandler):
|
|
51
|
-
def do_GET(self): # noqa: N802
|
|
52
|
-
SEEN.append(self.path)
|
|
53
|
-
if self.path in LISTS:
|
|
54
|
-
return self._send({"data": [{"id": i} for i in LISTS[self.path]]})
|
|
55
|
-
if self.path == "/api/models":
|
|
56
|
-
return self._send({"data": OWUI_MODELS})
|
|
57
|
-
if self.path == "/openai/config":
|
|
58
|
-
return self._send(OWUI_CONFIG)
|
|
59
|
-
self._send({}, 404)
|
|
60
|
-
|
|
61
|
-
def do_POST(self): # noqa: N802
|
|
62
|
-
SEEN.append(self.path)
|
|
63
|
-
self.rfile.read(int(self.headers.get("Content-Length") or 0))
|
|
64
|
-
if self.path == "/openai/decisions": # Open WebUI's passthrough
|
|
65
|
-
return self._send({"answers": {"probe": {"answer": True}}})
|
|
66
|
-
self._send({}, 404)
|
|
67
|
-
|
|
68
|
-
def _send(self, data, status=200):
|
|
69
|
-
body = json.dumps(data).encode()
|
|
70
|
-
self.send_response(status)
|
|
71
|
-
self.send_header("Content-Type", "application/json")
|
|
72
|
-
self.send_header("Content-Length", str(len(body)))
|
|
73
|
-
self.end_headers()
|
|
74
|
-
self.wfile.write(body)
|
|
75
|
-
|
|
76
|
-
def log_message(self, *_a):
|
|
77
|
-
pass
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
def write(name: str, data) -> None:
|
|
81
|
-
path = TMP / "config" / name
|
|
82
|
-
path.write_text(data if isinstance(data, str) else json.dumps(data), encoding="utf-8")
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
def quietly(fn, *args):
|
|
86
|
-
with contextlib.redirect_stdout(io.StringIO()) as out:
|
|
87
|
-
result = fn(*args)
|
|
88
|
-
return result, out.getvalue()
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
def run_main(module, argv: list[str]):
|
|
92
|
-
"""A script's main() as its command line would run it: (exit, printed)."""
|
|
93
|
-
saved, sys.argv = sys.argv, argv
|
|
94
|
-
try:
|
|
95
|
-
return quietly(module.main)
|
|
96
|
-
except SystemExit as exc:
|
|
97
|
-
return exc.code, ""
|
|
98
|
-
finally:
|
|
99
|
-
sys.argv = saved
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
def owui_calls() -> list[str]:
|
|
103
|
-
return [p for p in SEEN if p.startswith(("/api/", "/openai/"))]
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
def stranger(fake: str) -> None:
|
|
107
|
-
write("endpoints.json", {"openrouter": {"base": fake + "/v1", "key": "openrouter", "default": True,
|
|
108
|
-
"kind": "aggregator"}})
|
|
109
|
-
write("alias-history.json", "{}")
|
|
110
|
-
import endpoints
|
|
111
|
-
write("extra-openai-models.yaml", endpoints.yaml_block("a-free", "vendor/a:free")
|
|
112
|
-
+ endpoints.yaml_block("b-free", "vendor/b:free"))
|
|
113
|
-
import route
|
|
114
|
-
import serve
|
|
115
|
-
import check_deprecations
|
|
116
|
-
import auto_retire
|
|
117
|
-
import owui
|
|
118
|
-
import provider_tags
|
|
119
|
-
import reprefix
|
|
120
|
-
|
|
121
|
-
assert (endpoints.kind("openrouter"), endpoints.hub("openrouter")) == ("aggregator", "")
|
|
122
|
-
assert endpoints.find_hub("openwebui") is None and not endpoints.recorded_whole("openrouter")
|
|
123
|
-
assert {r["alias"]: r["endpoint"] for r in endpoints.registry()} == {"a-free": "openrouter", "b-free": "openrouter"}
|
|
124
|
-
# No tags file at all: the endpoint is the connection.
|
|
125
|
-
assert not (TMP / "config" / "provider-tags.json").exists()
|
|
126
|
-
assert route.connection_of("a-free") == route.full_tag_of("b-free") == "openrouter"
|
|
127
|
-
assert serve.tag_for("a-free") == "openrouter"
|
|
128
|
-
|
|
129
|
-
# Onboarding candidates: what it lists minus what is registered; nothing badged new,
|
|
130
|
-
# because the served check records only registered models here.
|
|
131
|
-
c = serve.onboard_candidates()
|
|
132
|
-
assert c["ok"] and not c["errors"], c
|
|
133
|
-
assert sorted(x["served"] for x in c["candidates"]) == ["vendor/c", "vendor/d", "vendor/e", "vendor/f"], c
|
|
134
|
-
assert {x["group"] for x in c["candidates"]} == {"openrouter (direct)"} and not any(x["new"] for x in c["candidates"])
|
|
135
|
-
|
|
136
|
-
# The served check: the registered models only, not the aggregator's catalogue.
|
|
137
|
-
assert check_deprecations.served() == {"vendor/a:free", "vendor/b:free"}
|
|
138
|
-
code, out = run_main(check_deprecations, ["check_deprecations.py", "--dry-run"])
|
|
139
|
-
assert code == 0 and "2 served, 2 registered" in out, (code, out)
|
|
140
|
-
assert not (TMP / "config" / "served-snapshot.json").exists() # a dry run writes nothing
|
|
141
|
-
|
|
142
|
-
# Auto-retire groups by connection without tags: both models on the one
|
|
143
|
-
# connection failing at once is held as the connection, not retired.
|
|
144
|
-
import datetime as dt
|
|
145
|
-
models = {"a-free": "vendor/a:free", "b-free": "vendor/b:free"}
|
|
146
|
-
tags, on_owui = auto_retire.connections(models)
|
|
147
|
-
assert tags == {"vendor/a:free": "openrouter", "vendor/b:free": "openrouter"} and on_owui == set(), tags
|
|
148
|
-
today = dt.date(2026, 10, 21)
|
|
149
|
-
rows = [{"alias": a, "date": today, "answered": False, "failure": "entitlement", "detail": "402"} for a in models]
|
|
150
|
-
p = auto_retire.plan(today, models, set(models.values()), {}, rows, {}, {}, tags, on_owui)
|
|
151
|
-
assert set(p["held"]) == {"openrouter"} and not p["confirm"], p
|
|
152
|
-
gone = auto_retire.plan(today, {"a-free": "vendor/a:free"}, {"x.vendor/a:free"},
|
|
153
|
-
{"vendor/a:free": "2026-10-15"}, [], {}, {}, tags, on_owui)
|
|
154
|
-
assert dict(gone["retire"])["a-free"].startswith("not served by its endpoint"), gone # no prefix guess
|
|
155
|
-
|
|
156
|
-
# Every Open WebUI-only path refuses or skips, and says why.
|
|
157
|
-
try:
|
|
158
|
-
owui.endpoint()
|
|
159
|
-
raise AssertionError("owui found a hub that is not there")
|
|
160
|
-
except ValueError as exc:
|
|
161
|
-
assert "Open WebUI hub" in str(exc)
|
|
162
|
-
r = serve.discover_add("groq", "vendor/z")
|
|
163
|
-
assert not r["ok"] and "Open WebUI hub" in r["errors"][0], r
|
|
164
|
-
_, out = quietly(provider_tags.main)
|
|
165
|
-
assert "no Open WebUI hub" in out and not (TMP / "config" / "provider-tags.json").exists(), out
|
|
166
|
-
code, _ = run_main(reprefix, ["reprefix.py", "groq", "--prefix", "g."])
|
|
167
|
-
assert "Open WebUI" in str(code), code
|
|
168
|
-
|
|
169
|
-
# Adding an endpoint: an explicit kind, a hub only with its type.
|
|
170
|
-
assert not serve.save_endpoint("odd", fake + "/v1", "k", new=True, kind="cloud")["ok"]
|
|
171
|
-
assert not serve.save_endpoint("odd", fake + "/v1", "k", new=True, kind="hub")["ok"]
|
|
172
|
-
# Test (the form's button before Save) answers as Save would and writes nothing, the key included.
|
|
173
|
-
t = serve.save_endpoint("tried", fake + "/v1", "k-tried", new=True, test_only=True)
|
|
174
|
-
assert t["ok"] and "served" in t and "tried" not in endpoints.load() and "tried" not in endpoints.keys(), t
|
|
175
|
-
assert serve.save_endpoint("plain", fake + "/v1", "k", new=True)["ok"]
|
|
176
|
-
assert not serve.save_endpoint("plain", fake + "/v1", "k", new=True, test_only=True)["ok"] # names checked in Test too
|
|
177
|
-
assert serve.save_endpoint("mine", fake + "/v1", "k", new=True, kind="hub", hub="openwebui")["ok"]
|
|
178
|
-
data = endpoints.load()
|
|
179
|
-
assert data["plain"]["kind"] == "provider" and "hub" not in data["plain"], data["plain"]
|
|
180
|
-
assert (data["mine"]["kind"], data["mine"]["hub"]) == ("hub", "openwebui"), data["mine"]
|
|
181
|
-
assert serve.save_endpoint("mine", fake + "/v1", "k2")["ok"] and endpoints.hub("mine") == "openwebui" # Change key keeps it
|
|
182
|
-
rows = {e["name"]: e for e in serve.endpoint_list()["endpoints"]}
|
|
183
|
-
assert (rows["openrouter"]["kind"], rows["mine"]["hub"]) == ("aggregator", "openwebui"), rows
|
|
184
|
-
|
|
185
|
-
assert owui_calls() == [], owui_calls()
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
def owner(fake: str) -> None:
|
|
189
|
-
"""The owner's endpoints.json shape: no `kind` anywhere."""
|
|
190
|
-
write("endpoints.json", {
|
|
191
|
-
"openwebui": {"base": fake + "/api", "key": "openwebui", "default": True,
|
|
192
|
-
"decisions": fake + "/openai/{path}"},
|
|
193
|
-
"openrouter": {"base": fake + "/or/api/v1", "key": "openrouter",
|
|
194
|
-
"headers": {"HTTP-Referer": "https://example.com", "X-Title": "Example"},
|
|
195
|
-
"unlisted_models": ["vendor/decide:free"]},
|
|
196
|
-
"ollama": {"key": "ollama", "connect": {"provider": "ollama", "at": "2026-10-20T22:30:52-05:00"},
|
|
197
|
-
"base": fake + "/ol/v1"}})
|
|
198
|
-
import endpoints
|
|
199
|
-
write("extra-openai-models.yaml", endpoints.yaml_block("fast-1", "fast-1")
|
|
200
|
-
+ endpoints.yaml_block("codestral", "codestral")
|
|
201
|
-
+ endpoints.yaml_block("x-free", "vendor/x:free", "openrouter"))
|
|
202
|
-
write("served-snapshot.json", {"last_seen": {"fast-1": "2026-10-20", "codestral": "2026-10-18"},
|
|
203
|
-
"first_missing": {"codestral": "2026-10-19"}})
|
|
204
|
-
import route
|
|
205
|
-
import serve
|
|
206
|
-
import check_deprecations
|
|
207
|
-
import auto_retire
|
|
208
|
-
import owui
|
|
209
|
-
import provider_tags
|
|
210
|
-
import reprefix
|
|
211
|
-
import decide
|
|
212
|
-
SEEN.clear()
|
|
213
|
-
|
|
214
|
-
# Inferred from his real bases (a LAN address stands in as hub.example)...
|
|
215
|
-
real = {"openwebui": {"base": "http://hub.example:4141/api"}, "openrouter": {"base": "https://openrouter.ai/api/v1"},
|
|
216
|
-
"ollama": {"base": "http://localhost:11434/v1"}}
|
|
217
|
-
kinds = {n: (endpoints.kind(n, real), endpoints.hub(n, real)) for n in real}
|
|
218
|
-
assert kinds == {"openwebui": ("hub", "openwebui"), "openrouter": ("provider", ""), "ollama": ("local", "")}, kinds
|
|
219
|
-
# ...and here, where every base is the fake on 127.0.0.1, the /api root still makes the one hub.
|
|
220
|
-
assert [n for n in endpoints.load() if endpoints.hub(n)] == ["openwebui"]
|
|
221
|
-
assert endpoints.find_hub("openwebui") == "openwebui" and endpoints.recorded_whole("openwebui")
|
|
222
|
-
assert owui.endpoint() == "openwebui" and owui._call("/openai/config") == OWUI_CONFIG
|
|
223
|
-
|
|
224
|
-
# Provider tags: Open WebUI's connections by host, the direct endpoint by name.
|
|
225
|
-
_, out = quietly(provider_tags.main)
|
|
226
|
-
tags = json.loads((TMP / "config" / "provider-tags.json").read_text(encoding="utf-8"))
|
|
227
|
-
assert tags["fast-1"] == "groq" and tags["llm7.codestral"] == "llm7-api" and tags["vendor/x:free"] == "openrouter", tags
|
|
228
|
-
assert "never-read" not in json.dumps(tags)
|
|
229
|
-
assert route.connection_of("fast-1") == "groq" and route.connection_of("x-free") == "openrouter"
|
|
230
|
-
|
|
231
|
-
# The default hub is recorded whole; the direct endpoint, its registered models only.
|
|
232
|
-
assert check_deprecations.served() == {"fast-1", "llm7.codestral", "fresh-2", "mercury-decide",
|
|
233
|
-
"vendor/x:free@openrouter"}
|
|
234
|
-
c = serve.onboard_candidates()
|
|
235
|
-
groups = {x["served"]: (x["group"], x["new"]) for x in c["candidates"]}
|
|
236
|
-
assert groups["fresh-2"] == ("groq", True) and groups["vendor/y"] == ("openrouter (direct)", False), groups
|
|
237
|
-
assert "never-read" not in json.dumps(c), c
|
|
238
|
-
# Added to Open WebUI after provider_tags.py ran: named live from its urlIdx,
|
|
239
|
-
# not "unknown connection"; the tags file answers when the connections
|
|
240
|
-
# cannot be read (a key that is not an admin's), and the rest still list.
|
|
241
|
-
OWUI_MODELS.append({"id": "just-added", "urlIdx": 1})
|
|
242
|
-
real_names = provider_tags.connection_names
|
|
243
|
-
try:
|
|
244
|
-
live = {x["served"]: x["group"] for x in serve.onboard_candidates()["candidates"]}
|
|
245
|
-
|
|
246
|
-
def refused(hub):
|
|
247
|
-
raise OSError("401")
|
|
248
|
-
provider_tags.connection_names = refused
|
|
249
|
-
c = serve.onboard_candidates()
|
|
250
|
-
finally:
|
|
251
|
-
provider_tags.connection_names = real_names
|
|
252
|
-
OWUI_MODELS.pop()
|
|
253
|
-
fallback = {x["served"]: x["group"] for x in c["candidates"]}
|
|
254
|
-
assert live["just-added"] == "llm7-api" and live["fresh-2"] == "groq", live
|
|
255
|
-
assert c["ok"] and not c["errors"] and fallback["just-added"] == "unknown connection", c
|
|
256
|
-
assert fallback["fresh-2"] == "groq" and fallback["vendor/y"] == "openrouter (direct)", fallback
|
|
257
|
-
|
|
258
|
-
# Auto-retire: a prefix added in Open WebUI is a rename, said in Open WebUI's words.
|
|
259
|
-
models = {"fast-1": "fast-1", "codestral": "codestral", "x-free": "vendor/x:free@openrouter"}
|
|
260
|
-
conns, on_owui = auto_retire.connections(models)
|
|
261
|
-
assert on_owui == {"fast-1", "codestral"} and conns["vendor/x:free@openrouter"] == "openrouter", (conns, on_owui)
|
|
262
|
-
import datetime as dt
|
|
263
|
-
p = auto_retire.plan(dt.date(2026, 10, 25), models, check_deprecations.served(),
|
|
264
|
-
{"codestral": "2026-10-19"}, [], {}, {}, conns, on_owui)
|
|
265
|
-
assert "reprefix" in dict(p["pending"])["codestral"] and not p["retire"], p
|
|
266
|
-
assert serve.model_status()["codestral"][2] == "missing from Open WebUI since 2026-10-19"
|
|
267
|
-
code, out = run_main(reprefix, ["reprefix.py", "llm7-api", "--prefix", "llm7."])
|
|
268
|
-
assert code is None and "codestral" in out and "llm7.codestral" in out, (code, out)
|
|
269
|
-
|
|
270
|
-
# Decisions through Open WebUI's passthrough.
|
|
271
|
-
assert decide.probe("openwebui", "mercury-decide") == (None, "decisions")
|
|
272
|
-
hit = set(owui_calls())
|
|
273
|
-
assert {"/openai/config", "/api/models", "/openai/decisions"} <= hit, hit
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
def main() -> int:
|
|
277
|
-
server = ThreadingHTTPServer(("127.0.0.1", 0), Fake)
|
|
278
|
-
threading.Thread(target=server.serve_forever, daemon=True).start()
|
|
279
|
-
fake = f"http://127.0.0.1:{server.server_port}"
|
|
280
|
-
try:
|
|
281
|
-
stranger(fake)
|
|
282
|
-
owner(fake)
|
|
283
|
-
finally:
|
|
284
|
-
server.shutdown()
|
|
285
|
-
shutil.rmtree(TMP, ignore_errors=True)
|
|
286
|
-
print("ok")
|
|
287
|
-
return 0
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
if __name__ == "__main__":
|
|
291
|
-
sys.exit(main())
|
|
1
|
+
"""Benchwarmers without Open WebUI, and the owner's install with it, against a fake server.
|
|
2
|
+
|
|
3
|
+
python scripts/test_no_owui.py
|
|
4
|
+
|
|
5
|
+
Two installs in one temp data folder (BENCHWARMERS_HOME and LLM_USER_PATH are
|
|
6
|
+
set before anything is imported, so no real file is read or written):
|
|
7
|
+
|
|
8
|
+
1. A stranger whose only endpoint is an OpenRouter-like aggregator, the
|
|
9
|
+
default. Registry, connections, onboarding candidates, the served check
|
|
10
|
+
and auto-retire's plan all work, and no Open WebUI path is ever requested:
|
|
11
|
+
owui.py, provider_tags.py and reprefix.py refuse or skip cleanly.
|
|
12
|
+
2. The owner's shape: endpoints.json with no `kind` anywhere, an `openwebui`
|
|
13
|
+
default at a bare /api root with a `{path}` decisions passthrough, an
|
|
14
|
+
OpenRouter endpoint and a local Ollama. Every Open WebUI path still runs.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
from __future__ import annotations
|
|
18
|
+
|
|
19
|
+
import contextlib
|
|
20
|
+
import io
|
|
21
|
+
import json
|
|
22
|
+
import os
|
|
23
|
+
import pathlib
|
|
24
|
+
import shutil
|
|
25
|
+
import sys
|
|
26
|
+
import tempfile
|
|
27
|
+
import threading
|
|
28
|
+
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
|
|
29
|
+
|
|
30
|
+
ROOT = pathlib.Path(__file__).resolve().parent.parent
|
|
31
|
+
TMP = pathlib.Path(tempfile.mkdtemp(prefix="bench-no-owui-"))
|
|
32
|
+
os.environ["BENCHWARMERS_HOME"] = str(TMP)
|
|
33
|
+
os.environ["LLM_USER_PATH"] = str(TMP / "llm")
|
|
34
|
+
(TMP / "config").mkdir()
|
|
35
|
+
(TMP / "llm").mkdir()
|
|
36
|
+
sys.path[:0] = [str(ROOT), str(ROOT / "metrics"), str(ROOT / "suite"), str(ROOT / "scripts")]
|
|
37
|
+
|
|
38
|
+
SEEN: list[str] = []
|
|
39
|
+
LISTS = {
|
|
40
|
+
"/v1/models": ["vendor/a:free", "vendor/b:free", "vendor/c", "vendor/d", "vendor/e", "vendor/f"],
|
|
41
|
+
"/or/api/v1/models": ["vendor/x:free", "vendor/y"],
|
|
42
|
+
"/ol/v1/models": ["llama-local"],
|
|
43
|
+
}
|
|
44
|
+
OWUI_MODELS = [{"id": "fast-1", "urlIdx": 0}, {"id": "llm7.codestral", "urlIdx": 1},
|
|
45
|
+
{"id": "fresh-2", "urlIdx": 0}, {"id": "mercury-decide", "urlIdx": 1}]
|
|
46
|
+
OWUI_CONFIG = {"OPENAI_API_BASE_URLS": ["https://api.groq.com/openai/v1", "https://api.llm7.io/v1"],
|
|
47
|
+
"OPENAI_API_KEYS": ["never-read-1", "never-read-2"]}
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class Fake(BaseHTTPRequestHandler):
|
|
51
|
+
def do_GET(self): # noqa: N802
|
|
52
|
+
SEEN.append(self.path)
|
|
53
|
+
if self.path in LISTS:
|
|
54
|
+
return self._send({"data": [{"id": i} for i in LISTS[self.path]]})
|
|
55
|
+
if self.path == "/api/models":
|
|
56
|
+
return self._send({"data": OWUI_MODELS})
|
|
57
|
+
if self.path == "/openai/config":
|
|
58
|
+
return self._send(OWUI_CONFIG)
|
|
59
|
+
self._send({}, 404)
|
|
60
|
+
|
|
61
|
+
def do_POST(self): # noqa: N802
|
|
62
|
+
SEEN.append(self.path)
|
|
63
|
+
self.rfile.read(int(self.headers.get("Content-Length") or 0))
|
|
64
|
+
if self.path == "/openai/decisions": # Open WebUI's passthrough
|
|
65
|
+
return self._send({"answers": {"probe": {"answer": True}}})
|
|
66
|
+
self._send({}, 404)
|
|
67
|
+
|
|
68
|
+
def _send(self, data, status=200):
|
|
69
|
+
body = json.dumps(data).encode()
|
|
70
|
+
self.send_response(status)
|
|
71
|
+
self.send_header("Content-Type", "application/json")
|
|
72
|
+
self.send_header("Content-Length", str(len(body)))
|
|
73
|
+
self.end_headers()
|
|
74
|
+
self.wfile.write(body)
|
|
75
|
+
|
|
76
|
+
def log_message(self, *_a):
|
|
77
|
+
pass
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def write(name: str, data) -> None:
|
|
81
|
+
path = TMP / "config" / name
|
|
82
|
+
path.write_text(data if isinstance(data, str) else json.dumps(data), encoding="utf-8")
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def quietly(fn, *args):
|
|
86
|
+
with contextlib.redirect_stdout(io.StringIO()) as out:
|
|
87
|
+
result = fn(*args)
|
|
88
|
+
return result, out.getvalue()
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def run_main(module, argv: list[str]):
|
|
92
|
+
"""A script's main() as its command line would run it: (exit, printed)."""
|
|
93
|
+
saved, sys.argv = sys.argv, argv
|
|
94
|
+
try:
|
|
95
|
+
return quietly(module.main)
|
|
96
|
+
except SystemExit as exc:
|
|
97
|
+
return exc.code, ""
|
|
98
|
+
finally:
|
|
99
|
+
sys.argv = saved
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def owui_calls() -> list[str]:
|
|
103
|
+
return [p for p in SEEN if p.startswith(("/api/", "/openai/"))]
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def stranger(fake: str) -> None:
|
|
107
|
+
write("endpoints.json", {"openrouter": {"base": fake + "/v1", "key": "openrouter", "default": True,
|
|
108
|
+
"kind": "aggregator"}})
|
|
109
|
+
write("alias-history.json", "{}")
|
|
110
|
+
import endpoints
|
|
111
|
+
write("extra-openai-models.yaml", endpoints.yaml_block("a-free", "vendor/a:free")
|
|
112
|
+
+ endpoints.yaml_block("b-free", "vendor/b:free"))
|
|
113
|
+
import route
|
|
114
|
+
import serve
|
|
115
|
+
import check_deprecations
|
|
116
|
+
import auto_retire
|
|
117
|
+
import owui
|
|
118
|
+
import provider_tags
|
|
119
|
+
import reprefix
|
|
120
|
+
|
|
121
|
+
assert (endpoints.kind("openrouter"), endpoints.hub("openrouter")) == ("aggregator", "")
|
|
122
|
+
assert endpoints.find_hub("openwebui") is None and not endpoints.recorded_whole("openrouter")
|
|
123
|
+
assert {r["alias"]: r["endpoint"] for r in endpoints.registry()} == {"a-free": "openrouter", "b-free": "openrouter"}
|
|
124
|
+
# No tags file at all: the endpoint is the connection.
|
|
125
|
+
assert not (TMP / "config" / "provider-tags.json").exists()
|
|
126
|
+
assert route.connection_of("a-free") == route.full_tag_of("b-free") == "openrouter"
|
|
127
|
+
assert serve.tag_for("a-free") == "openrouter"
|
|
128
|
+
|
|
129
|
+
# Onboarding candidates: what it lists minus what is registered; nothing badged new,
|
|
130
|
+
# because the served check records only registered models here.
|
|
131
|
+
c = serve.onboard_candidates()
|
|
132
|
+
assert c["ok"] and not c["errors"], c
|
|
133
|
+
assert sorted(x["served"] for x in c["candidates"]) == ["vendor/c", "vendor/d", "vendor/e", "vendor/f"], c
|
|
134
|
+
assert {x["group"] for x in c["candidates"]} == {"openrouter (direct)"} and not any(x["new"] for x in c["candidates"])
|
|
135
|
+
|
|
136
|
+
# The served check: the registered models only, not the aggregator's catalogue.
|
|
137
|
+
assert check_deprecations.served() == {"vendor/a:free", "vendor/b:free"}
|
|
138
|
+
code, out = run_main(check_deprecations, ["check_deprecations.py", "--dry-run"])
|
|
139
|
+
assert code == 0 and "2 served, 2 registered" in out, (code, out)
|
|
140
|
+
assert not (TMP / "config" / "served-snapshot.json").exists() # a dry run writes nothing
|
|
141
|
+
|
|
142
|
+
# Auto-retire groups by connection without tags: both models on the one
|
|
143
|
+
# connection failing at once is held as the connection, not retired.
|
|
144
|
+
import datetime as dt
|
|
145
|
+
models = {"a-free": "vendor/a:free", "b-free": "vendor/b:free"}
|
|
146
|
+
tags, on_owui = auto_retire.connections(models)
|
|
147
|
+
assert tags == {"vendor/a:free": "openrouter", "vendor/b:free": "openrouter"} and on_owui == set(), tags
|
|
148
|
+
today = dt.date(2026, 10, 21)
|
|
149
|
+
rows = [{"alias": a, "date": today, "answered": False, "failure": "entitlement", "detail": "402"} for a in models]
|
|
150
|
+
p = auto_retire.plan(today, models, set(models.values()), {}, rows, {}, {}, tags, on_owui)
|
|
151
|
+
assert set(p["held"]) == {"openrouter"} and not p["confirm"], p
|
|
152
|
+
gone = auto_retire.plan(today, {"a-free": "vendor/a:free"}, {"x.vendor/a:free"},
|
|
153
|
+
{"vendor/a:free": "2026-10-15"}, [], {}, {}, tags, on_owui)
|
|
154
|
+
assert dict(gone["retire"])["a-free"].startswith("not served by its endpoint"), gone # no prefix guess
|
|
155
|
+
|
|
156
|
+
# Every Open WebUI-only path refuses or skips, and says why.
|
|
157
|
+
try:
|
|
158
|
+
owui.endpoint()
|
|
159
|
+
raise AssertionError("owui found a hub that is not there")
|
|
160
|
+
except ValueError as exc:
|
|
161
|
+
assert "Open WebUI hub" in str(exc)
|
|
162
|
+
r = serve.discover_add("groq", "vendor/z")
|
|
163
|
+
assert not r["ok"] and "Open WebUI hub" in r["errors"][0], r
|
|
164
|
+
_, out = quietly(provider_tags.main)
|
|
165
|
+
assert "no Open WebUI hub" in out and not (TMP / "config" / "provider-tags.json").exists(), out
|
|
166
|
+
code, _ = run_main(reprefix, ["reprefix.py", "groq", "--prefix", "g."])
|
|
167
|
+
assert "Open WebUI" in str(code), code
|
|
168
|
+
|
|
169
|
+
# Adding an endpoint: an explicit kind, a hub only with its type.
|
|
170
|
+
assert not serve.save_endpoint("odd", fake + "/v1", "k", new=True, kind="cloud")["ok"]
|
|
171
|
+
assert not serve.save_endpoint("odd", fake + "/v1", "k", new=True, kind="hub")["ok"]
|
|
172
|
+
# Test (the form's button before Save) answers as Save would and writes nothing, the key included.
|
|
173
|
+
t = serve.save_endpoint("tried", fake + "/v1", "k-tried", new=True, test_only=True)
|
|
174
|
+
assert t["ok"] and "served" in t and "tried" not in endpoints.load() and "tried" not in endpoints.keys(), t
|
|
175
|
+
assert serve.save_endpoint("plain", fake + "/v1", "k", new=True)["ok"]
|
|
176
|
+
assert not serve.save_endpoint("plain", fake + "/v1", "k", new=True, test_only=True)["ok"] # names checked in Test too
|
|
177
|
+
assert serve.save_endpoint("mine", fake + "/v1", "k", new=True, kind="hub", hub="openwebui")["ok"]
|
|
178
|
+
data = endpoints.load()
|
|
179
|
+
assert data["plain"]["kind"] == "provider" and "hub" not in data["plain"], data["plain"]
|
|
180
|
+
assert (data["mine"]["kind"], data["mine"]["hub"]) == ("hub", "openwebui"), data["mine"]
|
|
181
|
+
assert serve.save_endpoint("mine", fake + "/v1", "k2")["ok"] and endpoints.hub("mine") == "openwebui" # Change key keeps it
|
|
182
|
+
rows = {e["name"]: e for e in serve.endpoint_list()["endpoints"]}
|
|
183
|
+
assert (rows["openrouter"]["kind"], rows["mine"]["hub"]) == ("aggregator", "openwebui"), rows
|
|
184
|
+
|
|
185
|
+
assert owui_calls() == [], owui_calls()
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
def owner(fake: str) -> None:
|
|
189
|
+
"""The owner's endpoints.json shape: no `kind` anywhere."""
|
|
190
|
+
write("endpoints.json", {
|
|
191
|
+
"openwebui": {"base": fake + "/api", "key": "openwebui", "default": True,
|
|
192
|
+
"decisions": fake + "/openai/{path}"},
|
|
193
|
+
"openrouter": {"base": fake + "/or/api/v1", "key": "openrouter",
|
|
194
|
+
"headers": {"HTTP-Referer": "https://example.com", "X-Title": "Example"},
|
|
195
|
+
"unlisted_models": ["vendor/decide:free"]},
|
|
196
|
+
"ollama": {"key": "ollama", "connect": {"provider": "ollama", "at": "2026-10-20T22:30:52-05:00"},
|
|
197
|
+
"base": fake + "/ol/v1"}})
|
|
198
|
+
import endpoints
|
|
199
|
+
write("extra-openai-models.yaml", endpoints.yaml_block("fast-1", "fast-1")
|
|
200
|
+
+ endpoints.yaml_block("codestral", "codestral")
|
|
201
|
+
+ endpoints.yaml_block("x-free", "vendor/x:free", "openrouter"))
|
|
202
|
+
write("served-snapshot.json", {"last_seen": {"fast-1": "2026-10-20", "codestral": "2026-10-18"},
|
|
203
|
+
"first_missing": {"codestral": "2026-10-19"}})
|
|
204
|
+
import route
|
|
205
|
+
import serve
|
|
206
|
+
import check_deprecations
|
|
207
|
+
import auto_retire
|
|
208
|
+
import owui
|
|
209
|
+
import provider_tags
|
|
210
|
+
import reprefix
|
|
211
|
+
import decide
|
|
212
|
+
SEEN.clear()
|
|
213
|
+
|
|
214
|
+
# Inferred from his real bases (a LAN address stands in as hub.example)...
|
|
215
|
+
real = {"openwebui": {"base": "http://hub.example:4141/api"}, "openrouter": {"base": "https://openrouter.ai/api/v1"},
|
|
216
|
+
"ollama": {"base": "http://localhost:11434/v1"}}
|
|
217
|
+
kinds = {n: (endpoints.kind(n, real), endpoints.hub(n, real)) for n in real}
|
|
218
|
+
assert kinds == {"openwebui": ("hub", "openwebui"), "openrouter": ("provider", ""), "ollama": ("local", "")}, kinds
|
|
219
|
+
# ...and here, where every base is the fake on 127.0.0.1, the /api root still makes the one hub.
|
|
220
|
+
assert [n for n in endpoints.load() if endpoints.hub(n)] == ["openwebui"]
|
|
221
|
+
assert endpoints.find_hub("openwebui") == "openwebui" and endpoints.recorded_whole("openwebui")
|
|
222
|
+
assert owui.endpoint() == "openwebui" and owui._call("/openai/config") == OWUI_CONFIG
|
|
223
|
+
|
|
224
|
+
# Provider tags: Open WebUI's connections by host, the direct endpoint by name.
|
|
225
|
+
_, out = quietly(provider_tags.main)
|
|
226
|
+
tags = json.loads((TMP / "config" / "provider-tags.json").read_text(encoding="utf-8"))
|
|
227
|
+
assert tags["fast-1"] == "groq" and tags["llm7.codestral"] == "llm7-api" and tags["vendor/x:free"] == "openrouter", tags
|
|
228
|
+
assert "never-read" not in json.dumps(tags)
|
|
229
|
+
assert route.connection_of("fast-1") == "groq" and route.connection_of("x-free") == "openrouter"
|
|
230
|
+
|
|
231
|
+
# The default hub is recorded whole; the direct endpoint, its registered models only.
|
|
232
|
+
assert check_deprecations.served() == {"fast-1", "llm7.codestral", "fresh-2", "mercury-decide",
|
|
233
|
+
"vendor/x:free@openrouter"}
|
|
234
|
+
c = serve.onboard_candidates()
|
|
235
|
+
groups = {x["served"]: (x["group"], x["new"]) for x in c["candidates"]}
|
|
236
|
+
assert groups["fresh-2"] == ("groq", True) and groups["vendor/y"] == ("openrouter (direct)", False), groups
|
|
237
|
+
assert "never-read" not in json.dumps(c), c
|
|
238
|
+
# Added to Open WebUI after provider_tags.py ran: named live from its urlIdx,
|
|
239
|
+
# not "unknown connection"; the tags file answers when the connections
|
|
240
|
+
# cannot be read (a key that is not an admin's), and the rest still list.
|
|
241
|
+
OWUI_MODELS.append({"id": "just-added", "urlIdx": 1})
|
|
242
|
+
real_names = provider_tags.connection_names
|
|
243
|
+
try:
|
|
244
|
+
live = {x["served"]: x["group"] for x in serve.onboard_candidates()["candidates"]}
|
|
245
|
+
|
|
246
|
+
def refused(hub):
|
|
247
|
+
raise OSError("401")
|
|
248
|
+
provider_tags.connection_names = refused
|
|
249
|
+
c = serve.onboard_candidates()
|
|
250
|
+
finally:
|
|
251
|
+
provider_tags.connection_names = real_names
|
|
252
|
+
OWUI_MODELS.pop()
|
|
253
|
+
fallback = {x["served"]: x["group"] for x in c["candidates"]}
|
|
254
|
+
assert live["just-added"] == "llm7-api" and live["fresh-2"] == "groq", live
|
|
255
|
+
assert c["ok"] and not c["errors"] and fallback["just-added"] == "unknown connection", c
|
|
256
|
+
assert fallback["fresh-2"] == "groq" and fallback["vendor/y"] == "openrouter (direct)", fallback
|
|
257
|
+
|
|
258
|
+
# Auto-retire: a prefix added in Open WebUI is a rename, said in Open WebUI's words.
|
|
259
|
+
models = {"fast-1": "fast-1", "codestral": "codestral", "x-free": "vendor/x:free@openrouter"}
|
|
260
|
+
conns, on_owui = auto_retire.connections(models)
|
|
261
|
+
assert on_owui == {"fast-1", "codestral"} and conns["vendor/x:free@openrouter"] == "openrouter", (conns, on_owui)
|
|
262
|
+
import datetime as dt
|
|
263
|
+
p = auto_retire.plan(dt.date(2026, 10, 25), models, check_deprecations.served(),
|
|
264
|
+
{"codestral": "2026-10-19"}, [], {}, {}, conns, on_owui)
|
|
265
|
+
assert "reprefix" in dict(p["pending"])["codestral"] and not p["retire"], p
|
|
266
|
+
assert serve.model_status()["codestral"][2] == "missing from Open WebUI since 2026-10-19"
|
|
267
|
+
code, out = run_main(reprefix, ["reprefix.py", "llm7-api", "--prefix", "llm7."])
|
|
268
|
+
assert code is None and "codestral" in out and "llm7.codestral" in out, (code, out)
|
|
269
|
+
|
|
270
|
+
# Decisions through Open WebUI's passthrough.
|
|
271
|
+
assert decide.probe("openwebui", "mercury-decide") == (None, "decisions")
|
|
272
|
+
hit = set(owui_calls())
|
|
273
|
+
assert {"/openai/config", "/api/models", "/openai/decisions"} <= hit, hit
|
|
274
|
+
|
|
275
|
+
|
|
276
|
+
def main() -> int:
|
|
277
|
+
server = ThreadingHTTPServer(("127.0.0.1", 0), Fake)
|
|
278
|
+
threading.Thread(target=server.serve_forever, daemon=True).start()
|
|
279
|
+
fake = f"http://127.0.0.1:{server.server_port}"
|
|
280
|
+
try:
|
|
281
|
+
stranger(fake)
|
|
282
|
+
owner(fake)
|
|
283
|
+
finally:
|
|
284
|
+
server.shutdown()
|
|
285
|
+
shutil.rmtree(TMP, ignore_errors=True)
|
|
286
|
+
print("ok")
|
|
287
|
+
return 0
|
|
288
|
+
|
|
289
|
+
|
|
290
|
+
if __name__ == "__main__":
|
|
291
|
+
sys.exit(main())
|