@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.
Files changed (103) hide show
  1. package/AGENTS.md +152 -147
  2. package/BENCHMARKS.md +262 -262
  3. package/GATEWAY.md +415 -272
  4. package/NOTICE +28 -28
  5. package/PRIVACY.md +46 -46
  6. package/README.md +408 -314
  7. package/SECURITY.md +83 -83
  8. package/TERMS.md +22 -22
  9. package/USAGE.md +186 -163
  10. package/VERSION +1 -1
  11. package/bin/benchwarmers.js +16 -74
  12. package/cli.py +674 -0
  13. package/examples/config/gateway.json +5 -5
  14. package/examples/config/model-roles.json +11 -11
  15. package/gateway.py +939 -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 +304 -295
  22. package/metrics/client.py +804 -394
  23. package/metrics/cliproxy.py +191 -191
  24. package/metrics/connect.py +691 -691
  25. package/metrics/cost.py +171 -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/formats.py +1021 -0
  30. package/metrics/guard.py +647 -592
  31. package/metrics/learn.py +234 -221
  32. package/metrics/ledger.py +148 -0
  33. package/metrics/litellm_hub.py +206 -206
  34. package/metrics/model_roles.py +94 -84
  35. package/metrics/offload.py +166 -166
  36. package/metrics/owui.py +205 -205
  37. package/metrics/pacing.py +248 -0
  38. package/metrics/paths.py +60 -60
  39. package/metrics/quota.py +383 -0
  40. package/metrics/route.py +581 -579
  41. package/metrics/scheduler.py +275 -275
  42. package/metrics/sources.py +167 -167
  43. package/metrics/wrong.py +144 -144
  44. package/package.json +69 -65
  45. package/perf/README.md +89 -89
  46. package/perf/run.py +284 -261
  47. package/scripts/auto_retire.py +367 -367
  48. package/scripts/check.py +113 -106
  49. package/scripts/check_deprecations.py +378 -378
  50. package/scripts/check_themes.py +103 -103
  51. package/scripts/init.py +124 -124
  52. package/scripts/list_served.py +78 -78
  53. package/scripts/notice.py +78 -78
  54. package/scripts/onboard.py +410 -331
  55. package/scripts/provider_tags.py +129 -129
  56. package/scripts/reprefix.py +136 -136
  57. package/scripts/sync_config.py +105 -105
  58. package/scripts/test_auth.py +255 -255
  59. package/scripts/test_cli.py +189 -0
  60. package/scripts/test_client.py +277 -233
  61. package/scripts/test_connect.py +222 -222
  62. package/scripts/test_first_run.py +180 -180
  63. package/scripts/test_formats.py +549 -0
  64. package/scripts/test_hubs.py +328 -328
  65. package/scripts/test_launcher.py +67 -0
  66. package/scripts/test_llm_log.py +85 -81
  67. package/scripts/test_mcp.py +178 -178
  68. package/scripts/test_merged.py +215 -214
  69. package/scripts/test_no_owui.py +291 -291
  70. package/scripts/test_owui.py +90 -90
  71. package/scripts/test_quota.py +334 -0
  72. package/scripts/test_serve_edit.py +453 -438
  73. package/scripts/test_server_json.py +79 -0
  74. package/scripts/test_verdict.py +102 -98
  75. package/scripts/test_vision.py +231 -228
  76. package/serve.py +3972 -3764
  77. package/suite/mine.py +154 -154
  78. package/suite/pace.py +145 -145
  79. package/suite/run.py +1046 -1042
  80. package/suite/seed.py +421 -421
  81. package/suite/tasks/claim-alias-unbound/fixtures/onboard.py +286 -286
  82. package/suite/tasks/claim-error-text-string/fixtures/decide.py +187 -187
  83. package/suite/tasks/claim-list-body/fixtures/gateway.py +235 -235
  84. package/suite/tasks/claim-member-nominated-twice/fixtures/gateway.py +284 -284
  85. package/suite/tasks/claim-nominee-uses-slot/fixtures/gateway.py +284 -284
  86. package/suite/tasks/claim-silent-exit/fixtures/gateway.py +235 -235
  87. package/suite/tasks/claim-sse-hang/fixtures/gateway.py +235 -235
  88. package/suite/tasks/claim-unknown-nominee-crash/fixtures/gateway.py +284 -284
  89. package/suite/tasks/rebench-matchms-809-bug/task.json +23 -23
  90. package/suite/tasks/rebench-matchms-809-control/task.json +27 -27
  91. package/suite/tasks/rebench-more-itertools-1028-bug/task.json +23 -23
  92. package/suite/tasks/rebench-more-itertools-1028-control/task.json +27 -27
  93. package/suite/tasks/rebench-narwhals-2864-bug/task.json +23 -23
  94. package/suite/tasks/rebench-narwhals-2864-control/task.json +27 -27
  95. package/suite/tasks/rebench-quantecon-py-769-bug/task.json +23 -23
  96. package/suite/tasks/rebench-quantecon-py-769-control/task.json +27 -27
  97. package/suite/tasks/rebench-scrapy-6867-bug/task.json +23 -23
  98. package/suite/tasks/rebench-scrapy-6867-control/task.json +27 -27
  99. package/ui/bench.css +430 -425
  100. package/ui/bench.js +1757 -1576
  101. package/ui/themes/dark.css +35 -35
  102. package/ui/themes/hardwood.css +52 -52
  103. package/ui/themes/rainforest.css +32 -32
