@wendellresearch-ai/benchwarmers 0.5.6 → 0.6.0

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.
@@ -5,9 +5,11 @@
5
5
  metrics/connect.py --selftest covers the flows themselves; this covers what
6
6
  serve.py does with them: the callback route on the real handler, saving the
7
7
  endpoint by save_endpoint's rules (an owner's headers kept), the refresh token
8
- beside the key, a device sign-in finished by polling, Ollama's local add, and
9
- models on a renewing endpoint routed to the built-in client. Endpoints, keys,
10
- the change log and connect.json all point at a temp folder.
8
+ beside the key, a device sign-in finished by polling, Ollama's local add,
9
+ models on a renewing endpoint routed to the built-in client, and model servers
10
+ found on this machine and what they say of their models for the catalog
11
+ (local_servers). Endpoints, keys, the change log and connect.json all point at
12
+ a temp folder.
11
13
  """
12
14
 
13
15
  from __future__ import annotations
@@ -70,6 +72,151 @@ def serve_on(handler) -> ThreadingHTTPServer:
70
72
  return server
71
73
 
72
74
 
75
+ SHAPES: dict[int, dict] = {} # port -> path -> (status, answer): what the server there says
76
+
77
+
78
+ class Server(BaseHTTPRequestHandler):
79
+ """A local model server, answering as SHAPES says the one on its port does."""
80
+ def do_GET(self): # noqa: N802
81
+ status, body = SHAPES[self.server.server_port].get(self.path, (404, {"error": "not found"}))
82
+ raw = body.encode() if isinstance(body, str) else json.dumps(body).encode()
83
+ self.send_response(status)
84
+ self.send_header("Content-Type", "text/html" if isinstance(body, str) else "application/json")
85
+ self.send_header("Content-Length", str(len(raw)))
86
+ self.end_headers()
87
+ self.wfile.write(raw)
88
+
89
+ def do_POST(self): # noqa: N802 -- Ollama's /api/show: one answer whatever is asked
90
+ self.rfile.read(int(self.headers.get("Content-Length") or 0))
91
+ self.do_GET()
92
+
93
+ def log_message(self, *_a):
94
+ pass
95
+
96
+
97
+ def local_servers() -> None:
98
+ """Found on this machine (connect.detect): each server told by its answers,
99
+ llama.cpp and LocalAI apart on one port, impostors and a hung port ignored,
100
+ this install's ports and servers already added skipped; then on any port,
101
+ by the process listening there. Fake servers on free ports stand in for the
102
+ real ones, a table of their own for this machine's processes (none at
103
+ first: the usual ports alone); endpoints.json is main()'s temp one."""
104
+ import os
105
+ import socket
106
+ import time
107
+
108
+ def listing(*owners):
109
+ return 200, {"object": "list", "data": [{"id": f"m{i}", "object": "model", **({"owned_by": o} if o else {})}
110
+ for i, o in enumerate(owners)]}
111
+ refused, version = (401, {"error": "Missing API key"}), (200, {"version": "1.0"})
112
+ shapes = {
113
+ "ollama": {"/v1/models": listing("library"), "/api/version": version, "/api/show": (200, {
114
+ "capabilities": ["completion", "tools"], "model_info": {
115
+ "general.architecture": "qwen35", "qwen35.context_length": 262144,
116
+ "qwen35.rope.scaling.original_context_length": 16384}})},
117
+ "lmstudio": {"/v1/models": listing(None, None, None), "/api/v1/models": (200, {"models": [
118
+ {"key": "pub/m0", "max_context_length": 32768, "variants": ["pub/m0@q4_k_m"],
119
+ "capabilities": {"vision": True, "trained_for_tool_use": False},
120
+ "loaded_instances": [{"id": "pub/m0:2", "config": {"context_length": 4096}}]},
121
+ {"key": "m1", "max_context_length": 8192, "loaded_instances": [],
122
+ "capabilities": {"vision": False, "trained_for_tool_use": True, "reasoning": {"default": "on"}}}]})},
123
+ "jan": {"/v1/models": refused},
124
+ "llamacpp": {"/v1/models": listing("llamacpp")},
125
+ "localai": {"/v1/models": listing(None, None), "/version": version},
126
+ "vllm": {"/v1/models": listing("vllm"), "/version": version},
127
+ "litellm": {"/v1/models": refused, "/health/liveliness": (200, "I'm alive!")},
128
+ "cliproxy": {"/v1/models": refused, "/": (200, {"message": "CLI Proxy API Server", "endpoints": []})},
129
+ "omniroute": {"/v1/models": listing("a", "b")},
130
+ }
131
+ servers = {sid: serve_on(Server) for sid in shapes if sid != "localai"}
132
+ port = {sid: s.server_port for sid, s in servers.items()}
133
+ port["localai"] = shared = port["llamacpp"] # as 8080 is both
134
+ for sid, shape in shapes.items():
135
+ if sid != "localai":
136
+ SHAPES[port[sid]] = shape
137
+ hung = socket.socket() # takes the connection, never answers
138
+ hung.bind(("127.0.0.1", 0))
139
+ hung.listen()
140
+ saved = (dict(connect.LOCAL), connect.listeners, os.environ.pop("OLLAMA_HOST", None))
141
+ connect.listeners = lambda: []
142
+ try:
143
+ for sid in shapes:
144
+ connect.LOCAL[sid] = {**saved[0][sid], "ports": (port[sid],)}
145
+ connect.LOCAL["hung"] = {"label": "Hung", "ports": (hung.getsockname()[1],), "kind": "local", "docs": "https://x"}
146
+
147
+ def found(**kw) -> dict:
148
+ return {f["id"]: f for f in connect.detect(**kw)}
149
+
150
+ start = time.time()
151
+ got = found()
152
+ # 0.5 s per probe: a hung port that held it up would take far longer; CI runners are slow.
153
+ assert time.time() - start < 5, "a hung port held detection up"
154
+ assert set(got) == set(shapes) - {"localai"}, got # 8080 answering as llama.cpp
155
+ assert got["llamacpp"]["port"] == shared and got["lmstudio"]["models"] == 3, got
156
+ for sid in ("jan", "litellm", "cliproxy"):
157
+ assert got[sid]["key"] and got[sid]["models"] is None, got[sid]
158
+ assert (got["litellm"]["kind"], got["litellm"]["hub"]) == ("hub", "litellm")
159
+ assert (got["cliproxy"]["kind"], got["cliproxy"]["hub"]) == ("hub", "cliproxy")
160
+ assert got["omniroute"]["kind"] == "aggregator" and got["vllm"]["kind"] == "local"
161
+ assert got["vllm"]["base"] == f"http://127.0.0.1:{port['vllm']}/v1"
162
+ assert got["ollama"]["provider"] == "ollama" and got["ollama"]["base"] == connect.PROVIDERS["ollama"]["base"]
163
+ SHAPES[shared] = shapes["localai"] # the same port, answering as LocalAI
164
+ assert "localai" in found() and "llamacpp" not in found()
165
+ # Something else on a server's port: a web page, an API without the server's sign.
166
+ SHAPES[shared] = {"/v1/models": (200, "<html>a web app</html>")}
167
+ SHAPES[port["vllm"]] = {"/v1/models": listing(None)}
168
+ assert not {"localai", "llamacpp", "vllm"} & set(found())
169
+ # This install's own port, and servers already endpoints (by localhost, or by this machine's name).
170
+ assert "omniroute" not in found(skip={port["omniroute"]})
171
+ connect.locked.update(connect.endpoints.FILE, lambda d: d.update(
172
+ mine={"base": f"http://localhost:{port['lmstudio']}/v1", "key": "mine"},
173
+ lan={"base": f"http://{socket.gethostname()}:{port['jan']}/v1", "key": "lan"}))
174
+ listed = {f["id"]: f for f in serve.connect_local()["found"]}
175
+ assert not {"lmstudio", "jan"} & set(listed) and "ollama" in listed, listed
176
+ assert listed["ollama"]["name"] == "ollama" and listed["litellm"]["name"] == "litellm", listed
177
+ # What each says of its own models, for the catalog: LM Studio's listing by every id a
178
+ # request can name, Ollama's /api/show a model at a time, and nothing from anything else.
179
+ import catalog
180
+ connect.locked.update(connect.endpoints.FILE, lambda d: d.update(
181
+ olla={"base": f"http://127.0.0.1:{port['ollama']}/v1", "key": "olla"},
182
+ vl={"base": f"http://127.0.0.1:{port['vllm']}/v1/", "key": "vl"}))
183
+ said, facts = catalog._native("mine", ["ignored"])
184
+ seen = {"context_window": 4096, "vision": True, "tools": False, "reasoning": False}
185
+ assert said == "lmstudio /api/v1/models" and facts["pub/m0"] == facts["pub/m0@q4_k_m"] == \
186
+ facts["pub/m0:2"] == seen, facts
187
+ assert facts["m1"] == {"context_window": 8192, "vision": False, "tools": True, "reasoning": True}, facts
188
+ assert catalog._native("olla", ["q:2b"]) == ("ollama /api/show", {"q:2b": {
189
+ "context_window": 262144, "vision": False, "tools": True, "reasoning": False}})
190
+ assert catalog._native("vl", ["m0"]) == ("", {})
191
+ # On any port, by the process listening there: LM Studio by its name, vLLM by its command
192
+ # line under Python, Ollama by OLLAMA_HOST (added at its real port, so not by its own
193
+ # flow, which knows only 11434); a port whose process is none of them is never asked.
194
+ extra = {sid: serve_on(Server) for sid in ("lmstudio", "vllm", "ollama", "other")}
195
+ servers.update({sid + "-moved": s for sid, s in extra.items()})
196
+ at = {sid: s.server_port for sid, s in extra.items()}
197
+ for sid in at:
198
+ SHAPES[at[sid]] = shapes["vllm" if sid == "other" else sid] # "other" answers as vLLM too
199
+ connect.listeners = lambda: [(1, at["lmstudio"], "LM Studio.exe", ""),
200
+ (2, at["vllm"], "python3", "/opt/venv/bin/python3 /opt/venv/bin/vllm serve m"),
201
+ (3, at["other"], "notepad.exe", "notepad.exe a.txt")]
202
+ os.environ["OLLAMA_HOST"] = f"0.0.0.0:{at['ollama']}"
203
+ moved = {(f["id"], f["port"]): f for f in connect.detect()}
204
+ assert {("lmstudio", at["lmstudio"]), ("vllm", at["vllm"]), ("ollama", at["ollama"])} <= set(moved), moved
205
+ assert at["other"] not in {p for _, p in moved}, moved
206
+ ollama = moved[("ollama", at["ollama"])]
207
+ assert not ollama["provider"] and ollama["base"] == f"http://127.0.0.1:{at['ollama']}/v1", ollama
208
+ finally:
209
+ connect.LOCAL.clear()
210
+ connect.LOCAL.update(saved[0])
211
+ connect.listeners = saved[1]
212
+ os.environ.pop("OLLAMA_HOST", None)
213
+ if saved[2] is not None:
214
+ os.environ["OLLAMA_HOST"] = saved[2]
215
+ hung.close()
216
+ for s in servers.values():
217
+ s.shutdown()
218
+
219
+
73
220
  def main() -> int:
