@wendellresearch-ai/benchwarmers 0.2.1 → 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/metrics/cost.py CHANGED
@@ -1,10 +1,13 @@
1
- """What each model actually costs us, from the `llm` CLI's own log.
1
+ """What each model actually costs us, from the bench's usage ledger and the `llm` CLI's log.
2
2
 
3
3
  python metrics/cost.py
4
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.
5
+ TWO SOURCES, NO CALL IN BOTH. The built-in client writes a line for every
6
+ call to the usage ledger (metrics/ledger.py, RELEASE-PLAN Phase 2b); calls
7
+ made through the `llm` backend, and every call before the switch, are in
8
+ llm's own logs.db, which it has kept all along (382 turns with token counts
9
+ were on disk before this file existed). The built-in client never writes
10
+ logs.db and `llm` never writes the ledger, so the two are added up.
8
11
 
9
12
  WHY REASONING TOKENS MATTER SEPARATELY
10
13
  --------------------------------------
@@ -77,33 +80,49 @@ def benchmark_calls() -> dict[str, int]:
77
80
  return {}
78
81
 
79
82
 
80
- def collect() -> dict:
81
- """Per-model totals, with old aliases folded into their current name."""
83
+ def _llm_turns():
84
+ """(model, input, output, reasoning or None, duration_ms) per turn in llm's log."""
82
85
  if not LOGS.exists():
83
- return {}
84
- rename = alias_history()
86
+ return
85
87
  db = sqlite3.connect(f"file:{LOGS}?mode=ro", uri=True)
86
88
  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
89
  for row in db.execute(
92
90
  "SELECT model, input_tokens, output_tokens, token_details,"
93
91
  " 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
92
  try:
102
93
  details = json.loads(row["token_details"] or "{}") or {}
103
94
  except ValueError:
104
95
  details = {}
105
96
  reasoning = ((details.get("completion_tokens_details") or {})
106
97
  .get("reasoning_tokens"))
98
+ yield (row["model"], row["input_tokens"], row["output_tokens"], reasoning,
99
+ row["duration_ms"])
100
+
101
+
102
+ def _ledger_calls():
103
+ """The same, per answered call in the usage ledger. Refused and failed
104
+ calls spent nothing, as llm's log (which keeps only answers) counts."""
105
+ import ledger
106
+ for row in ledger.rows():
107
+ if row.get("status") in ("ok", "empty"):
108
+ yield (row.get("alias") or "?", row.get("tokens_in"), row.get("tokens_out"),
109
+ row.get("reasoning"), round((row.get("seconds") or 0) * 1000))
110
+
111
+
112
+ def collect() -> dict:
113
+ """Per-model totals, with old aliases folded into their current name."""
114
+ rename = alias_history()
115
+ agg: dict[str, dict] = collections.defaultdict(
116
+ lambda: {"calls": 0, "input": 0, "output": 0, "reasoning": 0,
117
+ "duration_ms": 0, "with_reasoning_data": 0})
118
+ for name, tokens_in, tokens_out, reasoning, ms in (*_llm_turns(), *_ledger_calls()):
119
+ # Turns predating the versioned rename carry the old alias.
120
+ model = rename.get(name, name)
121
+ a = agg[model]
122
+ a["calls"] += 1
123
+ a["input"] += tokens_in or 0
124
+ a["output"] += tokens_out or 0
125
+ a["duration_ms"] += ms or 0
107
126
  if reasoning is not None:
108
127
  a["reasoning"] += reasoning
109
128
  a["with_reasoning_data"] += 1