Repository navigation
feat(budget): surface cached/cache-write/reasoning token counts on last_call (§3) - #377
Merged
Merged
Conversation
…st_call (§3)
Parse the per-call usage token detail counts from the live proxy response
body — `usage.input_tokens_details.{cached_tokens,cache_write_tokens}` and
`usage.output_tokens_details.reasoning_tokens` — and surface them (with the
totals) on `donkey.last_call` and as OTel span attributes. Closes #307.
The usage shape is already live-verified (§2), so this is a consumption task,
not a verification one: no UnverifiedValueWarning on the read path. Both the
Responses-API (`input_tokens*`) and Chat-Completions (`prompt_tokens*`) wire
shapes are read via one shared `parse_usage`/`usage_from_response` in
`core/lastcall.py`.
- `LastCall` gains six `int | None` usage fields; `from_response` fills them
from the body. Absent detail fields (or an absent `usage` object) report
`None`, never a fabricated `0` — a real `0` is a distinct, kept observation.
- `observe_usage()` merges the streamed terminal-event counts into the OBSERVED
record on stream finalize (the body is unread at `_on_response` time).
- `build_genai_attributes` emits the three detail counts under the stable
`donkey.usage.*` namespace (the semconv pins no key for them at the pinned
version); allowlisted so `span()` cannot drop them. `gen_ai.usage.*` unchanged.
- Carried identically on async + sync transports and the streaming SSE path.
Tests: fixture-driven parse test uses `responses.success.body.json` verbatim
(detail counts are all `0` there — exercises the 0-vs-None contract directly);
async + sync last_call fill; end-to-end streaming span + last_call fill from a
terminal usage event; no-UnverifiedValueWarning assertion.
Docs (cross-surface lockstep): docs/verified-apis.md §2/§3, website
observability / telemetry / attribution pages. TypeScript parity is deferred to
Phase 5 (the TS SDK is not yet shipped) — a recorded, intentional deferral.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This was referenced Sep 15, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #307.
What
Surfaces the per-call usage token detail counts from the live LLM-proxy
response body on
donkey.last_calland as OTel span attributes:cached_tokens,cache_write_tokens(fromusage.input_tokens_details)reasoning_tokens(fromusage.output_tokens_details)input_tokens/output_tokens/total_tokensWhy this is a consumption task, not a verification one
The
usageshape is already live-verified (docs/verified-apis.md §2,responses.success.body.json). This PR reads an already-confirmed shape, sothere is no
UnverifiedValueWarningon the read path (asserted by test).Design
parse_usage/usage_from_response/usage_mappingincore/lastcall.pyreads both the Responses-API (input_tokens*) andChat-Completions (
prompt_tokens*/completion_tokens*) wire shapes.LastCallgains sixint | Noneusage fields;from_responsefills them.An absent detail field (or an absent
usageobject entirely) reportsNone,never a fabricated
0— a real0is a distinct, kept observation.observe_usage()merges the streamed terminal-event counts into theOBSERVED record on stream finalize (the body is unread at
_on_response).build_genai_attributesemits the three detail counts under the stabledonkey.usage.*namespace — the semconv pins no key for them at the pinnedversion — and they are allowlisted so
span()cannot silently drop them.gen_ai.usage.*is unchanged.(attach-to-the-hook, per
BG §1.6; nosend()override).Tests
responses.success.body.jsonverbatim(its detail counts are all
0, which exercises the 0-vs-Nonecontract).last_callfill; end-to-end streaming testasserting the span
donkey.usage.*attrs andlast_callfill from a terminalusage event; identity-only response leaves usage
None.UnverifiedValueWarningassertion on the read path.Docs (cross-surface lockstep)
docs/verified-apis.md§2 (detail sub-fields named on the token-accountingrow) and §3 (a paragraph recording the budget: surface cached and reasoning token counts from the live usage payload #307 surfacing).
website/: observability (span-attribute table + streaming note), telemetry(span example + prose), attribution (usage-block field list).
shipped) — a recorded, intentional deferral, not a silent omission.
Gates
pytest -q(372 passed, 9 skipped),mypy --strictclean,ruff check .clean,
lint-imports4/4 KEPT.🤖 Generated with Claude Code