74
221
  provider, dash = serve_on(Provider), serve_on(serve.Handler)
75
222
  fake = f"http://127.0.0.1:{provider.server_port}"
@@ -207,6 +354,8 @@ def main() -> int:
207
354
  assert set(json.loads(serve.DISMISSED_PATH.read_text(encoding="utf-8"))) == {"m2@hub", "m3"}
208
355
  assert not serve.remove_endpoint("fk-2")["ok"] and not serve.set_default("nope")["ok"]
209
356
  assert sum(1 for e in serve.endpoints.load().values() if e.get("default")) == 1
357
+
358
+ local_servers()
210
359
  finally:
211
360
  connect.PROVIDERS.clear()
212
361
  connect.PROVIDERS.update(saved[0])
@@ -94,9 +94,11 @@ def main() -> int:
94
94
  assert "cannot be read" in get("/"), "a broken endpoints file read as none"
95
95
  broken.unlink()
96
96
 
97
- # 1. No endpoint: the page renders and leads into Add; the APIs answer.
97
+ # 1. No endpoint: the page renders and leads into Add (on the Endpoints
98
+ # page, which bench.js fetches when the step opens it); the APIs answer.
98
99
  page = get("/")
99
- assert "id='first-run' data-step='endpoint'" in page and "id='e-add'" in page, "no first-run step"
100
+ assert "id='first-run' data-step='endpoint'" in page and "id='e-add'" in get("/api/pane?tab=endpoints")["html"], \
101
+ "no first-run step"
100
102
  assert get("/api/endpoints")["endpoints"] == []
