@wendellresearch-ai/benchwarmers 0.1.1 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +147 -147
- package/BENCHMARKS.md +262 -262
- package/GATEWAY.md +272 -271
- package/NOTICE +28 -28
- package/PRIVACY.md +46 -46
- package/README.md +368 -275
- package/SECURITY.md +83 -83
- package/TERMS.md +22 -22
- package/USAGE.md +170 -155
- package/VERSION +1 -1
- package/bin/benchwarmers.js +16 -72
- package/cli.py +666 -0
- package/examples/config/gateway.json +5 -5
- package/examples/config/model-roles.json +11 -11
- package/gateway.py +755 -712
- package/launcher.py +120 -0
- package/mcp_server.py +266 -0
- package/metrics/alerts.py +116 -116
- package/metrics/aliases.py +41 -41
- package/metrics/bench_collections.py +141 -141
- package/metrics/catalog.py +295 -295
- package/metrics/client.py +394 -373
- package/metrics/cliproxy.py +191 -191
- package/metrics/connect.py +691 -691
- package/metrics/cost.py +152 -152
- package/metrics/decide.py +227 -227
- package/metrics/discover.py +318 -318
- package/metrics/endpoints.py +436 -436
- package/metrics/guard.py +617 -592
- package/metrics/learn.py +221 -221
- package/metrics/litellm_hub.py +206 -206
- package/metrics/model_roles.py +84 -84
- package/metrics/offload.py +166 -166
- package/metrics/owui.py +205 -205
- package/metrics/paths.py +60 -60
- package/metrics/route.py +579 -574
- package/metrics/scheduler.py +275 -275
- package/metrics/sources.py +167 -167
- package/metrics/wrong.py +144 -144
- package/package.json +68 -64
- package/perf/README.md +89 -89
- package/perf/run.py +261 -261
- package/scripts/auto_retire.py +367 -367
- package/scripts/check.py +108 -104
- 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 +401 -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 +233 -233
- package/scripts/test_connect.py +222 -222
- package/scripts/test_first_run.py +180 -180
- package/scripts/test_hubs.py +328 -328
- package/scripts/test_launcher.py +67 -0
- package/scripts/test_llm_log.py +85 -0
- package/scripts/test_mcp.py +178 -0
- package/scripts/test_merged.py +214 -193
- package/scripts/test_no_owui.py +291 -291
- package/scripts/test_owui.py +90 -90
- package/scripts/test_serve_edit.py +448 -386
- package/scripts/test_verdict.py +98 -98
- package/scripts/test_vision.py +228 -228
- package/serve.py +3904 -3662
- package/suite/mine.py +154 -154
- package/suite/pace.py +145 -145
- package/suite/run.py +1046 -1040
- 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 -424
- package/ui/bench.js +1756 -1555
- package/ui/themes/dark.css +35 -35
- package/ui/themes/hardwood.css +52 -52
- package/ui/themes/rainforest.css +32 -32
package/metrics/cost.py
CHANGED
|
@@ -1,152 +1,152 @@
|
|
|
1
|
-
"""What each model actually costs us, from the `llm` CLI's own log.
|
|
2
|
-
|
|
3
|
-
python metrics/cost.py
|
|
4
|
-
|
|
5
|
-
**No new instrumentation.** The `llm` CLI has been logging every call all
|
|
6
|
-
along — 382 turns with token counts were already on disk before this file
|
|
7
|
-
existed. This reads them.
|
|
8
|
-
|
|
9
|
-
WHY REASONING TOKENS MATTER SEPARATELY
|
|
10
|
-
--------------------------------------
|
|
11
|
-
They are billed like output and are invisible in the answer. One
|
|
12
|
-
`gemini-3.8-flash-high` call spent **6,524** of them on a prompt whose reply
|
|
13
|
-
was a few lines; a trivial one spent 123 of 134 total tokens thinking. A model
|
|
14
|
-
that answers correctly at four times the token cost of another is a different
|
|
15
|
-
proposition for bulk work, and nothing else in this repo could see that.
|
|
16
|
-
|
|
17
|
-
WHAT THIS IS NOT
|
|
18
|
-
----------------
|
|
19
|
-
Money. Most of what we call is free or flat-rate, and inventing a price per
|
|
20
|
-
model would be a number nobody could check. Tokens are the honest unit: they
|
|
21
|
-
are what we spend, they are what a provider meters, and a rate can be applied
|
|
22
|
-
later by whoever knows it.
|
|
23
|
-
"""
|
|
24
|
-
|
|
25
|
-
from __future__ import annotations
|
|
26
|
-
|
|
27
|
-
import collections
|
|
28
|
-
import json
|
|
29
|
-
import pathlib
|
|
30
|
-
import sqlite3
|
|
31
|
-
|
|
32
|
-
ROOT = pathlib.Path(__file__).resolve().parent.parent
|
|
33
|
-
from paths import DATA # noqa: E402 -- the install's data folder (metrics/paths.py)
|
|
34
|
-
from endpoints import LLM_DIR # noqa: E402
|
|
35
|
-
LOGS = LLM_DIR / "logs.db"
|
|
36
|
-
OUT = DATA / "metrics" / "cost.json"
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
def alias_history() -> dict[str, str]:
|
|
40
|
-
path = DATA / "config" / "alias-history.json"
|
|
41
|
-
if not path.exists():
|
|
42
|
-
return {}
|
|
43
|
-
return json.loads(path.read_text(encoding="utf-8"))
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
LEDGER = DATA / "metrics" / "benchmark-calls.json"
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
def note_benchmark_calls(model: str, calls: int) -> None:
|
|
50
|
-
"""Record that N of a model's logged calls were a BENCHMARK, not work.
|
|
51
|
-
|
|
52
|
-
Without this the call counter measures how much a model was BENCHMARKED
|
|
53
|
-
rather than how much it was relied on, and says the opposite of what it
|
|
54
|
-
appears to. `deepseek-v4-pro-0813` shows 214 calls, of which roughly 200
|
|
55
|
-
are floor runs that failed — it looks like the most-used model here and it
|
|
56
|
-
is nothing of the sort.
|
|
57
|
-
|
|
58
|
-
Each runner reports its own total, because each runner is the only thing
|
|
59
|
-
that knows. Offload calls are then the remainder.
|
|
60
|
-
"""
|
|
61
|
-
try:
|
|
62
|
-
data = json.loads(LEDGER.read_text(encoding="utf-8")) if LEDGER.exists() else {}
|
|
63
|
-
except ValueError:
|
|
64
|
-
data = {}
|
|
65
|
-
data[model] = data.get(model, 0) + calls
|
|
66
|
-
LEDGER.parent.mkdir(parents=True, exist_ok=True)
|
|
67
|
-
LEDGER.write_text(json.dumps(data, indent=2, sort_keys=True) + "\n",
|
|
68
|
-
encoding="utf-8")
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
def benchmark_calls() -> dict[str, int]:
|
|
72
|
-
if not LEDGER.exists():
|
|
73
|
-
return {}
|
|
74
|
-
try:
|
|
75
|
-
return json.loads(LEDGER.read_text(encoding="utf-8"))
|
|
76
|
-
except ValueError:
|
|
77
|
-
return {}
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
def collect() -> dict:
|
|
81
|
-
"""Per-model totals, with old aliases folded into their current name."""
|
|
82
|
-
if not LOGS.exists():
|
|
83
|
-
return {}
|
|
84
|
-
rename = alias_history()
|
|
85
|
-
db = sqlite3.connect(f"file:{LOGS}?mode=ro", uri=True)
|
|
86
|
-
db.row_factory = sqlite3.Row
|
|
87
|
-
|
|
88
|
-
agg: dict[str, dict] = collections.defaultdict(
|
|
89
|
-
lambda: {"calls": 0, "input": 0, "output": 0, "reasoning": 0,
|
|
90
|
-
"duration_ms": 0, "with_reasoning_data": 0})
|
|
91
|
-
for row in db.execute(
|
|
92
|
-
"SELECT model, input_tokens, output_tokens, token_details,"
|
|
93
|
-
" duration_ms FROM turns"):
|
|
94
|
-
# Turns predating the versioned rename carry the old alias.
|
|
95
|
-
model = rename.get(row["model"], row["model"])
|
|
96
|
-
a = agg[model]
|
|
97
|
-
a["calls"] += 1
|
|
98
|
-
a["input"] += row["input_tokens"] or 0
|
|
99
|
-
a["output"] += row["output_tokens"] or 0
|
|
100
|
-
a["duration_ms"] += row["duration_ms"] or 0
|
|
101
|
-
try:
|
|
102
|
-
details = json.loads(row["token_details"] or "{}") or {}
|
|
103
|
-
except ValueError:
|
|
104
|
-
details = {}
|
|
105
|
-
reasoning = ((details.get("completion_tokens_details") or {})
|
|
106
|
-
.get("reasoning_tokens"))
|
|
107
|
-
if reasoning is not None:
|
|
108
|
-
a["reasoning"] += reasoning
|
|
109
|
-
a["with_reasoning_data"] += 1
|
|
110
|
-
|
|
111
|
-
bench = benchmark_calls()
|
|
112
|
-
out = {}
|
|
113
|
-
for model, a in agg.items():
|
|
114
|
-
billed = a["output"] + a["reasoning"]
|
|
115
|
-
out[model] = {
|
|
116
|
-
**a,
|
|
117
|
-
# The number that decides bulk work: how much of what we pay for
|
|
118
|
-
# never appears in the answer.
|
|
119
|
-
"reasoning_share": (round(a["reasoning"] / billed, 3)
|
|
120
|
-
if billed else None),
|
|
121
|
-
"tokens_per_call": (round((a["input"] + billed) / a["calls"])
|
|
122
|
-
if a["calls"] else 0),
|
|
123
|
-
# Benchmark calls ONLY. There was an `offload_calls` here too,
|
|
124
|
-
# derived as calls minus benchmarks, and it was wrong in both
|
|
125
|
-
# directions: the ledger began the day it was written, so every
|
|
126
|
-
# earlier suite run counted as work. Real offloads are recorded
|
|
127
|
-
# deliberately in metrics/offload.py, because subtracting one
|
|
128
|
-
# imperfect number from another gives a third nobody can check.
|
|
129
|
-
"benchmark_calls": bench.get(model, 0),
|
|
130
|
-
}
|
|
131
|
-
return out
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
def main() -> None:
|
|
135
|
-
data = collect()
|
|
136
|
-
OUT.write_text(json.dumps(data, indent=2, sort_keys=True) + "\n",
|
|
137
|
-
encoding="utf-8")
|
|
138
|
-
rows = sorted(data.items(), key=lambda kv: -kv[1]["tokens_per_call"])
|
|
139
|
-
print(f"{'model':<30} {'calls':>5} {'tok/call':>9} {'reasoning share':>16}")
|
|
140
|
-
for model, a in rows:
|
|
141
|
-
share = ("—" if a["reasoning_share"] is None
|
|
142
|
-
else f"{a['reasoning_share']:.1%}")
|
|
143
|
-
note = "" if a["with_reasoning_data"] else " (no reasoning data)"
|
|
144
|
-
print(f"{model:<30} {a['calls']:>5} {a['tokens_per_call']:>9,} "
|
|
145
|
-
f"{share:>16}{note}")
|
|
146
|
-
print(f"\nwrote {OUT}")
|
|
147
|
-
print("Tokens, not money: most of what we call is free or flat-rate, and "
|
|
148
|
-
"a made-up price is a number nobody can check.")
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
if __name__ == "__main__":
|
|
152
|
-
main()
|
|
1
|
+
"""What each model actually costs us, from the `llm` CLI's own log.
|
|
2
|
+
|
|
3
|
+
python metrics/cost.py
|
|
4
|
+
|
|
5
|
+
**No new instrumentation.** The `llm` CLI has been logging every call all
|
|
6
|
+
along — 382 turns with token counts were already on disk before this file
|
|
7
|
+
existed. This reads them.
|
|
8
|
+
|
|
9
|
+
WHY REASONING TOKENS MATTER SEPARATELY
|
|
10
|
+
--------------------------------------
|
|
11
|
+
They are billed like output and are invisible in the answer. One
|
|
12
|
+
`gemini-3.8-flash-high` call spent **6,524** of them on a prompt whose reply
|
|
13
|
+
was a few lines; a trivial one spent 123 of 134 total tokens thinking. A model
|
|
14
|
+
that answers correctly at four times the token cost of another is a different
|
|
15
|
+
proposition for bulk work, and nothing else in this repo could see that.
|
|
16
|
+
|
|
17
|
+
WHAT THIS IS NOT
|
|
18
|
+
----------------
|
|
19
|
+
Money. Most of what we call is free or flat-rate, and inventing a price per
|
|
20
|
+
model would be a number nobody could check. Tokens are the honest unit: they
|
|
21
|
+
are what we spend, they are what a provider meters, and a rate can be applied
|
|
22
|
+
later by whoever knows it.
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
from __future__ import annotations
|
|
26
|
+
|
|
27
|
+
import collections
|
|
28
|
+
import json
|
|
29
|
+
import pathlib
|
|
30
|
+
import sqlite3
|
|
31
|
+
|
|
32
|
+
ROOT = pathlib.Path(__file__).resolve().parent.parent
|
|
33
|
+
from paths import DATA # noqa: E402 -- the install's data folder (metrics/paths.py)
|
|
34
|
+
from endpoints import LLM_DIR # noqa: E402
|
|
35
|
+
LOGS = LLM_DIR / "logs.db"
|
|
36
|
+
OUT = DATA / "metrics" / "cost.json"
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def alias_history() -> dict[str, str]:
|
|
40
|
+
path = DATA / "config" / "alias-history.json"
|
|
41
|
+
if not path.exists():
|
|
42
|
+
return {}
|
|
43
|
+
return json.loads(path.read_text(encoding="utf-8"))
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
LEDGER = DATA / "metrics" / "benchmark-calls.json"
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def note_benchmark_calls(model: str, calls: int) -> None:
|
|
50
|
+
"""Record that N of a model's logged calls were a BENCHMARK, not work.
|
|
51
|
+
|
|
52
|
+
Without this the call counter measures how much a model was BENCHMARKED
|
|
53
|
+
rather than how much it was relied on, and says the opposite of what it
|
|
54
|
+
appears to. `deepseek-v4-pro-0813` shows 214 calls, of which roughly 200
|
|
55
|
+
are floor runs that failed — it looks like the most-used model here and it
|
|
56
|
+
is nothing of the sort.
|
|
57
|
+
|
|
58
|
+
Each runner reports its own total, because each runner is the only thing
|
|
59
|
+
that knows. Offload calls are then the remainder.
|
|
60
|
+
"""
|
|
61
|
+
try:
|
|
62
|
+
data = json.loads(LEDGER.read_text(encoding="utf-8")) if LEDGER.exists() else {}
|
|
63
|
+
except ValueError:
|
|
64
|
+
data = {}
|
|
65
|
+
data[model] = data.get(model, 0) + calls
|
|
66
|
+
LEDGER.parent.mkdir(parents=True, exist_ok=True)
|
|
67
|
+
LEDGER.write_text(json.dumps(data, indent=2, sort_keys=True) + "\n",
|
|
68
|
+
encoding="utf-8")
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def benchmark_calls() -> dict[str, int]:
|
|
72
|
+
if not LEDGER.exists():
|
|
73
|
+
return {}
|
|
74
|
+
try:
|
|
75
|
+
return json.loads(LEDGER.read_text(encoding="utf-8"))
|
|
76
|
+
except ValueError:
|
|
77
|
+
return {}
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def collect() -> dict:
|
|
81
|
+
"""Per-model totals, with old aliases folded into their current name."""
|
|
82
|
+
if not LOGS.exists():
|
|
83
|
+
return {}
|
|
84
|
+
rename = alias_history()
|
|
85
|
+
db = sqlite3.connect(f"file:{LOGS}?mode=ro", uri=True)
|
|
86
|
+
db.row_factory = sqlite3.Row
|
|
87
|
+
|
|
88
|
+
agg: dict[str, dict] = collections.defaultdict(
|
|
89
|
+
lambda: {"calls": 0, "input": 0, "output": 0, "reasoning": 0,
|
|
90
|
+
"duration_ms": 0, "with_reasoning_data": 0})
|
|
91
|
+
for row in db.execute(
|
|
92
|
+
"SELECT model, input_tokens, output_tokens, token_details,"
|
|
93
|
+
" duration_ms FROM turns"):
|
|
94
|
+
# Turns predating the versioned rename carry the old alias.
|
|
95
|
+
model = rename.get(row["model"], row["model"])
|
|
96
|
+
a = agg[model]
|
|
97
|
+
a["calls"] += 1
|
|
98
|
+
a["input"] += row["input_tokens"] or 0
|
|
99
|
+
a["output"] += row["output_tokens"] or 0
|
|
100
|
+
a["duration_ms"] += row["duration_ms"] or 0
|
|
101
|
+
try:
|
|
102
|
+
details = json.loads(row["token_details"] or "{}") or {}
|
|
103
|
+
except ValueError:
|
|
104
|
+
details = {}
|
|
105
|
+
reasoning = ((details.get("completion_tokens_details") or {})
|
|
106
|
+
.get("reasoning_tokens"))
|
|
107
|
+
if reasoning is not None:
|
|
108
|
+
a["reasoning"] += reasoning
|
|
109
|
+
a["with_reasoning_data"] += 1
|
|
110
|
+
|
|
111
|
+
bench = benchmark_calls()
|
|
112
|
+
out = {}
|
|
113
|
+
for model, a in agg.items():
|
|
114
|
+
billed = a["output"] + a["reasoning"]
|
|
115
|
+
out[model] = {
|
|
116
|
+
**a,
|
|
117
|
+
# The number that decides bulk work: how much of what we pay for
|
|
118
|
+
# never appears in the answer.
|
|
119
|
+
"reasoning_share": (round(a["reasoning"] / billed, 3)
|
|
120
|
+
if billed else None),
|
|
121
|
+
"tokens_per_call": (round((a["input"] + billed) / a["calls"])
|
|
122
|
+
if a["calls"] else 0),
|
|
123
|
+
# Benchmark calls ONLY. There was an `offload_calls` here too,
|
|
124
|
+
# derived as calls minus benchmarks, and it was wrong in both
|
|
125
|
+
# directions: the ledger began the day it was written, so every
|
|
126
|
+
# earlier suite run counted as work. Real offloads are recorded
|
|
127
|
+
# deliberately in metrics/offload.py, because subtracting one
|
|
128
|
+
# imperfect number from another gives a third nobody can check.
|
|
129
|
+
"benchmark_calls": bench.get(model, 0),
|
|
130
|
+
}
|
|
131
|
+
return out
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def main() -> None:
|
|
135
|
+
data = collect()
|
|
136
|
+
OUT.write_text(json.dumps(data, indent=2, sort_keys=True) + "\n",
|
|
137
|
+
encoding="utf-8")
|
|
138
|
+
rows = sorted(data.items(), key=lambda kv: -kv[1]["tokens_per_call"])
|
|
139
|
+
print(f"{'model':<30} {'calls':>5} {'tok/call':>9} {'reasoning share':>16}")
|
|
140
|
+
for model, a in rows:
|
|
141
|
+
share = ("—" if a["reasoning_share"] is None
|
|
142
|
+
else f"{a['reasoning_share']:.1%}")
|
|
143
|
+
note = "" if a["with_reasoning_data"] else " (no reasoning data)"
|
|
144
|
+
print(f"{model:<30} {a['calls']:>5} {a['tokens_per_call']:>9,} "
|
|
145
|
+
f"{share:>16}{note}")
|
|
146
|
+
print(f"\nwrote {OUT}")
|
|
147
|
+
print("Tokens, not money: most of what we call is free or flat-rate, and "
|
|
148
|
+
"a made-up price is a number nobody can check.")
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
if __name__ == "__main__":
|
|
152
|
+
main()
|