Skip to content

Add GET /v1/users/me/llm-usage/daily: per-day LLM usage drill-down#9061

Open
ZachL111 wants to merge 3 commits into
BasedHardware:mainfrom
ZachL111:zach/llm-usage-daily
Open

Add GET /v1/users/me/llm-usage/daily: per-day LLM usage drill-down#9061
ZachL111 wants to merge 3 commits into
BasedHardware:mainfrom
ZachL111:zach/llm-usage-daily

Conversation

@ZachL111

@ZachL111 ZachL111 commented Jul 5, 2026

Copy link
Copy Markdown
Contributor

What

Adds GET /v1/users/me/llm-usage/daily, returning the current user's LLM token usage for a single UTC day.

Why

The existing GET /v1/users/me/llm-usage returns only a 30-day aggregate (totals plus top features), so there is no way to inspect one day. The backing helper get_daily_usage is already unit-tested in the database layer but was never wired to a route. This exposes it.

Details

  • Returns per-feature token usage (input, output, total, call_count) for the requested day, with day-level totals and a has_data flag. date is an optional YYYY-MM-DD query param defaulting to today (UTC); a malformed date returns 400.
  • Reads only users/{uid}/llm_usage/{date} (self-scoped, authenticated), with no new database code.
  • Normalization mirrors the existing get_usage_summary: it sums only the nested {feature}.{model} token dicts, skips scalar meta fields, and drops zero-sum buckets for a clean payload.
  • Hosted in a small new router rather than the large routers/users.py where the sibling llm-usage routes live.

Test

backend/tests/unit/test_llm_usage_daily.py: no-data zeros with has_data=false, per-feature and total normalization, skipping of cost-only and zero-sum buckets, and a 400 for a malformed date (raised before any database access). 4 passed locally.

Review in cubic

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

2 issues found across 3 files

Confidence score: 3/5

  • In backend/routers/llm_usage.py, the daily usage normalization loop re-implements aggregation logic that already exists in database.llm_usage.get_usage_summary (and get_global_top_features), which creates a drift risk where usage totals or feature rankings can silently diverge after future rule changes — centralize this mapping in one shared helper before merging.
  • In backend/routers/llm_usage.py, declaring date as Optional[str] and parsing with datetime.strptime can accept non-canonical inputs (for %Y-%m-%d) and shifts validation out of Pydantic, which can lead to inconsistent request handling at the API boundary — switch to a typed date parameter (or stricter validation) before merging.

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread backend/routers/llm_usage.py Outdated
Comment thread backend/routers/llm_usage.py Outdated

@kodjima33 kodjima33 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Additive read endpoint (per-day LLM usage). Backend feature — approve only.

@ZachL111 ZachL111 force-pushed the zach/llm-usage-daily branch from 331f7fa to 4d89300 Compare July 5, 2026 14:45
@ZachL111

ZachL111 commented Jul 5, 2026

Copy link
Copy Markdown
Contributor Author

Addressed both. Extracted a shared _sum_model_tokens helper in database/llm_usage.py and routed both get_usage_summary and the new get_daily_usage_summary through it, so the per-feature token aggregation lives in one place and cannot drift. The router is now a thin delegator. Also switched the date query param to a typed date so Pydantic validates it at the boundary (a malformed value returns 422 instead of a hand-rolled 400). The existing get_usage_summary tests still pass, and I added tests for the shared helper, the daily summary, and the endpoint delegation.

@Git-on-my-level Git-on-my-level added needs-tests PR introduces logic that should be covered by tests needs-maintainer-review Needs human maintainer review before merge labels Jul 5, 2026

@Git-on-my-level Git-on-my-level left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the focused endpoint addition and for addressing the earlier validation/aggregation feedback.

I found one blocker before this can merge:

  • The new daily summary currently drops the bucket-based usage schema written by record_llm_usage_bucket. Those documents store buckets like desktop_chat as flat dicts (input_tokens, output_tokens, cache_read_tokens, total_tokens, cost_usd, call_count), but _sum_model_tokens() treats each inner scalar as a non-dict and returns zeros, so /v1/users/me/llm-usage/daily can report has_data=false and empty totals for real daily desktop/chat usage. Please either support both the nested per-model schema and the flat bucket schema in get_daily_usage_summary, or make the route explicitly scoped to the nested schema only if that is the intended product surface.