101
103
  assert get("/api/connect")["providers"], "no providers to sign in with"
102
104
  cand = get("/api/candidates")
@@ -565,6 +565,45 @@ def gateway_routes() -> None:
565
565
  assert n == {"totalTokens": F.estimate("x" * 400)}, n
566
566
  assert not seats.asked
567
567
 
568
+ # NO COLLECTION NAMED: a decision model sure enough picks one (not ad hoc, one the key may
569
+ # call), and the reply says which and why; unsure, or switched off, it is refused as before.
570
+ real_pick, asked_pick = gateway.decide.pick, []
571
+
572
+ def pick(name, state, options, use, answer=None):
573
+ asked_pick.append((name, state, set(options), use))
574
+ return answer or {"choice": "other", "confidence": 0.9}, "dm", ""
575
+ gateway.decide.pick = pick
576
+ unnamed = {"messages": [{"role": "system", "content": "Be brief."}, {"role": "user", "content": "pick for me"}]}
577
+ try:
578
+ text, h = ok("/v1/chat/completions", unnamed)
579
+ r = json.loads(text)
580
+ assert r["model"] == "bench/other" and r["bench"]["picked"]["collection"] == "bench/other", r
581
+ assert h["X-Bench-Picked"] == "bench/other; dm, 0.90", h
582
+ assert "Collection bench/other chosen for this request: no collection was named, so dm chose it" \
583
+ in r["choices"][0]["message"]["content"], r
584
+ name, state, options, use = asked_pick[-1]
585
+ assert (name, state, use) == ("collection", "Be brief.\n\npick for me", "pick_collection"), asked_pick
586
+ assert "other" in options and "core" in options and "dear" not in options, options
587
+ logged = json.loads((TMP / "runs" / "gateway" / "calls.jsonl").read_text(encoding="utf-8").splitlines()[-1])
588
+ assert logged["picked"]["by"] == "dm", logged # the benchmark never learns from it
589
+ status, h, text, _ = call("/v1/chat/completions", {**unnamed, "stream": True})
590
+ assert status == 200 and h["X-Bench-Picked"].startswith("bench/other;") and "picked" in text, (h, text)
591
+ text, h = ok("/v1/messages", {"max_tokens": 8, "messages": [{"role": "user", "content": "pick for me"}]})
592
+ assert json.loads(text)["model"] == "bench/other" and h["X-Bench-Picked"], text
593
+ gateway.decide.pick = lambda *a: pick(*a, answer={"choice": "other", "confidence": 0.5})
594
+ status, h, text, _ = call("/v1/chat/completions", unnamed)
595
+ assert status == 400 and "No collection was named" in text and "X-Bench-Picked" not in h, text
596
+ write("gateway.json", {"max_calls": 3, "max_explorers": 0, "pick_confidence": 0.4}) # its own cutoff
597
+ assert call("/v1/chat/completions", unnamed)[0] == 200
598
+ write("gateway.json", {"max_calls": 3, "max_explorers": 0, "pick_collection": False})
599
+ asked_pick.clear()
600
+ assert call("/v1/chat/completions", unnamed)[0] == 400 and not asked_pick # off: not even asked
601
+ assert call("/v1/chat/completions", {**unnamed, "model": "bench/nope"})[0] == 404 and not asked_pick
602
+ finally:
603
+ gateway.decide.pick = real_pick
604
+ write("gateway.json", {"max_calls": 3, "max_explorers": 0})
605
+ seats.asked.clear()
606
+
568
607
  # The dashboard's port answers Ollama's /api/ routes as the gateway's, beside its own.
569
608
  text, _ = ok("/api/tags", on=dash.server_port)
570
609
  assert "bench/core" in text
@@ -4,8 +4,9 @@
4
4
 
5
5
  Each rule the popup enforces is tried once and must refuse; then a role change
6
6
  and a rename are applied to the copy and checked. The llm sync is not run:
