her / engine /core /tokens.py
geekwrestler's picture
Squash history (purge pre-scrub demo session blobs)
5f43c7d
Raw
History Blame Contribute Delete
4.97 kB
"""tokens.py — session token rollup.
The per-turn rollup already happens in the loader (sum of message.usage across the
turn's assistant rows). This module sums turns → session totals and ALWAYS exposes
the cacheRead/out ratio (the cost-driver headline: cacheRead dominates and is the
"expensive because big, not wasteful" signal). Pure code, NO model.
"""
from __future__ import annotations
from typing import Any
from engine.contract import Tokens
# Context-WINDOW tiers (the GAUGE denominator) — ascending known Claude windows. The
# JSONL records only a bare model id (e.g. "claude-opus-4-8"), never the 1M-beta "[1m]"
# suffix or a window field, so the tier can't always be read off the name. We therefore
# pick the SMALLEST known tier that fits the session's observed peak occupancy, capped
# at what the model family supports (Haiku tops out at 200k; Opus/Sonnet can opt into
# the 1M beta). Denominator stays honest: never smaller than what the data proves was
# used, never larger than the model can do. NOT hardcoded to 1M.
CONTEXT_TIERS = (200_000, 1_000_000)
def _model_max_window(model: str | None) -> int:
"""The largest context window the model family supports."""
m = (model or "").lower()
if "haiku" in m:
return 200_000 # Haiku has no 1M tier
return 1_000_000 # Opus / Sonnet (and unknown) can run the 1M beta
def context_limit(model: str | None, peak: int) -> int:
"""Gauge denominator: the smallest known tier that fits `peak`, capped at the model
family's max. Underfilled sessions read against the standard 200k window (the common
case); a peak above 200k proves the 1M tier was in use. If peak exceeds every tier
the model supports, returns the cap (and the caller's overLimit guard flags it)."""
cap = _model_max_window(model)
for tier in CONTEXT_TIERS:
if tier > cap:
break
if tier >= peak:
return tier
return cap
# A compaction shows up as a sharp DROP in occupancy: a new prompt can only ADD to the
# window, and cache-TTL expiry merely shifts cacheRead↔input without lowering the total,
# so the only things that pull occupancy down are /compact, /clear, or an auto-compact.
# Flag a drop ONLY once the window was substantially full, so small turns never read as
# compactions.
_COMPACT_FLOOR = 100_000 # occupancy must have been at least this full first
_COMPACT_RATIO = 0.6 # ...and then fell below 60% of that
def session_tokens(turns) -> Tokens:
"""Sum per-turn token rollups into a session total."""
total = Tokens()
for t in turns:
total = total.add(t.tokens)
return total
def cache_read_ratio(tokens: Tokens) -> float:
"""cacheRead / out. Always reported. 0.0 when out == 0 (avoid div-by-zero)."""
if not tokens.out:
return 0.0
return tokens.cacheRead / tokens.out
def rollup(turns) -> dict[str, Any]:
"""Session token summary: totals + the always-present cacheRead/out ratio."""
total = session_tokens(turns)
return {
"tokens": total.to_dict(),
"cacheReadOverOut": cache_read_ratio(total),
}
def context_window(turns, model: str | None = None) -> dict[str, Any]:
"""Point-in-time context-WINDOW occupancy — the "fuel gauge", NOT the cumulative
token sums in rollup(). Returns the peak fill, the model-aware window `limit` (see
context_limit — NOT hardcoded), a per-turn trajectory, and any inferred compactions
(sharp occupancy drops). `overLimit` is the genuinely-suspect case: a request whose
occupancy exceeds the model's window (physically impossible — if it appears, the
source data or parse is wrong). Pure code, NO model inference at runtime."""
traj: list[dict[str, int]] = []
peak = 0
for t in turns:
if not t.ctxPeak: # no main-thread usage seen for this turn
continue
traj.append({"i": t.i, "start": t.ctxStart, "peak": t.ctxPeak, "end": t.ctxEnd})
peak = max(peak, t.ctxPeak)
limit = context_limit(model, peak)
over_limit: list[int] = [t.i for t in turns if t.ctxPeak and t.ctxPeak > limit]
# compaction = a sharp drop, BETWEEN turns (prev end → this start) or WITHIN a
# turn (this peak → this end), once the window was substantially full.
compactions: list[dict[str, int]] = []
prev_end = 0
for e in traj:
if prev_end > _COMPACT_FLOOR and e["start"] < prev_end * _COMPACT_RATIO:
compactions.append({"atTurn": e["i"], "before": prev_end, "after": e["start"]})
elif e["peak"] > _COMPACT_FLOOR and e["end"] < e["peak"] * _COMPACT_RATIO:
compactions.append({"atTurn": e["i"], "before": e["peak"], "after": e["end"]})
prev_end = e["end"]
return {
"limit": limit,
"peak": peak,
"peakPct": (peak / limit) if limit else 0.0,
"trajectory": traj,
"compactions": compactions,
"overLimit": over_limit,
}