@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/metrics/discover.py
CHANGED
|
@@ -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()
|