7
- serve.ROOT points at the copy, which has no scripts/sync_config.py, so the
8
- sync reports that it could not run. Alerts and endpoints point at copies too,
7
+ with the built-in client it is skipped, and with the llm CLI serve.ROOT points
8
+ at the copy, which has no scripts/sync_config.py, so the sync reports that it
9
+ could not run. Alerts and endpoints point at copies too,
9
10
  because removing a model clears its alerts and a regressed endpoint check
10
11
  would write the endpoint file.
11
12
  """
@@ -170,6 +171,50 @@ def main() -> int:
170
171
  ok = (set(view["served"]) == set(view["models"]) and victim not in view["served"]
171
172
  and view["served"].get(other) == d3[other]["served"])
172
173
  print(("ok " if ok else "FAIL ") + "collection view: served name for every member choice"); failures += 0 if ok else 1
174
+ # Clients: every snippet parses in its format, names each collection, sets the
175
+ # timeouts a panel needs, and a made key goes only into a shell line or a field.
176
+ import tomllib
177
+ try:
178
+ import yaml # comes with the llm CLI; the built-in client's venv may lack it
179
+ load_yaml = yaml.safe_load
180
+ except ImportError:
181
+ load_yaml = None
182
+ bad = []
183
+ for keys in (False, True):
184
+ setups = {c["id"]: c for c in serve.client_setups("http://127.0.0.1:8122", [("core", 200_000, False),
185
+ ("fast", None, True)], keys)}
186
+ parsed = {}
187
+ for c in setups.values():
188
+ for b in c["blocks"]:
189
+ parse = {"json": json.loads, "toml": tomllib.loads, "yaml": load_yaml}.get(b["lang"])
190
+ try:
191
+ parsed[c["id"], b["label"]] = parse(b["text"]) if parse else b["text"]
192
+ except Exception as exc: # noqa: BLE001
193
+ bad.append(f"{c['id']} {b['label']} does not parse: {exc}")
194
+ if b["key"] and b["lang"] not in ("sh", "bat", "text"):
195
+ bad.append(f"{c['id']} {b['label']}: a key would go into a file")
196
+ alltext = "\n".join(b["text"] for b in c["blocks"])
197
+ want = ("bench/core:merged", "bench/fast:merged") if c["id"] == "chat" else \
198
+ ("bench/core:route", "bench/fast:route") if c["id"] != "codex" else ("bench/core:route",)
199
+ bad += [f"{c['id']} lacks {w}" for w in want if w not in alltext]
200
+ oc, cx = parsed.get(("opencode", "opencode.json")), parsed.get(("codex", "config.toml"))
201
+ if oc and cx:
202
+ p = oc["provider"]["benchwarmers"]
203
+ if (oc["mcp"]["benchwarmers"]["timeout"] != 900_000 or cx["mcp_servers"]["benchwarmers"]["tool_timeout_sec"] != 900
204
+ or cx["model_providers"]["benchwarmers"]["wire_api"] != "responses" or cx["web_search"] != "disabled"
205
+ or p["models"]["bench/fast:route"]["limit"]["context"] != serve.UNKNOWN_CONTEXT
206
+ or ("apiKey" in p["options"]) != keys or ("env_key" in cx["model_providers"]["benchwarmers"]) != keys):
207
+ bad.append(f"opencode or Codex settings wrong (keys={keys})")
208
+ if parsed.get(("cline", "Cline: cline_mcp_settings.json"), {}).get("mcpServers", {}).get("benchwarmers", {}).get("timeout") != 900:
209
+ bad.append("Cline's MCP timeout is not 900 s")
210
+ if not all(m["toolCalling"] for m in parsed.get(("vscode", "chatLanguageModels.json"), [{}])[0].get("models", [{}])):
211
+ bad.append("a VS Code model without toolCalling is hidden from the agent picker")
212
+ if load_yaml and parsed.get(("goose", "config.yaml"), {}).get("extensions", {}).get("benchwarmers", {}).get("timeout") != 900:
213
+ bad.append("goose's extension timeout is not 900 s")
214
+ ok = not bad
215
+ print(("ok " if ok else "FAIL ") + "client set-ups: JSON, TOML" + (", YAML" if load_yaml else " (YAML unread: no PyYAML)")
216
+ + " parse, collections named, timeouts set, keys only in shell lines and fields" + ("" if ok else f" -> {bad}"))
217
+ failures += 0 if ok else 1
173
218
  # restore: same alias back, out of retired.json, role and ad hoc kept, and
174
219
  # back into the collections that still exist (one deleted, one changed since)
175
220
  serve.delete_collection("doomed")
@@ -255,7 +300,7 @@ def main() -> int:
255
300
 
256
301
  # Call options: max_tokens set, refused above the model's max output or as
257
302
  # a fraction or for no model, then cleared; the llm CLI's copy is stubbed.
258
- told, real_info = [], serve.catalog.info
303
+ told, real_info, llm_option = [], serve.catalog.info, serve._llm_option
259
304
  serve._llm_option = lambda alias, name, value: told.append((alias, name, value)) or "stub"
260
305
  serve.catalog.info = lambda a: {**real_info(a), "max_output": 50_000}
261
306
  chat = next(a for a, m in sorted(serve.model_directory().items()) if m.get("kind") != "decision")
@@ -290,6 +335,7 @@ def main() -> int:
290
335
  import client as model_client
291
336
  model_client.CHOICE = cfg / "client.json"
292
337
  env, real_which = os.environ.pop("BENCH_CLIENT", None), shutil.which
338
+ ran, real_run = [], serve.subprocess.run
293
339
  try:
294
340
  shutil.which = lambda name: "/usr/bin/" + name # llm installed: still built-in
295
341
  first = model_client.chosen()
@@ -305,8 +351,14 @@ def main() -> int:
305
351
  no_llm = serve.save_client("llm")["ok"]
306
352
  shutil.which = real_which
307
353
  unknown = serve.save_client("curl")["ok"]
354
+ # With the built-in client nothing is mirrored into the llm CLI (5-6 s
355
+ # a save); choosing the llm CLI syncs it once.
356
+ serve.subprocess.run = lambda args, **kw: ran.append(args) or serve.subprocess.CompletedProcess(args, 0, "", "")
357
+ quiet = (serve._sync_cli(), llm_option(chat, "max_tokens", 5), list(ran))
358
+ shutil.which = lambda name: "/usr/bin/" + name
359
+ switched, again = serve.save_client("llm"), serve.save_client("llm")
308
360
  finally:
309
- shutil.which = real_which
361
+ serve.subprocess.run, shutil.which = real_run, real_which
310
362
  os.environ.pop("BENCH_CLIENT", None)
311
363
  if env is not None:
312
364
  os.environ["BENCH_CLIENT"] = env
@@ -314,6 +366,12 @@ def main() -> int:
314
366
  and pinned == (("llm", "environment"), False) and not no_llm and not unknown)
315
367
  print(("ok " if ok else "FAIL ") + "model client choice" + ("" if ok else f" -> {first} {made} {picked} {pinned}"))
316
368
  failures += 0 if ok else 1
369
+ # Saving it again syncs again: the way to retry a failed sync.
370
+ ok = (quiet == (serve.NOT_SYNCED, serve.NOT_SYNCED, []) and switched["sync"] == "synced to the llm CLI"
371
+ and again["sync"] == "synced to the llm CLI" and len(ran) == 2
372
+ and all(str(r[-1]).endswith("sync_config.py") for r in ran))
373
+ print(("ok " if ok else "FAIL ") + "llm CLI synced only when it is the client" + ("" if ok else f" -> {quiet} {switched} {ran}"))
374
+ failures += 0 if ok else 1
317
375
 
318
376
  # Free only in their own app: only what differs from the built-in is written; bad input refused.
319
377
  import sources
@@ -553,6 +611,252 @@ def main() -> int:
553
611
  if (DATA / "config" / "collections.json").exists() else None))
554
612
  print(("ok " if ok else "FAIL ") + "the real alerts, endpoints and collections were not touched")
555
613
  failures += 0 if ok else 1
614
+
615
+ # The Logs page reads a log from its end, and still gets whole lines.
616
+ log = cfg / "big.log"
617
+ lines = [f"{i} " + "x" * 2000 for i in range(400)]
618
+ log.write_text("\n".join(lines) + "\n\n", encoding="utf-8")
619
+ ok = (serve._log_rows(log) == lines[::-1][:200] and serve._log_rows(log, 100) == lines[::-1][:100]
620
+ and serve._log_rows(log.with_name("none.log")) == [] and list(serve._from_end(log)) == lines[::-1])
621
+ print(("ok " if ok else "FAIL ") + "a log's last lines, read from its end")
622
+ failures += 0 if ok else 1
623
+
624
+ # Confidently wrong is scored again when a task or a saved answer changes.
625
+ answer = cfg / "tasks" / "t" / "output" / "m.md"
626
+ answer.parent.mkdir(parents=True)
627
+ (cfg / "tasks" / "t" / "task.json").write_text("{}", encoding="utf-8")
628
+ answer.write_text("a", encoding="utf-8")
629
+ before = serve._tasks_key(cfg / "tasks")
630
+ answer.write_text("ab", encoding="utf-8")
631
+ ok = len(before) == 2 and serve._tasks_key(cfg / "tasks") != before and serve._tasks_key(cfg / "none") == ()
632
+ print(("ok " if ok else "FAIL ") + "a changed answer changes the scoring's key")
633
+ failures += 0 if ok else 1
634
+
635
+ # Suite scores and statuses are worked out once a render; what one pane
636
+ # changes in its copy, the next does not see.
637
+ (serve.DATA / "runs" / "suite-2026-01-01.md").write_text("| t1 | `m1` | 1/2 | — | 3.0 |\n", encoding="utf-8")
638
+ with serve.locked.snapshot():
639
+ serve.model_status().clear()
640
+ for v in serve.suite_scores().values():
641
+ v.clear()
642
+ ok = (serve.model_status() == serve._model_status() and serve._suite_scores()
643
+ and serve.suite_scores() == serve._suite_scores())
644
+ print(("ok " if ok else "FAIL ") + "suite scores and statuses once a render, a copy each")
645
+ failures += 0 if ok else 1
646
+
647
+ # Trend: a model against itself, task by task. A drop in two runs is a trend;
648
+ # the same drop in one run, or against one earlier run, is not. A retried row
649
+ # ("2.0 (x2)") is a score, and a renamed alias goes by its current name.
650
+ import datetime as dt
651
+ hist = json.loads(serve.HISTORY_PATH.read_text(encoding="utf-8"))
652
+ serve.HISTORY_PATH.write_text(json.dumps({**hist, "tr-old": "tr-down"}), encoding="utf-8")
653
+ aliases.forget()
654
+ today = dt.date.today()
655
+
656
+ def note(ago, rows):
657
+ day = (today - dt.timedelta(days=ago)).isoformat()
658
+ with (serve.DATA / "runs" / f"suite-{day}.md").open("a", encoding="utf-8") as fh:
659
+ fh.write(f"\n## Suite run {day}\n\n| Task | Model | Found | Invented | Seconds |\n| --- | --- | --- | --- | --- |\n"
660
+ + "".join(f"| {t} | `{m}` | {f} | — | 2.0 (x2) |\n" for t, m, f in rows))
661
+ three = ("ta", "tb", "tc")
662
+ note(50, [(t, m, "5/5") for m in ("tr-old", "tr-once", "tr-thin") for t in three] + [("ta", "tr-twice", "0/1")])
663
+ note(50, [("ta", "tr-twice", "1/1")]) # the same task again that day: one score, the mean
664
+ note(40, [(t, m, "5/5") for m in ("tr-old", "tr-once") for t in three] + [("ta", "tr-down", "timeout")])
665
+ note(10, [("ta", "tr-down", "3/5"), ("tb", "tr-down", "2/5"), ("ta", "tr-thin", "2/5"), ("tb", "tr-thin", "2/5")]
666
+ + [(t, "tr-once", "5/5") for t in three])
667
+ note(3, [("tc", "tr-down", "3/5"), ("tc", "tr-thin", "2/5")] + [(t, "tr-once", "1/5") for t in three])
668
+ note(10, [("ta", "tr-twice", "1/1")])
669
+ # A note appended in the middle of a render is read from disk, not from that render's copy.
670
+ with serve.locked.snapshot():
671
+ serve.note_rows()
672
+ note(0, [("ta", "tr-late", "1/1")])
673
+ late = any(r[3] == "tr-late" for r in serve.note_rows())
674
+ t = serve.trend_rows()["models"]
675
+ w = {m: t[m]["windows"][serve.TREND_DAYS[0]] for m in ("tr-down", "tr-once", "tr-thin")}
676
+ twice = t["tr-twice"]
677
+ late = late and "tr-late" in t and not t["tr-late"]["compared"]
678
+ # The chart's and the table's order: by flag, the biggest move first; too little by name.
679
+ flags = {"x": ("higher", 0.1), "y": ("higher", 0.3), "z": ("lower", -0.1), "b": ("too little", 0.1), "a": ("too little", 0.9)}
680
+ order = sorted(flags, key=lambda m: serve._trend_order(m, dict(zip(("state", "change"), flags[m]))))
681
+ ok = ({m: s["state"] for m, s in w.items()} == {"tr-down": "lower", "tr-once": "steady", "tr-thin": "too little"}
682
+ and [[k, round(v, 9)] for k, v in w["tr-down"]["per"]] == [["tb", -0.6], ["ta", -0.4], ["tc", -0.4]]
683
+ and order == ["z", "y", "x", "a", "b"]
684
+ and (w["tr-down"]["tasks"], w["tr-down"]["down"], w["tr-down"]["runs"], w["tr-down"]["before"]) == (3, 3, 2, 2)
685
+ and abs(w["tr-down"]["change"] + 1.4 / 3) < 1e-9 and w["tr-once"]["runsDown"] == 1
686
+ and "tr-old" not in t and t["tr-down"]["last"] == (today - dt.timedelta(days=3)).isoformat()
687
+ and ("ta", "tr-down") in serve._suite_scores() and serve.trend_rows() is serve.trend_rows()
688
+ and late and twice["compared"] and twice["last"] == (today - dt.timedelta(days=10)).isoformat()
689
+ and abs(twice["windows"][serve.TREND_DAYS[0]]["change"] - 0.5) < 1e-9) # day 50's two scores: one mean
690
+ print(("ok " if ok else "FAIL ") + "trend: a model against itself, two runs to be a trend"
691
+ + ("" if ok else f" -> {w} {twice} late {late}"))
692
+ failures += 0 if ok else 1
693
+
694
+ # gzip for a caller off this machine that takes it; never over loopback.
695
+ import gzip
696
+
697
+ class Reply:
698
+ def __init__(self, address, accept):
699
+ self.headers, self.client_address, self.sent = {"Accept-Encoding": accept}, (address, 1), {}
700
+
701
+ def send_header(self, k, v):
702
+ self.sent[k] = v
703
+ body = b"<p>a page</p>" * 500
704
+ far, near, plain = Reply("203.0.113.20", "gzip, deflate, br"), Reply("::ffff:127.0.0.1", "gzip"), Reply("10.0.0.2", "")
705
+ refused, other = Reply("10.0.0.3", "gzip;q=0, identity"), Reply("10.0.0.4", "x-gzip-not")
706
+ ok = (gzip.decompress(serve.Handler._length(far, body)) == body and far.sent["Content-Encoding"] == "gzip"
707
+ and int(far.sent["Content-Length"]) < len(body) and serve.Handler._length(near, body) == body
708
+ and serve.Handler._length(plain, body) == body and serve.Handler._length(refused, body) == body
709
+ and serve.Handler._length(other, body) == body
710
+ and "Content-Encoding" not in {**near.sent, **plain.sent, **refused.sent, **other.sent}
711
+ and plain.sent["Content-Length"] == str(len(body)))
712
+ print(("ok " if ok else "FAIL ") + "gzip off this machine only" + ("" if ok else f" -> {far.sent} {near.sent}"))
713
+ failures += 0 if ok else 1
714
+
715
+ # Uptime counts each call once: a gateway seat from its log, a probe from its
716
+ # row, the suite's calls from the ledger only after its last failure kept
717
+ # without text; the ledger's own lines for the gateway and the probe are skipped.
718
+ import datetime as dt
719
+ import ledger
720
+ import time
721
+ home = serve.DATA # `tmp` names another folder by now
722
+ ledger.DIR = home / "usage"
723
+ ledger.DIR.mkdir()
724
+ now = time.time()
725
+ at = dt.datetime.fromtimestamp(now).astimezone().isoformat(timespec="seconds")
726
+ (home / "runs" / "gateway").mkdir(parents=True, exist_ok=True)
727
+ seat = {"call": "p.member1", "seat": "member", "alias": "m1", "status": "ok", "answer": "yes", "error": "", "seconds": 2}
728
+ panel = {"at": at, "shape": "design", "seats": [
729
+ seat, {**seat, "status": "error", "answer": "", "error": "Error: Error code: 503 - high demand"},
730
+ {**seat, "status": "error", "answer": "", "error": "Error: Error code: 429 - Daily token quota exceeded."}]}
731
+ calls = home / "runs" / "gateway" / "calls.jsonl"
732
+ calls.write_text(json.dumps(panel) + "\n", encoding="utf-8")
733
+ (home / "perf").mkdir(exist_ok=True)
734
+ (home / "perf" / "history.csv").write_text(
735
+ "timestamp,model,prompt,n,p50_s,p95_s,min_s,max_s,chars_per_s,answered,refused,error,timeout,failure,detail\n"
736
+ f"{at[:19]},m1,tiny,1,1.5,1.5,1.5,1.5,0,1,0,0,0,,\n", encoding="utf-8")
737
+
738
+ def line(dt_s, caller, status, **more):
739
+ return json.dumps({"t": now + dt_s, "at": at, "alias": "m1", "connection": "c1", "endpoint": "e",
740
+ "served": "m1", "status": status, "attempts": 1, "seconds": 3.0, "caller": caller,
741
+ "purpose": "", **more}, separators=(",", ":")) + "\n"
742
+ ledger._file(now).write_text(
743
+ line(-30, "serve", "ok") + line(-20, "suite/run", "ok") + line(-10, "suite/run", "error")
744
+ + line(10, "suite/run", "ok") + line(20, "suite/run", "error", error="Error: Error code: 503 - overloaded"),
745
+ encoding="utf-8")
746
+ first = serve._uptime_rows()["models"]["m1"][-1]
747
+ with calls.open("a", encoding="utf-8") as fh:
748
+ fh.write(json.dumps({**panel, "seats": [seat]}) + "\n")
749
+ again = serve._uptime_rows()
750
+ ok = (first[:3] == [3, 2, 1] and again["models"]["m1"][-1][:3] == [4, 2, 1]
751
+ and again["providers"]["c1"][-1][:3] == [4, 2, 1] and first[3] == {"busy": 2, "our quota": 1})
752
+ print(("ok " if ok else "FAIL ") + "uptime counts each call once, the new ones only"
753
+ + ("" if ok else f" -> {first} {again['models']['m1'][-1]}"))
754
+ failures += 0 if ok else 1
755
+
756
+ # Decision models in Uptime, from their own log; and an error no rule knows, counted as a
757
+ # decision model said, marked so in the hover and the list (never asked here).
758
+ real_decide = decide.FILE, decide.LOG, decide.DECIDED, decide.RESULTS
759
+ decide.FILE, decide.LOG = home / "config" / "decision-models.json", home / "metrics" / "decisions.jsonl"
760
+ decide.DECIDED, decide.RESULTS = home / "metrics" / "decided.json", home / "metrics" / "decide-bench.json"
761
+ decide.FILE.write_text(json.dumps({"dmA": {"served": "dmA", "endpoint": "e", "via": "e",
762
+ "question_types": ["choice", "noul"]},
763
+ "dmB": {"served": "v/dmB:free", "endpoint": "e", "via": "e",
764
+ "question_types": ["choice"]}}),
765
+ encoding="utf-8")
766
+ decide.LOG.write_text(
767
+ json.dumps({"at": at, "alias": "dmB", "served": "v/dmB:free", "via": "e", "answers": {"q": {}}, "error": "",
768
+ "seconds": 0.4}) + "\n"
769
+ + json.dumps({"at": at, "alias": "dmB", "served": "v/dmB:free", "via": "e", "answers": None,
770
+ "error": "404 No endpoints found for v/dmB:free.", "seconds": 0.1}) + "\n", encoding="utf-8")
771
+ odd_error = "Error: the gremlins ate it"
772
+ decide.DECIDED.write_text(json.dumps({"errors": {decide.shape(odd_error): {"class": "busy", "confidence": 0.81,
773
+ "by": "dmA"}},
774
+ "roles": {"m1": {"role": "code", "confidence": 0.7, "by": "dmA"}}}),
775
+ encoding="utf-8")
776
+ with calls.open("a", encoding="utf-8") as fh:
777
+ fh.write(json.dumps({**panel, "seats": [{**seat, "status": "error", "answer": "", "error": odd_error}]}) + "\n")
778
+ u = serve._uptime_rows()
779
+ marked = "busy (decided by dmA at 0.81)"
780
+ ok = (u["models"]["dmB"][-1][:4] == [1, 0, 1, {"setup: not found": 1}]
781
+ and u["models"]["m1"][-1][3].get(marked) == 1 and serve._down(marked)
782
+ and [o[4:] for o in u["odd"]] == [[serve._shape(odd_error), "busy, decided by dmA at 0.81"]]
783
+ and any(r["name"] == "dmB" for r in serve.uptime_data()["models"])
784
+ and "decided by dmA at 0.81" in serve.uptime_card())
785
+ print(("ok " if ok else "FAIL ") + "uptime: decision models' calls, and decided shapes marked"
786
+ + ("" if ok else f" -> {u['models'].get('dmB')} {u['models']['m1'][-1]} {u['odd']}"))
787
+ failures += 0 if ok else 1
788
+
789
+ # The Decision models card: ranked from MIN_ITEMS items, said so below that; the role
790
+ # suggestion reaches the popup's data, and the collection pick is saved beside the limits.
791
+ cal = [{"from": 0.8, "n": 8, "said": 0.9, "right": 0.875}]
792
+ decide.RESULTS.write_text(json.dumps({"updated": at, "models": {
793
+ "dmA": {"errors": {"asked": 8, "answered": 8, "correct": 7, "accuracy": 0.875, "macro_f1": 0.8, "brier": 0.1,
794
+ "ece": 0.03, "calibration": cal, "confident": {"cutoff": 0.6, "coverage": 1.0, "accuracy": 0.875},
795
+ "seconds": 0.3, "failures": {}, "last": at}},
796
+ "dmB": {"errors": {"asked": 1, "answered": 0, "correct": 0, "failures": {"setup": 1}, "seconds": 0.1,
797
+ "last": at}}}}), encoding="utf-8")
798
+ html = serve.decision_card()
799
+ data = serve.decision_data()
800
+ ok = ("1 of 1" in html and "below 8 items" in html and "7/8" in html and "88% <span class='muted'>of 8"
801
+ in html and data["points"] == [{"model": "dmA", "set": "errors", "said": 0.9, "right": 0.875, "n": 8}]
802
+ and "best of 1 ranked on the errors set: 7 right of 8 asked" in html
803
+ and json.loads(serve.directory_json())["suggested"]["m1"]["role"] == "code")
804
+ print(("ok " if ok else "FAIL ") + "decision card: ranks, below the floor, uses, role suggestion"
805
+ + ("" if ok else f" -> {data}"))
806
+ failures += 0 if ok else 1
807
+ gw = serve.bench_collections.SETTINGS_FILE
808
+ saved = [serve.save_gateway_settings(5, 2, pick_collection=False, pick_confidence=0.8),
809
+ serve.save_gateway_settings(5, 2)] # not sent: left as they are
810
+ kept = serve.bench_collections.pick_settings()
811
+ saved.append(serve.save_gateway_settings(5, 2, pick_collection=True, pick_confidence=None))
812
+ bad = [serve.save_gateway_settings(5, 2, pick_collection="yes"), serve.save_gateway_settings(5, 2, pick_confidence=0),
813
+ serve.save_gateway_settings(5, 2, pick_confidence=True)]
814
+ ok = (all(r["ok"] for r in saved) and not any(r["ok"] for r in bad)
815
+ and kept == {"pick_collection": False, "pick_confidence": 0.8}
816
+ and json.loads(gw.read_text(encoding="utf-8")).get("pick_collection") is True
817
+ and "pick_confidence" not in json.loads(gw.read_text(encoding="utf-8")))
818
+ print(("ok " if ok else "FAIL ") + "gateway settings: the collection pick, on, off and its confidence"
819
+ + ("" if ok else f" -> {saved} {bad} {kept}"))
820
+ failures += 0 if ok else 1
821
+ # Export and import: the settings and the endpoints' keys out of this copy, into
822
+ # an empty install; a preview writes nothing; a name outside the scope is refused whole.
823
+ import io
824
+ import zipfile
825
+ real_llm, there = serve.endpoints.LLM_DIR, serve.DATA
826
+ serve.endpoints.LLM_DIR = there
827
+ ep = next(e for e in serve.endpoints.load().values() if e.get("key"))
828
+ (there / "keys.json").write_text(json.dumps({ep["key"]: "k-1", "unrelated": "k-2"}), encoding="utf-8")
829
+ (cfg / "x.json.lock").write_text("", encoding="utf-8")
830
+ plain, with_keys, evil = io.BytesIO(), io.BytesIO(), io.BytesIO()
831
+ serve.export_zip("settings", False, plain)
832
+ made = serve.export_zip("settings", True, with_keys)
833
+ names = zipfile.ZipFile(plain).namelist()
834
+ carried = json.loads(zipfile.ZipFile(with_keys).read("keys.json"))
835
+ other = there / "other"
836
+ other.mkdir()
837
+ serve.DATA = serve.endpoints.LLM_DIR = other
838
+ with_keys.seek(0)
839
+ look = serve.import_zip(with_keys, apply=False)
840
+ wrote_nothing = not (other / "config").exists()
841
+ with_keys.seek(0)
842
+ done = serve.import_zip(with_keys, apply=True)
843
+ with zipfile.ZipFile(evil, "w") as z:
844
+ z.writestr("manifest.json", json.dumps({"benchwarmers_export": 1, "scope": "settings"}))
845
+ z.writestr("config/../../outside.txt", "x")
846
+ evil.seek(0)
847
+ refused = serve.import_zip(evil, apply=True)
848
+ ok = ("manifest.json" in names and "keys.json" not in names and "config/endpoints.json" in names
849
+ and not any(n.endswith(".lock") for n in names) and carried == {ep["key"]: "k-1"} and made["keys"] == 1
850
+ and look["ok"] and look["replaces"] == 0 and look["keys"] == 1 and wrote_nothing
851
+ and done["ok"] and (other / done["backup"]).is_file()
852
+ and (other / "config" / "endpoints.json").read_bytes() == (cfg / "endpoints.json").read_bytes()
853
+ and json.loads((other / "keys.json").read_text(encoding="utf-8")) == {ep["key"]: "k-1"}
854
+ and not refused["ok"] and not (there / "outside.txt").exists() and not (there.parent / "outside.txt").exists())
855
+ serve.DATA, serve.endpoints.LLM_DIR = there, real_llm
856
+ print(("ok " if ok else "FAIL ") + "export and import: settings and keys out, into an empty install, nothing outside"
857
+ + ("" if ok else f" -> {names[:5]} {carried} {look} {done} {refused}"))
858
+ failures += 0 if ok else 1
859
+ decide.FILE, decide.LOG, decide.DECIDED, decide.RESULTS = real_decide
556
860
  shutil.rmtree(tmp, ignore_errors=True)
557
861
  print("\nall rules hold" if not failures else f"\n{failures} FAILED")
558
862
  return 1 if failures else 0