feat: per-session cost and usage telemetry (#357) #494
Loading…
Reference in a new issue
No description provided.
Delete branch "feat/357-usage-telemetry"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Closes #357. Also lands the "cache hints" half #351 deferred here:
Usage.cached_tokensis now read from Anthropic (cache_read + cache_creation) and OpenAI (prompt_tokens_details.cached_tokens).What
b6c7d8e9fa0b(aftera5b6c7d8e9fa, single head): tablesession_usage, one row per processing run — audio seconds submitted and transcribed (the VAD lever), ASR/LLM provider and model, LLM calls / prompt / completion / cached tokens, ASR and LLM phase wall time, retry count,asr_cost_usd/llm_cost_usd(nullable),cost_is_estimate,pricing_as_of,status. Up/down/up verified on PostgreSQL 16.app/services/usage_meter.py: aContextVarscope opened byprocess_audioaround its LLM phases;llm_service._check_usage(the funnel every transport calls) records each call into the active meter and is inert otherwise. Scope widening, deliberate: the four_summarise_*prose transports did not go through_check_usage— they open-coded the truncation checks — so the session summary itself would have gone uncounted. They now route through the funnel viaaudio_service._prose_usage, with truncation behaviour unchanged.app/services/ai_pricing.py, list rates per 1M tokens for the Anthropic and OpenAI families the registry knows, longest-prefix match,PRICING_AS_OF = 2026-05-01(the horizon the table can vouch for; it renders on the panel so staleness is visible). Self-hosted →None, never0, which is what lets the UI show compute time instead.PROMPT_TOKENS_INCLUDE_CACHEDhandles the fact that OpenAI's prompt count includes the cached share and Anthropic's does not.GET /api/admin/ai/usage?days=(sessions, per-campaign rollups with VAD reduction, per-month line) and/usage/campaigns/{id}. This is what answers "what does a customer cost per month".AdminAiUsageunder the capability panel: by campaign, by month, run by run; every dollar figure badged "estimated"; self-hosted rows show GPU/CPU minutes.process_audio's body has no test in the repo, so the instrumentation was exercised with a throwaway eager run against Postgres with faked providers — success and failure paths each wrote one row with exact arithmetic (script not committed; a committed integration test for this task is worth a follow-up).Rebased onto main after #359 (changelog conflict, both entries kept).
Verification
alembic heads→ one head. Version sync OK; no bot contract change.🤖 Generated with Claude Code
Two admin-only endpoints over the rows the pipeline now writes. `GET /api/admin/ai/usage?days=30` answers the question a hosted tier cannot be priced without — what does a customer cost per month — with a campaign rollup, a monthly line and the individual runs behind them; `/usage/campaigns/{id}` narrows the same shape to one group. Every dollar figure carries `cost_is_estimate` and the rate table's date, and rows from a self-hosted stack carry no dollars at all: `est_cost_usd` is null and `compute_seconds` is the number that means something. The two travel together rather than one being derived from the other, so a client cannot render "$0.00" for a deployment that has an electricity bill instead. Empty lists rather than a 404 on a deployment that has processed nothing — "nothing yet" is a legitimate answer and the page should render it. The campaign endpoint 404s only on a campaign that does not exist. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>