Files
Teknium 6d2645e64e feat(logging): the API call line carries cache write count, the provider's response id, and the serving upstream
Diagnosing the 1,393-agent run's cache misses took a state.db join and a live
probe because none of the three were on the one line we log per call:

  write=<n>    cache_creation tokens; a write costs 50x a read, so this is the
               money, and "read stuck, write large" on consecutive lines is a
               routing miss visible without a probe
  id=<id>      the provider's response id (Anthropic msg_..., OpenRouter/Nous
               gen-...): what a provider needs to look a request up
  upstream=<n> who actually served, when the route reports it (OpenRouter's
               `provider`); how we learned GMI was not serving

Fields are appended to the existing line and omitted when absent, so every
existing parser (evals/postmortem, the two cache_prefix probes) keeps
matching; the forensics parser reads them when present.

The streamed chat path fabricated `id="stream-<uuid>"` and dropped the
chunks' id/provider on the floor; it now keeps the first chunk's id and
provider, falling back to the fabricated id only when the stream never sent
one. Nothing depended on the prefix.

Live (Nous, Fable 5.1, both wires): chat -> `write=3759
id=gen-1788727882-... upstream=Anthropic`; native -> `id=gen-1788727891-...`
(the Anthropic Message object's id was already real). Tests: fields present
and omitted, prefix unchanged; forensics parser reads new and old lines.
2026-09-06 13:52:15 -07:00

148 lines
7.8 KiB
Python

"""Lane 1b: per-call cache behaviour from ``agent.log`` (OBSERVED provider usage per call).
Hermes logs one line per API call::
... INFO [<session_id>] agent.conversation_loop: API call #N: model=... in=<prompt> out=<out> total=... latency=..s cache=<hit>/<total> (pct) [write=<n>] [id=<response id>] [upstream=<name>]
Given the rotated logs, this reproduces: prompt-size distribution, cache hit-ratio buckets, the
"plateau" signature of a broken cache prefix (hit count stuck at the previous call's breakpoint while
``in`` grows), the share of uncached input those plateaus explain, and the sawtooth replay of an absolute
context cap on REAL per-call prompt sizes (the figure the tokens lane reported as -49%).
python -m evals.postmortem.forensics.logcalls --db state_copy.db --logs ~/.hermes/logs/agent.log*
Coverage caveat: logs rotate; report the fraction of the run's calls that were found before quoting
anything from this lane, and treat extrapolations as upper bounds.
"""
from __future__ import annotations
import collections
import glob
import re
import statistics
from typing import Any, Dict, List
from evals.postmortem.forensics.common import Run
_LINE = re.compile(
r"^(\S+ \S+) INFO \[(\S+)\] agent\.conversation_loop: API call #(\d+): model=(\S+) provider=\S+ "
r"in=(\d+) out=(\d+) total=\d+ latency=([\d.]+)s cache=(\d+)/(\d+)"
)
_WRITE = re.compile(r" write=(\d+)")
_ID = re.compile(r" id=(\S+)")
_UPSTREAM = re.compile(r" upstream=(.+?)(?: [a-z_]+=|$)")
def parse_logs(paths: List[str], sids: set) -> List[Dict[str, Any]]:
calls: List[Dict[str, Any]] = []
for path in paths:
try:
fh = open(path, encoding="utf-8", errors="replace")
except OSError:
continue
with fh:
for line in fh:
m = _LINE.match(line)
if m and m.group(2) in sids:
rec = {"ts": m.group(1), "sid": m.group(2), "n": int(m.group(3)), "model": m.group(4),
"inp": int(m.group(5)), "out": int(m.group(6)), "lat": float(m.group(7)),
"hit": int(m.group(8))}
# Newer lines (post 2026-09) also carry write= / id= / upstream=; optional.
for key, pat in (("write", _WRITE), ("id", _ID), ("upstream", _UPSTREAM)):
mm = pat.search(line)
if mm:
rec[key] = int(mm.group(1)) if key == "write" else mm.group(1)
calls.append(rec)
return calls
def sawtooth_real(by_sid: Dict[str, List[Dict[str, Any]]], cap: int, floor: int) -> float:
"""Replay on REAL prompt sizes: appended = in[i] - in[i-1]; compress to floor when above cap."""
total = 0.0
for calls in by_sid.values():
calls = sorted(calls, key=lambda c: c["n"])
ctx = None
for i, c in enumerate(calls):
appended = c["inp"] if i == 0 else max(0, c["inp"] - calls[i - 1]["inp"])
ctx = c["inp"] if ctx is None else ctx + appended
if ctx > cap:
ctx = float(floor)
total += ctx
return total
def main(argv=None) -> int:
ap = Run.parser((__doc__ or "").split("\n\n")[0])
ap.add_argument("--logs", nargs="+", required=True, help="agent.log files (globs ok)")
ap.add_argument("--cap", type=int, default=200_000)
ap.add_argument("--floor", type=int, default=65_000)
a = ap.parse_args(argv)
run = Run.open(a.db, root=a.root, out=a.out)
paths = sorted(p for g in a.logs for p in glob.glob(g))
calls = parse_logs(paths, set(run.in_run))
total_calls = run.summary()["api_calls"]
if not calls:
print("[logcalls] no matching API-call lines found in", paths); return 1
inp = [c["inp"] for c in calls]
hit_total, in_total = sum(c["hit"] for c in calls), sum(inp)
buckets = collections.Counter()
for c in calls:
r = c["hit"] / c["inp"] if c["inp"] else 0
buckets["<50%" if r < .5 else "50-90%" if r < .9 else "90-97%" if r < .97 else "97-99%" if r < .99 else ">=99%"] += 1
by_sid: Dict[str, List[Dict[str, Any]]] = collections.defaultdict(list)
for c in calls:
by_sid[c["sid"]].append(c)
# Two definitions, reported separately (an independent review caught them being conflated):
# strict plateau: hit count EXACTLY equal to the previous call's (prefix stuck at the old breakpoint)
# non-advancing: hit count did not grow while input did (includes partial misses of other causes)
strict_pairs = strict_uncached = loose_pairs = loose_uncached = pairs = uncached_total = 0
for cs in by_sid.values():
cs.sort(key=lambda c: c["n"])
for prev, cur in zip(cs, cs[1:]):
pairs += 1
u = max(0, cur["inp"] - cur["hit"]); uncached_total += u
if cur["inp"] > prev["inp"]:
if cur["hit"] == prev["hit"]:
strict_pairs += 1; strict_uncached += u
if cur["hit"] <= prev["hit"]:
loose_pairs += 1; loose_uncached += u
price = run.price_per_token
real = sum(inp); capped = sawtooth_real(by_sid, a.cap, a.floor)
report = {
"coverage": {"calls_found": len(calls), "run_api_calls": total_calls, "fraction": round(len(calls) / total_calls, 4) if total_calls else None,
"first_ts": min(c["ts"] for c in calls), "last_ts": max(c["ts"] for c in calls),
"note": "rotated logs; extrapolations from this window are upper bounds"},
"observed": {
"prompt_tokens": {"median": int(statistics.median(inp)), "p90": sorted(inp)[int(.9 * len(inp))], "max": max(inp)},
"share_of_calls_above": {t: round(sum(1 for x in inp if x > t) / len(inp), 3) for t in (150_000, 200_000, 300_000)},
"cache_hit_ratio_overall": round(hit_total / in_total, 4),
"hit_ratio_buckets": dict(buckets),
"strict_plateau": {"pairs_share": round(strict_pairs / pairs, 4) if pairs else None,
"share_of_uncached_input": round(strict_uncached / uncached_total, 4) if uncached_total else None},
"non_advancing_hit": {"pairs_share": round(loose_pairs / pairs, 4) if pairs else None,
"share_of_uncached_input": round(loose_uncached / uncached_total, 4) if uncached_total else None},
"uncached_input_usd_in_window": round(uncached_total * price["cache_write_tokens"], 2),
},
"modeled": {
"sawtooth_on_real_prompt_sizes": {
"cap": a.cap, "floor": a.floor, "prompt_tokens_ratio": round(capped / real, 3) if real else None,
"caveats": ["compression-call cost excluded", "read/write ratio assumed unchanged", "window only"],
}
},
}
path = run.write("logcalls.json", report)
o = report["observed"]
print(f"[logcalls] coverage {len(calls):,}/{total_calls:,} calls ({report['coverage']['fraction']:.1%}) from {len(paths)} file(s)")
print(f"[logcalls] OBSERVED median prompt {o['prompt_tokens']['median']:,} p90 {o['prompt_tokens']['p90']:,}; >200K: {o['share_of_calls_above'][200000]:.0%}; hit ratio {o['cache_hit_ratio_overall']:.1%}")
print(f"[logcalls] OBSERVED strict plateau (hit unchanged): {o['strict_plateau']['pairs_share']:.1%} of pairs, {o['strict_plateau']['share_of_uncached_input']:.1%} of uncached input; "
f"non-advancing hit: {o['non_advancing_hit']['pairs_share']:.1%} / {o['non_advancing_hit']['share_of_uncached_input']:.1%}; uncached in window ${o['uncached_input_usd_in_window']:,}")
print(f"[logcalls] MODELED sawtooth cap {a.cap}/{a.floor} on real sizes: prompt volume x{report['modeled']['sawtooth_on_real_prompt_sizes']['prompt_tokens_ratio']}")
print(f"[logcalls] wrote {path}")
return 0
if __name__ == "__main__":
raise SystemExit(main())