@@ -1,318 +1,318 @@
1
- """One list of models worth adding, from models.dev and FreeLLMAPI, deduped.
2
-
3
- python metrics/discover.py # summary: free models by reach, new this week
4
-
5
- The shopping list: Discover's Add (serve.py) puts a model on the bench by one
6
- of the routes below. A model offered by the same provider in both sources
7
- appears once, carrying both sources and the better-filled fields.
8
-
9
- FREE
10
- ----
11
- models.dev lists a price per provider, which is the PAID price even where the
12
- provider has a free tier (Groq, Mistral, NVIDIA). So a model counts as free if
13
- models.dev prices it at 0 (or its id ends :free), OR FreeLLMAPI lists it,
14
- since FreeLLMAPI is a catalog of free tiers. The paid ones are kept, with
15
- their price, behind the dashboard's toggle. A provider whose free models work
16
- only in its own client (opencode) has those left out; see
17
- sources.FREE_IN_OWN_CLIENT_ONLY.
18
-
19
- REACH
20
- -----
21
- on bench already registered here
22
- connected this install has a ROUTE to the provider (below)
23
- easy to add the provider has an OpenAI-compatible API (models.dev gives its
24
- base URL), so it is one new endpoint (or hub connection) away
25
- other a native API only, or a reseller nobody has vetted
26
-
27
- ROUTES
28
- ------
29
- The ways this install can reach a provider, each a row's `routes`:
30
- openwebui a connection in Open WebUI, known because a bench model on the
31
- Open WebUI hub (endpoints.find_hub) is served from it. Adding
32
- goes through metrics/owui.py, so the model also appears in the
33
- chats. None without an Open WebUI hub.
34
- direct an endpoint of its own in config/endpoints.json whose name or
35
- base URL is the provider's (models.dev gives the base). Adding
36
- is plain onboarding on that endpoint: bench only.
37
- hub a connection in a LiteLLM or CLI Proxy API hub (hub_routes),
38
- whose name is the provider's or whose base URL is its API.
39
- Only the hub knows its connections, so it is asked when a
40
- model's popup opens, not here: build() never waits on the
41
- network. Adding goes through that hub's adapter.
42
- ponytail: so these routes do not count toward reach, and a
43
- paid model reachable only through such a hub stays hidden.
44
- The same model offered by two providers (OpenRouter and NVIDIA) has a route
45
- on each; the dashboard's Discover popup lists them together by `norm`.
46
-
47
- NEW is the date a model was first seen in either list, kept in
48
- runs/discover-seen.json (gitignored). The first build records everything as a
49
- baseline, so only models that appear afterwards are badged new.
50
- """
51
-
52
- from __future__ import annotations
53
-
54
- import datetime as dt
55
- import json
56
- import pathlib
57
- import sys
58
-
59
- ROOT = pathlib.Path(__file__).resolve().parent.parent
60
- from paths import DATA # noqa: E402 -- the install's data folder (metrics/paths.py)
61
- SEEN = DATA / "runs" / "discover-seen.json"
62
- sys.path.insert(0, str(ROOT / "metrics"))
63
- sys.path.insert(0, str(ROOT / "suite"))
64
- import sources # noqa: E402
65
-
66
- # FreeLLMAPI platform -> the bench connection host that serves it, where you have one.
67
- FREELLMAPI_TO_CONNECTION = {"groq": "groq", "nvidia": "nvidia-api", "mistral": "mistral",
68
- "openrouter": "openrouter", "ollama": "ollama-cloud",
69
- "cerebras": "cerebras", "llm7": "llm7-api"}
70
- NEW_DAYS = 7
71
-
72
-
73
- def _on_bench() -> list[tuple[str, str, str, str, str]]:
74
- """(endpoint, connection, normalised model, alias, served) for every registered model."""
75
- import endpoints
76
- import route
77
- return [(r["endpoint"], route.connection_of(r["alias"]), sources.norm_model(r["served"]), r["alias"],
78
- r["served"]) for r in endpoints.registry()]
79
-
80
-
81
- def _direct(md: dict) -> dict[str, list[str]]:
82
- """models.dev provider -> the endpoints of its own this install has (not a
83
- hub, which fronts many): by the connection-name map, else by base URL."""
84
- import endpoints
85
- names = sources.connection_to_modelsdev()
86
- by_base = {(p.get("api") or "").rstrip("/"): prov for prov, p in md.items() if p.get("api")}
87
- out: dict[str, list[str]] = {}
88
- data = endpoints.load()
89
- for name, e in data.items():
90
- prov = None if endpoints.hub(name, data) else names.get(name) or by_base.get(e["base"].rstrip("/"))
91
- if prov:
92
- out.setdefault(prov, []).append(name)
93
- return out
94
-
95
-
96
- def _routes(r: dict, bench: list, owui_conns: set[str], direct: dict, owui: str | None) -> list[dict]:
97
- """How this install could add one row's model, and whether it already has.
98
- By the exact id, not `norm`: `vendor/m:free` on the bench is not `vendor/m`
99
- (paid) on the bench. Open WebUI may serve it as `<prefix>.<id>`. `owui`:
100
- the Open WebUI hub's endpoint, None without one (then `owui_conns` is empty)."""
101
- conn, name, mid, out = r["connection"], r["provider_name"], r["model"], []
102
- if conn and conn in owui_conns:
103
- out.append({"via": "openwebui", "connection": conn, "endpoint": owui,
104
- "label": f"{name} via Open WebUI ({conn} connection)",
105
- "note": "Also puts it in Open WebUI's chats: the connection's Model IDs,"
106
- " a public record, its tag and logo.",
107
- "on_bench_as": next((a for ep, c, _n, a, s in bench if ep == owui and c == conn
108
- and (s == mid or s.endswith("." + mid))), None)})
109
- for ep in direct.get(r["provider"], []):
110
- out.append({"via": "direct", "connection": conn or ep, "endpoint": ep,
111
- "label": f"{name} direct ({ep} endpoint)",
112
- "note": "Bench only: registered on its own endpoint, never in Open WebUI's chats.",
113
- "on_bench_as": next((a for e, _c, _n, a, s in bench if e == ep and s == mid), None)})
114
- if len(out) > 1:
115
- # Two routes to one provider. On this install both use the one account
116
- # (OpenRouter's 50 free calls a day are per account, not per route).
117
- for x in out:
118
- x["note"] += f" Shares one {name} quota with the other {name} route when both use the same account."
119
- return out
120
-
121
-
122
- def hub_routes(rows: list[dict]) -> tuple[list[list[dict]], list[str]]:
123
- """The hub routes (see ROUTES) for each of `rows`, and the hubs that could
124
- not be asked. Each LiteLLM or CLI Proxy API hub is asked once, by its
125
- adapter's upstreams(); an install with neither asks nothing."""
126
- import endpoints
127
- hubs, errors = [], []
128
- for which, label in endpoints.HUBS.items():
129
- ep = endpoints.find_hub(which) if which != "openwebui" else None # Open WebUI's are in build()
130
- if not ep:
131
- continue
132
- mod = endpoints.adapter(which)
133
- try:
134
- hubs.append((which, label, ep, mod.SUPPORTS, mod.upstreams()))
135
- except Exception as exc: # noqa: BLE001
136
- errors.append(f"could not ask {label} ({ep}) which connections it has: {exc}")
137
- bench = _on_bench() if hubs else []
138
- out = []
139
- for r in rows:
140
- api, mine = (r.get("api") or "").rstrip("/"), []
141
- for which, label, ep, can, ups in hubs:
142
- for conn, (base, prefix) in ups.items():
143
- if conn not in (r["provider"], r.get("connection")) and not (base and base == api):
144
- continue
145
- served = f"{prefix}/{r['model']}" if prefix else r["model"]
146
- # Only what the hub takes (its adapter's SUPPORTS): no logo or tags where it has none.
147
- said = [f"{label}: into the {conn} connection's {can['allow_list']}"]
148
- said += [f"{k}: {can[k]}" for k in ("tags", "logo", "access") if can[k]]
149
- mine.append({"via": "hub", "hub": which, "connection": conn, "endpoint": ep,
150
- "label": f"{r['provider_name']} via {label} ({conn} connection)",
151
- "note": f"Also serves it to everything else that uses {label}.",
152
- "steps": ["; ".join(said), "The bench: register, a 4-call probe, then the suite"],
153
- "on_bench_as": next((a for e, _c, _n, a, s in bench if e == ep and s == served), None)})
154
- out.append(mine)
155
- return out, errors
156
-
157
-
158
- def build() -> list[dict]:
159
- import endpoints
160
- md, fl = sources.cached("modelsdev"), sources.cached("freellmapi")
161
- md_conn = {v: k for k, v in sources.connection_to_modelsdev().items()}
162
- bench = _on_bench()
163
- # Open WebUI's connections, as far as the bench can tell without asking it.
164
- owui = endpoints.find_hub("openwebui")
165
- owui_conns = {c for ep, c, *_ in bench if ep == owui} - {"?"}
166
- direct = _direct(md)
167
- rows: dict[tuple[str, str], dict] = {}
168
-
169
- for prov, p in md.items():
170
- openai_like = bool(p.get("api")) or p.get("npm") == "@ai-sdk/openai-compatible"
171
- for mid, m in (p.get("models") or {}).items():
172
- out_mods = (m.get("modalities") or {}).get("output") or ["text"]
173
- if "text" not in out_mods or m.get("status") == "deprecated":
174
- continue
175
- cost = m.get("cost")
176
- mods = m.get("modalities") if isinstance(m.get("modalities"), dict) else {}
177
- rows[(prov, sources.norm_model(mid))] = {
178
- "in_mods": _mods(mods.get("input")), "out_mods": _mods(mods.get("output")),
179
- "provider": prov, "provider_name": p.get("name", prov), "model": mid,
180
- "name": m.get("name") or mid, "sources": ["models.dev"],
181
- "free": mid.endswith(":free") or sources.is_free(cost) is True,
182
- "cost": cost, "context": (m.get("limit") or {}).get("context"),
183
- "output": (m.get("limit") or {}).get("output"),
184
- "tools": m.get("tool_call"), "reasoning": m.get("reasoning"),
185
- "vision": "image" in ((m.get("modalities") or {}).get("input") or []),
186
- "released": m.get("release_date"), "knowledge": m.get("knowledge"),
187
- "open_weights": m.get("open_weights"), "api": p.get("api"),
188
- "openai_like": openai_like, "connection": md_conn.get(prov), "limits": None}
189
-
190
- for m in fl.get("models") or []:
191
- plat = m.get("platform", "")
192
- prov = sources.FREELLMAPI_TO_MODELSDEV.get(plat, plat)
193
- key = (prov, sources.norm_model(m.get("modelId", "")))
194
- lim = {k: v for k, v in (m.get("limits") or {}).items() if v} or None
195
- row = rows.get(key)
196
- if row:
197
- row["sources"].append("freellmapi")
198
- row["free"] = True # listed in a free-tier catalog
199
- row["limits"] = lim
200
- row["context"] = row["context"] or m.get("contextWindow")
201
- row["tools"] = row["tools"] if row["tools"] is not None else m.get("supportsTools")
202
- else:
203
- rows[key] = {
204
- "in_mods": ["text", "image"] if m.get("supportsVision") else ["text"], "out_mods": ["text"],
205
- "provider": prov, "provider_name": prov, "model": m.get("modelId"),
206
- "name": m.get("displayName") or m.get("modelId"), "sources": ["freellmapi"],
207
- "free": True, "cost": None, "context": m.get("contextWindow"), "output": None,
208
- "tools": m.get("supportsTools"), "reasoning": None,
209
- "vision": m.get("supportsVision"), "released": None, "knowledge": None,
210
- "open_weights": None, "api": None,
211
- "openai_like": False, "connection": FREELLMAPI_TO_CONNECTION.get(plat),
212
- "limits": lim}
213
-
214
- # The same model through ANOTHER provider is fine to add, but worth knowing.
215
- by_model: dict[str, list[str]] = {}
216
- on_conn: dict[tuple[str, str], str] = {}
217
- for _ep, conn, model, alias, _s in bench:
218
- by_model.setdefault(model, []).append(f"{alias} via {conn}")
219
- on_conn.setdefault((conn, model), alias)
220
- seen = _read_seen()
221
- today = dt.date.today().isoformat()
222
- baseline = not seen
223
- walled = {p for p, on in sources.free_in_own_client_only().items() if on}
224
- out = []
225
- for key, r in rows.items():
226
- k = "|".join(key)
227
- if k not in seen:
228
- seen[k] = "baseline" if baseline else today
229
- r["first_seen"] = seen[k]
230
- r["new"] = seen[k] not in ("baseline",) and seen[k] >= (dt.date.today() - dt.timedelta(days=NEW_DAYS)).isoformat()
231
- if r["free"] and r["provider"] in walled:
232
- continue # free only in the provider's own client (sources.FREE_IN_OWN_CLIENT_ONLY)
233
- conn = r["connection"]
234
- r["norm"] = key[1]
235
- r["routes"] = _routes(r, bench, owui_conns, direct, owui)
236
- # Where there are routes they decide, by exact id: the bench's `v/m:free`
237
- # leaves the paid `v/m` addable, and shows under also_on instead.
238
- mine = (next((x["on_bench_as"] for x in r["routes"] if x["on_bench_as"]), None) if r["routes"]
239
- else on_conn.get((conn, key[1])) if conn else None)
240
- r["on_bench_as"] = mine
241
- r["also_on"] = [x for x in by_model.get(key[1], []) if not mine or not x.startswith(mine + " ")]
242
- r["reach"] = ("on bench" if mine else
243
- "connected" if r["routes"] else
244
- "easy to add" if r["openai_like"] else "other")
245
- out.append(r)
246
- _write_seen(seen)
247
- order = {"connected": 0, "easy to add": 1, "on bench": 2, "other": 3}
248
- out.sort(key=lambda r: (not r["free"], not r["new"], order[r["reach"]], r["provider"], r["model"]))
249
- return out
250
-
251
-
252
- def _mods(value) -> list[str]:
253
- """A modality list as models.dev should give it; a few entries do not."""
254
- return [str(v) for v in value if isinstance(v, str)] or ["text"] if isinstance(value, list) else ["text"]
255
-
256
-
257
- def _read_seen() -> dict:
258
- try:
259
- return json.loads(SEEN.read_text(encoding="utf-8"))
260
- except (OSError, ValueError):
261
- return {}
262
-
263
-
264
- def _write_seen(seen: dict) -> None:
265
- SEEN.parent.mkdir(parents=True, exist_ok=True)
266
- tmp = SEEN.with_suffix(".part")
267
- tmp.write_text(json.dumps(seen, ensure_ascii=False), encoding="utf-8")
268
- tmp.replace(SEEN)
269
-
270
-
271
- def _selftest() -> None:
272
- bench = [("openwebui", "openrouter", "m", "m-owui", "or.v/m"),
273
- ("openrouter", "openrouter", "x", "x-direct", "v/x:free")]
274
- row = {"connection": "openrouter", "provider": "openrouter", "provider_name": "OpenRouter", "model": "v/m"}
275
- both = _routes(row, bench, {"openrouter"}, {"openrouter": ["openrouter"]}, "openwebui")
276
- assert [r["via"] for r in both] == ["openwebui", "direct"], both
277
- # On the bench through Open WebUI (under its prefix) is not on the bench through the endpoint.
278
- assert both[0]["on_bench_as"] == "m-owui" and both[1]["on_bench_as"] is None, both
279
- assert all("Shares one OpenRouter quota" in r["note"] for r in both)
280
- x = _routes({**row, "model": "v/x:free"}, bench, {"openrouter"}, {"openrouter": ["openrouter"]}, "openwebui")
281
- assert x[0]["on_bench_as"] is None and x[1]["on_bench_as"] == "x-direct", x
282
- paid = _routes({**row, "model": "v/x"}, bench, {"openrouter"}, {"openrouter": ["openrouter"]}, "openwebui")
283
- assert paid[1]["on_bench_as"] is None, paid # the :free slug is not the paid one
284
- nv = _routes({"connection": "nvidia-api", "provider": "nvidia", "provider_name": "NVIDIA", "model": "v/m"},
285
- bench, {"nvidia-api"}, {}, "openwebui")
286
- assert len(nv) == 1 and nv[0]["connection"] == "nvidia-api" and "Shares" not in nv[0]["note"], nv
287
- assert _routes({"connection": "groq", "provider": "groq", "provider_name": "Groq", "model": "v/m"}, bench,
288
- {"openrouter"}, {}, "openwebui") == [] # no connection, no endpoint: no route
289
- # No Open WebUI hub: no connections behind one, so only the endpoint's own route.
290
- assert [r["via"] for r in _routes(row, bench, set(), {"openrouter": ["openrouter"]}, None)] == ["direct"]
291
- # Free-only-in-own-client: built in, and an install's switch wins either way.
292
- assert sources.free_in_own_client_only({})["opencode"] is True
293
- over = sources.free_in_own_client_only({"opencode": False, "x": True})
294
- assert over["opencode"] is False and over["x"] is True and over["opencode-go"] is True, over
295
- assert sources.free_in_own_client_only(True) == sources.free_in_own_client_only({}) # not a map: ignored
296
- print("discover selftest ok")
297
-
298
-
299
- def main() -> None:
300
- sys.stdout.reconfigure(encoding="utf-8", errors="replace")
301
- if "--selftest" in sys.argv:
302
- _selftest()
303
- return
304
- rows = build()
305
- free = [r for r in rows if r["free"]]
306
- by = {}
307
- for r in free:
308
- by[r["reach"]] = by.get(r["reach"], 0) + 1
309
- print(f"{len(rows)} models ({len(free)} free) from models.dev + FreeLLMAPI;"
310
- f" free by reach: {by}; new this week: {sum(r['new'] for r in rows)}")
311
- both = sum(1 for r in rows if len(r["sources"]) > 1)
312
- print(f"{both} listed by both sources (shown once)")
313
- for r in [r for r in free if r["reach"] == "connected"][:12]:
314
- print(f" {r['provider']:<14} {r['model'][:48]:<48} ctx {r['context'] or '?'}")
315
-
316
-
317
- if __name__ == "__main__":
318
- main()
1
+ """One list of models worth adding, from models.dev and FreeLLMAPI, deduped.
2
+
3
+ python metrics/discover.py # summary: free models by reach, new this week
4
+
5
+ The shopping list: Discover's Add (serve.py) puts a model on the bench by one
6
+ of the routes below. A model offered by the same provider in both sources
7
+ appears once, carrying both sources and the better-filled fields.
8
+
9
+ FREE
10
+ ----
11
+ models.dev lists a price per provider, which is the PAID price even where the
12
+ provider has a free tier (Groq, Mistral, NVIDIA). So a model counts as free if
13
+ models.dev prices it at 0 (or its id ends :free), OR FreeLLMAPI lists it,
14
+ since FreeLLMAPI is a catalog of free tiers. The paid ones are kept, with
15
+ their price, behind the dashboard's toggle. A provider whose free models work
16
+ only in its own client (opencode) has those left out; see
17
+ sources.FREE_IN_OWN_CLIENT_ONLY.
18
+
19
+ REACH
20
+ -----
21
+ on bench already registered here
22
+ connected this install has a ROUTE to the provider (below)
23
+ easy to add the provider has an OpenAI-compatible API (models.dev gives its
24
+ base URL), so it is one new endpoint (or hub connection) away
25
+ other a native API only, or a reseller nobody has vetted
26
+
27
+ ROUTES
28
+ ------
29
+ The ways this install can reach a provider, each a row's `routes`:
30
+ openwebui a connection in Open WebUI, known because a bench model on the
31
+ Open WebUI hub (endpoints.find_hub) is served from it. Adding
32
+ goes through metrics/owui.py, so the model also appears in the
33
+ chats. None without an Open WebUI hub.
34
+ direct an endpoint of its own in config/endpoints.json whose name or
35
+ base URL is the provider's (models.dev gives the base). Adding
36
+ is plain onboarding on that endpoint: bench only.
37
+ hub a connection in a LiteLLM or CLI Proxy API hub (hub_routes),
38
+ whose name is the provider's or whose base URL is its API.
39
+ Only the hub knows its connections, so it is asked when a
40
+ model's popup opens, not here: build() never waits on the
41
+ network. Adding goes through that hub's adapter.
42
+ ponytail: so these routes do not count toward reach, and a
43
+ paid model reachable only through such a hub stays hidden.
44
+ The same model offered by two providers (OpenRouter and NVIDIA) has a route
45
+ on each; the dashboard's Discover popup lists them together by `norm`.
46
+
47
+ NEW is the date a model was first seen in either list, kept in
48
+ runs/discover-seen.json (gitignored). The first build records everything as a
49
+ baseline, so only models that appear afterwards are badged new.
50
+ """
51
+
52
+ from __future__ import annotations
53
+
54
+ import datetime as dt
55
+ import json
56
+ import pathlib
57
+ import sys
58
+
59
+ ROOT = pathlib.Path(__file__).resolve().parent.parent
60
+ from paths import DATA # noqa: E402 -- the install's data folder (metrics/paths.py)
61
+ SEEN = DATA / "runs" / "discover-seen.json"
62
+ sys.path.insert(0, str(ROOT / "metrics"))
63
+ sys.path.insert(0, str(ROOT / "suite"))
64
+ import sources # noqa: E402
65
+
66
+ # FreeLLMAPI platform -> the bench connection host that serves it, where you have one.
67
+ FREELLMAPI_TO_CONNECTION = {"groq": "groq", "nvidia": "nvidia-api", "mistral": "mistral",
68
+ "openrouter": "openrouter", "ollama": "ollama-cloud",
69
+ "cerebras": "cerebras", "llm7": "llm7-api"}
70
+ NEW_DAYS = 7
71
+
72
+
73
+ def _on_bench() -> list[tuple[str, str, str, str, str]]:
74
+ """(endpoint, connection, normalised model, alias, served) for every registered model."""
75
+ import endpoints
76
+ import route
77
+ return [(r["endpoint"], route.connection_of(r["alias"]), sources.norm_model(r["served"]), r["alias"],
78
+ r["served"]) for r in endpoints.registry()]
79
+
80
+
81
+ def _direct(md: dict) -> dict[str, list[str]]:
82
+ """models.dev provider -> the endpoints of its own this install has (not a
83
+ hub, which fronts many): by the connection-name map, else by base URL."""
84
+ import endpoints
85
+ names = sources.connection_to_modelsdev()
86
+ by_base = {(p.get("api") or "").rstrip("/"): prov for prov, p in md.items() if p.get("api")}
87
+ out: dict[str, list[str]] = {}
88
+ data = endpoints.load()
89
+ for name, e in data.items():
90
+ prov = None if endpoints.hub(name, data) else names.get(name) or by_base.get(e["base"].rstrip("/"))
91
+ if prov:
92
+ out.setdefault(prov, []).append(name)
93
+ return out
94
+
95
+
96
+ def _routes(r: dict, bench: list, owui_conns: set[str], direct: dict, owui: str | None) -> list[dict]:
97
+ """How this install could add one row's model, and whether it already has.
98
+ By the exact id, not `norm`: `vendor/m:free` on the bench is not `vendor/m`
99
+ (paid) on the bench. Open WebUI may serve it as `<prefix>.<id>`. `owui`:
100
+ the Open WebUI hub's endpoint, None without one (then `owui_conns` is empty)."""
101
+ conn, name, mid, out = r["connection"], r["provider_name"], r["model"], []
102
+ if conn and conn in owui_conns:
103
+ out.append({"via": "openwebui", "connection": conn, "endpoint": owui,
104
+ "label": f"{name} via Open WebUI ({conn} connection)",
105
+ "note": "Also puts it in Open WebUI's chats: the connection's Model IDs,"
106
+ " a public record, its tag and logo.",
107
+ "on_bench_as": next((a for ep, c, _n, a, s in bench if ep == owui and c == conn
108
+ and (s == mid or s.endswith("." + mid))), None)})
109
+ for ep in direct.get(r["provider"], []):
110
+ out.append({"via": "direct", "connection": conn or ep, "endpoint": ep,
111
+ "label": f"{name} direct ({ep} endpoint)",
112
+ "note": "Bench only: registered on its own endpoint, never in Open WebUI's chats.",
113
+ "on_bench_as": next((a for e, _c, _n, a, s in bench if e == ep and s == mid), None)})
114
+ if len(out) > 1:
115
+ # Two routes to one provider. On this install both use the one account
116
+ # (OpenRouter's 50 free calls a day are per account, not per route).
117
+ for x in out:
118
+ x["note"] += f" Shares one {name} quota with the other {name} route when both use the same account."
119
+ return out
120
+
121
+
122
+ def hub_routes(rows: list[dict]) -> tuple[list[list[dict]], list[str]]:
123
+ """The hub routes (see ROUTES) for each of `rows`, and the hubs that could
124
+ not be asked. Each LiteLLM or CLI Proxy API hub is asked once, by its
125
+ adapter's upstreams(); an install with neither asks nothing."""
126
+ import endpoints
127
+ hubs, errors = [], []
128
+ for which, label in endpoints.HUBS.items():
129
+ ep = endpoints.find_hub(which) if which != "openwebui" else None # Open WebUI's are in build()
130
+ if not ep:
131
+ continue
132
+ mod = endpoints.adapter(which)
133
+ try:
134
+ hubs.append((which, label, ep, mod.SUPPORTS, mod.upstreams()))
135
+ except Exception as exc: # noqa: BLE001
136
+ errors.append(f"could not ask {label} ({ep}) which connections it has: {exc}")
137
+ bench = _on_bench() if hubs else []
138
+ out = []
139
+ for r in rows:
140
+ api, mine = (r.get("api") or "").rstrip("/"), []
141
+ for which, label, ep, can, ups in hubs:
142
+ for conn, (base, prefix) in ups.items():
143
+ if conn not in (r["provider"], r.get("connection")) and not (base and base == api):
144
+ continue
145
+ served = f"{prefix}/{r['model']}" if prefix else r["model"]
146
+ # Only what the hub takes (its adapter's SUPPORTS): no logo or tags where it has none.
147
+ said = [f"{label}: into the {conn} connection's {can['allow_list']}"]
148
+ said += [f"{k}: {can[k]}" for k in ("tags", "logo", "access") if can[k]]
149
+ mine.append({"via": "hub", "hub": which, "connection": conn, "endpoint": ep,
150
+ "label": f"{r['provider_name']} via {label} ({conn} connection)",
151
+ "note": f"Also serves it to everything else that uses {label}.",
152
+ "steps": ["; ".join(said), "The bench: register, a 4-call probe, then the suite"],
153
+ "on_bench_as": next((a for e, _c, _n, a, s in bench if e == ep and s == served), None)})
154
+ out.append(mine)
155
+ return out, errors
156
+
157
+
158
+ def build() -> list[dict]:
159
+ import endpoints
160
+ md, fl = sources.cached("modelsdev"), sources.cached("freellmapi")
161
+ md_conn = {v: k for k, v in sources.connection_to_modelsdev().items()}
162
+ bench = _on_bench()
163
+ # Open WebUI's connections, as far as the bench can tell without asking it.
164
+ owui = endpoints.find_hub("openwebui")
165
+ owui_conns = {c for ep, c, *_ in bench if ep == owui} - {"?"}
166
+ direct = _direct(md)
167
+ rows: dict[tuple[str, str], dict] = {}
168
+
169
+ for prov, p in md.items():
170
+ openai_like = bool(p.get("api")) or p.get("npm") == "@ai-sdk/openai-compatible"
171
+ for mid, m in (p.get("models") or {}).items():
172
+ out_mods = (m.get("modalities") or {}).get("output") or ["text"]
173
+ if "text" not in out_mods or m.get("status") == "deprecated":
174
+ continue
175
+ cost = m.get("cost")
176
+ mods = m.get("modalities") if isinstance(m.get("modalities"), dict) else {}
177
+ rows[(prov, sources.norm_model(mid))] = {
178
+ "in_mods": _mods(mods.get("input")), "out_mods": _mods(mods.get("output")),
179
+ "provider": prov, "provider_name": p.get("name", prov), "model": mid,
180
+ "name": m.get("name") or mid, "sources": ["models.dev"],
181
+ "free": mid.endswith(":free") or sources.is_free(cost) is True,
182
+ "cost": cost, "context": (m.get("limit") or {}).get("context"),
183
+ "output": (m.get("limit") or {}).get("output"),
184
+ "tools": m.get("tool_call"), "reasoning": m.get("reasoning"),
185
+ "vision": "image" in ((m.get("modalities") or {}).get("input") or []),
186
+ "released": m.get("release_date"), "knowledge": m.get("knowledge"),
187
+ "open_weights": m.get("open_weights"), "api": p.get("api"),
188
+ "openai_like": openai_like, "connection": md_conn.get(prov), "limits": None}
189
+
190
+ for m in fl.get("models") or []:
191
+ plat = m.get("platform", "")
192
+ prov = sources.FREELLMAPI_TO_MODELSDEV.get(plat, plat)
193
+ key = (prov, sources.norm_model(m.get("modelId", "")))
194
+ lim = {k: v for k, v in (m.get("limits") or {}).items() if v} or None
195
+ row = rows.get(key)
196
+ if row:
197
+ row["sources"].append("freellmapi")
198
+ row["free"] = True # listed in a free-tier catalog
199
+ row["limits"] = lim
200
+ row["context"] = row["context"] or m.get("contextWindow")
201
+ row["tools"] = row["tools"] if row["tools"] is not None else m.get("supportsTools")
202
+ else:
203
+ rows[key] = {
204
+ "in_mods": ["text", "image"] if m.get("supportsVision") else ["text"], "out_mods": ["text"],
205
+ "provider": prov, "provider_name": prov, "model": m.get("modelId"),
206
+ "name": m.get("displayName") or m.get("modelId"), "sources": ["freellmapi"],
207
+ "free": True, "cost": None, "context": m.get("contextWindow"), "output": None,
208
+ "tools": m.get("supportsTools"), "reasoning": None,
209
+ "vision": m.get("supportsVision"), "released": None, "knowledge": None,
210
+ "open_weights": None, "api": None,
211
+ "openai_like": False, "connection": FREELLMAPI_TO_CONNECTION.get(plat),
212
+ "limits": lim}
213
+
214
+ # The same model through ANOTHER provider is fine to add, but worth knowing.
215
+ by_model: dict[str, list[str]] = {}
216
+ on_conn: dict[tuple[str, str], str] = {}
217
+ for _ep, conn, model, alias, _s in bench:
218
+ by_model.setdefault(model, []).append(f"{alias} via {conn}")
219
+ on_conn.setdefault((conn, model), alias)
220
+ seen = _read_seen()
221
+ today = dt.date.today().isoformat()
222
+ baseline = not seen
223
+ walled = {p for p, on in sources.free_in_own_client_only().items() if on}
224
+ out = []
225
+ for key, r in rows.items():
226
+ k = "|".join(key)
227
+ if k not in seen:
228
+ seen[k] = "baseline" if baseline else today
229
+ r["first_seen"] = seen[k]
230
+ r["new"] = seen[k] not in ("baseline",) and seen[k] >= (dt.date.today() - dt.timedelta(days=NEW_DAYS)).isoformat()
231
+ if r["free"] and r["provider"] in walled:
232
+ continue # free only in the provider's own client (sources.FREE_IN_OWN_CLIENT_ONLY)
233
+ conn = r["connection"]
234
+ r["norm"] = key[1]
235
+ r["routes"] = _routes(r, bench, owui_conns, direct, owui)
236
+ # Where there are routes they decide, by exact id: the bench's `v/m:free`
237
+ # leaves the paid `v/m` addable, and shows under also_on instead.
238
+ mine = (next((x["on_bench_as"] for x in r["routes"] if x["on_bench_as"]), None) if r["routes"]
239
+ else on_conn.get((conn, key[1])) if conn else None)
240
+ r["on_bench_as"] = mine
241
+ r["also_on"] = [x for x in by_model.get(key[1], []) if not mine or not x.startswith(mine + " ")]
242
+ r["reach"] = ("on bench" if mine else
243
+ "connected" if r["routes"] else
244
+ "easy to add" if r["openai_like"] else "other")
245
+ out.append(r)
246
+ _write_seen(seen)
247
+ order = {"connected": 0, "easy to add": 1, "on bench": 2, "other": 3}
248
+ out.sort(key=lambda r: (not r["free"], not r["new"], order[r["reach"]], r["provider"], r["model"]))
249
+ return out
250
+
251
+
252
+ def _mods(value) -> list[str]:
253
+ """A modality list as models.dev should give it; a few entries do not."""
254
+ return [str(v) for v in value if isinstance(v, str)] or ["text"] if isinstance(value, list) else ["text"]
255
+
256
+
257
+ def _read_seen() -> dict:
258
+ try:
259
+ return json.loads(SEEN.read_text(encoding="utf-8"))
260
+ except (OSError, ValueError):
261
+ return {}
262
+
263
+
264
+ def _write_seen(seen: dict) -> None:
265
+ SEEN.parent.mkdir(parents=True, exist_ok=True)
266
+ tmp = SEEN.with_suffix(".part")
267
+ tmp.write_text(json.dumps(seen, ensure_ascii=False), encoding="utf-8")
268
+ tmp.replace(SEEN)
269
+
270
+
271
+ def _selftest() -> None:
272
+ bench = [("openwebui", "openrouter", "m", "m-owui", "or.v/m"),
273
+ ("openrouter", "openrouter", "x", "x-direct", "v/x:free")]
274
+ row = {"connection": "openrouter", "provider": "openrouter", "provider_name": "OpenRouter", "model": "v/m"}
275
+ both = _routes(row, bench, {"openrouter"}, {"openrouter": ["openrouter"]}, "openwebui")
276
+ assert [r["via"] for r in both] == ["openwebui", "direct"], both
277
+ # On the bench through Open WebUI (under its prefix) is not on the bench through the endpoint.
278
+ assert both[0]["on_bench_as"] == "m-owui" and both[1]["on_bench_as"] is None, both
279
+ assert all("Shares one OpenRouter quota" in r["note"] for r in both)
280
+ x = _routes({**row, "model": "v/x:free"}, bench, {"openrouter"}, {"openrouter": ["openrouter"]}, "openwebui")
281
+ assert x[0]["on_bench_as"] is None and x[1]["on_bench_as"] == "x-direct", x
282
+ paid = _routes({**row, "model": "v/x"}, bench, {"openrouter"}, {"openrouter": ["openrouter"]}, "openwebui")
283
+ assert paid[1]["on_bench_as"] is None, paid # the :free slug is not the paid one
284
+ nv = _routes({"connection": "nvidia-api", "provider": "nvidia", "provider_name": "NVIDIA", "model": "v/m"},
285
+ bench, {"nvidia-api"}, {}, "openwebui")
286
+ assert len(nv) == 1 and nv[0]["connection"] == "nvidia-api" and "Shares" not in nv[0]["note"], nv
287
+ assert _routes({"connection": "groq", "provider": "groq", "provider_name": "Groq", "model": "v/m"}, bench,
288
+ {"openrouter"}, {}, "openwebui") == [] # no connection, no endpoint: no route
289
+ # No Open WebUI hub: no connections behind one, so only the endpoint's own route.
290
+ assert [r["via"] for r in _routes(row, bench, set(), {"openrouter": ["openrouter"]}, None)] == ["direct"]
291
+ # Free-only-in-own-client: built in, and an install's switch wins either way.
292
+ assert sources.free_in_own_client_only({})["opencode"] is True
293
+ over = sources.free_in_own_client_only({"opencode": False, "x": True})
294
+ assert over["opencode"] is False and over["x"] is True and over["opencode-go"] is True, over
295
+ assert sources.free_in_own_client_only(True) == sources.free_in_own_client_only({}) # not a map: ignored
296
+ print("discover selftest ok")
297
+
298
+
299
+ def main() -> None:
300
+ sys.stdout.reconfigure(encoding="utf-8", errors="replace")
301
+ if "--selftest" in sys.argv:
302
+ _selftest()
303
+ return
304
+ rows = build()
305
+ free = [r for r in rows if r["free"]]
306
+ by = {}
307
+ for r in free:
308
+ by[r["reach"]] = by.get(r["reach"], 0) + 1
309
+ print(f"{len(rows)} models ({len(free)} free) from models.dev + FreeLLMAPI;"
310
+ f" free by reach: {by}; new this week: {sum(r['new'] for r in rows)}")
311
+ both = sum(1 for r in rows if len(r["sources"]) > 1)
312
+ print(f"{both} listed by both sources (shown once)")
313
+ for r in [r for r in free if r["reach"] == "connected"][:12]:
314
+ print(f" {r['provider']:<14} {r['model'][:48]:<48} ctx {r['context'] or '?'}")
315
+
316
+
317
+ if __name__ == "__main__":
318
+ main()