Also, the current Backend unit suite check is failing, so I’m leaving this as changes requested rather than approval. A regression test with a flat desktop_chat/bucket document would cover the issue above.

No security attack/supply-chain concern spotted, but because this is an authenticated user-usage/data endpoint it should get maintainer review once the behavior and checks are fixed.

ZachL111 added 3 commits July 5, 2026 11:48
The existing /v1/users/me/llm-usage returns only a 30-day aggregate; there was
no way to inspect a single day. Wire the tested-but-unwired get_daily_usage
helper to a read endpoint returning the caller's token usage for one UTC day,
by feature (input/output/total tokens plus call_count) with day-level totals
and has_data. New light router because the natural host routers/users.py is large.
…eview)

cubic flagged that the router re-implemented aggregation that already exists in
get_usage_summary (drift risk), and that the string date param moved validation
out of Pydantic. Extract a shared _sum_model_tokens helper (used by both
get_usage_summary and the new get_daily_usage_summary), move the daily
normalization into the database layer, and make the router a thin delegator with
a typed date query param (Pydantic returns 422 on a malformed date).
…count aliases

get_daily_usage_summary treated each scalar in a flat desktop_chat-style bucket as
a non-dict model and returned zeros, so /v1/users/me/llm-usage/daily could report
has_data=false and empty totals for real desktop/chat usage. Detect the flat bucket
schema (token fields on the feature dict) alongside the nested per-model schema, and
skip the "{bucket}_{account}" aliases that restate the same totals so they cannot
double-count. Adds regression tests for a mixed day and a bucket-only day.
@ZachL111 ZachL111 force-pushed the zach/llm-usage-daily branch from 4d89300 to 87944d7 Compare July 5, 2026 18:59
@ZachL111

ZachL111 commented Jul 5, 2026

Copy link
Copy Markdown
Contributor Author

Good catch, fixed in the latest push.

get_daily_usage_summary now normalizes both schemas that can share a daily document:

  • nested per-model features (chat, memory, ...), summed via the shared _sum_model_tokens as before
  • the flat cost buckets written by record_llm_usage_bucket (desktop_chat and similar), where the token fields sit directly on the feature dict

A feature is treated as a flat bucket when it carries input_tokens/output_tokens itself. One wrinkle worth calling out: record_llm_usage_bucket also dual-writes a per-account alias ({bucket}_{account}) that restates the same totals for a per-account breakdown, so naively summing every flat dict would double-count the primary. I keep a small set of primary bucket names (_PRIMARY_BUCKETS, currently {"desktop_chat"}) and skip the aliases; a new primary bucket would be added to that set.

Regression tests in test_llm_usage_daily.py cover:

  • a mixed day (nested chat + flat desktop_chat + its desktop_chat_omi alias): the bucket surfaces as its own feature, the alias is dropped, and the totals count each once
  • a bucket-only day: has_data is true and the totals reflect the bucket (this previously reported has_data=false)

On the current red check: that is the separate main-CI breakage the full unit suite is hitting right now (the dev-read rate-limit guard, tracked in #9065), not this change. The llm-usage suites here pass locally. Once #9065 lands I will rebase and it should go green.

@Git-on-my-level

Copy link
Copy Markdown
Collaborator

Thanks for the follow-up here. I re-reviewed the latest head (87944d7) and the earlier flat-bucket issue looks addressed: the daily summary now handles the nested per-model schema plus the desktop_chat flat bucket and avoids double-counting its per-account alias, with regression coverage for mixed and bucket-only days.

I’m not approving this yet because the PR still has maintainer-review/test guardrails active and the Backend unit suite is red. I checked the failing job and the visible failure is in the existing developer read rate-limit test, not in the new LLM-usage tests, but this still needs a green run/rebase before merge. Because this is an authenticated user-usage endpoint, a human maintainer should also confirm the product/API shape before landing.

No security attack or supply-chain concern spotted in this diff.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

needs-maintainer-review Needs human maintainer review before merge needs-tests PR introduces logic that should be covered by tests

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants