fix(insights): include auxiliary usage in overview token totals (#65603)

The overview's total_input/output/cache token counts summed only the
sessions counters (main-loop usage), while the per-model breakdown
already included auxiliary usage rows (task dimension from #65537) and
reconciled residuals. Result: hermes insights top-line totals
undercounted aux spend (compression summarizer, vision, titles) and
disagreed with the per-model table below them — the symptom reported
in #58592 and requested in #9979.

When the per-model breakdown is available, derive the overview token
totals from it (same pattern total_cost already used). Verified no
double-count across incremental CLI deltas, gateway absolute
overwrites, and aux rows.
This commit is contained in:
Teknium 2026-07-16 05:39:33 -07:00 committed by GitHub
parent 07e537d8ea
commit 21dedb8586
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 58 additions and 0 deletions

View file

@ -439,6 +439,18 @@ class InsightsEngine:
if models:
total_cost = sum(float(m.get("cost") or 0.0) for m in models)
# Token totals likewise: the per-model breakdown includes
# auxiliary usage rows (vision/compression/titles — task
# dimension in session_model_usage, #23270) plus reconciled
# residuals, while the sessions counters carry main-loop usage
# only. Summing the breakdown keeps overview totals consistent
# with the per-model table and stops `hermes insights`
# undercounting aux spend (#58592, #9979).
total_input = sum(int(m.get("input_tokens") or 0) for m in models)
total_output = sum(int(m.get("output_tokens") or 0) for m in models)
total_cache_read = sum(int(m.get("cache_read_tokens") or 0) for m in models)
total_cache_write = sum(int(m.get("cache_write_tokens") or 0) for m in models)
total_tokens = total_input + total_output + total_cache_read + total_cache_write
# Session duration stats (guard against negative durations from clock drift)
durations = []

View file

@ -340,3 +340,49 @@ class TestAnalyticsAuxRows:
tasks = _aux_task_summary(aux)
assert {t["task"] for t in tasks} == {"vision", "compression"}
class TestInsightsAuxTotals:
def test_overview_totals_include_aux_usage(self, db):
"""`hermes insights` overview must count aux tokens, not just the
sessions counters (issues #58592, #9979)."""
from agent.insights import InsightsEngine
db.create_session("s1", source="cli")
db.update_token_counts(
"s1", input_tokens=1000, output_tokens=100,
model="main-model", billing_provider="nous", api_call_count=1,
)
db.record_auxiliary_usage(
"s1", "compression", model="glm-5",
billing_provider="openrouter", input_tokens=5000, output_tokens=500,
)
report = InsightsEngine(db).generate(days=30)
ov = report["overview"]
assert ov["total_input_tokens"] == 6000
assert ov["total_output_tokens"] == 600
models = {m["model"] for m in report["models"]}
assert {"main-model", "glm-5"} <= models
def test_overview_totals_not_double_counted_with_absolute_updates(self, db):
"""Gateway absolute overwrites + aux rows must not inflate totals."""
from agent.insights import InsightsEngine
db.create_session("s2", source="telegram")
db.update_token_counts(
"s2", input_tokens=2000, output_tokens=200,
model="main-model", billing_provider="nous", api_call_count=1,
)
db.update_token_counts(
"s2", input_tokens=2000, output_tokens=200,
model="main-model", billing_provider="nous",
absolute=True, api_call_count=1,
)
db.record_auxiliary_usage(
"s2", "title_generation", model="main-model",
billing_provider="nous", input_tokens=40, output_tokens=8,
)
report = InsightsEngine(db).generate(days=30)
ov = report["overview"]
assert ov["total_input_tokens"] == 2040
assert ov["total_output_tokens"] == 208