Repository navigation
feat(usage): server-side per-model top keys for tag/org/customer/agent and aggregated User Usage - #43175
Conversation
|
I'll fix CI failures and address comments from users with write access. I'll skip comments containing "(aside)".
|
|
|
Codecov Report❌ Patch coverage is 📢 Thoughts on this report? Let us know! |
|
yuneng-berri
left a comment
There was a problem hiding this comment.
- Spend Per User is always empty for admins viewing all users, since /user/daily/activity/aggregated never sets include_entity_breakdown to true
- nit: user usage used to page through every key. On /aggregated it now caps at the top 100 keys across the whole proxy
|
Point 1 fixed in c20e66b: /user/daily/activity/aggregated now sets include_entity_breakdown and resolves user emails, with a red-then-green regression test |
|
Point 2 is intentional: same cap Team and Tag usage already have. The truncation note, key search, model top keys and export reach the tail |
yuneng-berri
left a comment
There was a problem hiding this comment.
the per key widgets on the user usage tab are still limited to the top N keys, can you verify?
|
@yuneng-berri verified, yes: the aggregated route caps keys at USAGE_TOP_API_KEYS_LIMIT like Team/Tag. Search, model top keys and export cover the tail. Want the cap lifted for users? |
f1ae967 to
0d68934
Compare
|
@greptileai please re-review at 0d68934. The stack was re-squashed onto the current parent branch so the earlier 5/5 (f1ae967) is no longer an ancestor of head; the PR-scoped diff is content-identical apart from helper renames that moved into the parent |
0d68934 to
91c6199
Compare
|
@greptileai please re-review at 91c6199. Only change since 0d68934: regenerated ui/litellm-dashboard/src/lib/http/schema.d.ts (adds the /agent/daily/activity/aggregated/model_top_keys route types), folded into the single commit. |
91c6199 to
20a821f
Compare
|
@greptileai please re-review at 20a821f. Changes since 91c6199: removed the redundant test docstrings you flagged, dropped two unused imports in team_endpoints.py (repo-wide lint), and refreshed tests/code_coverage_tests/unbounded_in_baseline.txt for renamed functions (same queries, new fingerprints). |
…t and aggregated User Usage Resolves LIT-8647 Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
20a821f to
6f8eaee
Compare
|
@greptileai please re-review at 6f8eaee. Only change since 20a821f: dropped one stale line from tests/code_coverage_tests/unbounded_in_baseline.txt (the query was already migrated to find_many_in on main, which the CI merge ref includes). |
TLDR
Problem this solves:
/user/daily/activity?page=Nand had no model top keys or searchHow it solves it:
/{tag,organization,customer,agent}/daily/activity/aggregated/model_top_keysmirroring the team routeUSAGE_MODEL_TOP_API_KEYS_LIMITkeys/user/daily/activity/exportno longer 500s (the stacked branches disagreed on the row field name)/user/daily/activity/aggregatedfolds the per-user rollup and user emails intobreakdown.entities, so the admin Spend Per User chart is populatedStacked on #43137 (user/team model top keys), #43138 (user export) and #43141 (entity aggregated/search/export routes). The base branch
litellm_lit_8647_stack_baseis main plus those three merged, so the diff shown here is only this PR's work. Retarget to main once the three parents landUser Flow
Before: an admin with
USAGE_TOP_API_KEYS_LIMIT=2looks at tag-b'sgpt-4ospend and sees keys that never used tag-bgpt-4o: Top Virtual Keys shows key-1 ($100) and key-2 ($90), the two global leaders, and never key-3 or key-4Not FoundAfter: the same admin sees every key ranked by its spend on that model, scoped to the selected tag
gpt-4o: the browser calls GET /tag/daily/activity/aggregated/model_top_keys?model=gpt-4o and lists key-1, key-2, key-3, key-4 by spendBefore (tag view,
gpt-4oexpanded, capped to the two global leaders):After (same page, same data, all four keys ranked server side):
Linear ticket
Resolves LIT-8647
Pre-Submission checklist
Please complete all items before asking a LiteLLM maintainer to review your PR
uv run pytest tests/test_litellm/<your_test_file>.py -v. Leave the suites (make test-unit-*,make test-unit) to CI: it finishes in ~15 minutes where a laptop takes an hour or more@greptileaito re-request a review after pushing changes)Screenshots / Proof of Fix
The stack was re-squashed onto the current parent branch, so the commit ids named below are the original proof commits; the current head is a content-identical squash plus regenerated
schema.d.ts, unused-import cleanup inteam_endpoints.py, and anunbounded_in_baseline.txtrefresh for renamed functions. Greptile 5/5 judged the current headSetup: proxy from
litellm/proxy/dev_config.yamlagainst local Postgres withUSAGE_TOP_API_KEYS_LIMIT=2, dashboard vianpm run dev. Seed: four virtual keys (key-1..key-4) owned by userlit8647-user. tag-a: key-1gpt-4o$100, key-2gpt-4o$90. tag-b: key-3gpt-4o$5, key-4gpt-4o$4, key-3gpt-4o-mini$50. So the tag-bgpt-4okeys sit outside the global top twoBefore (60ec99a)
Tag model top keys route
curl -s -o /dev/null -w "%{http_code}\n" "localhost:4000/tag/daily/activity/aggregated/model_top_keys?model=gpt-4o&tags=tag-b&start_date=2026-09-20&end_date=2026-09-26" -H "Authorization: Bearer $LITELLM_MASTER_KEY"404(same for/organization,/customer,/agent)Tag aggregate key cap
curl -s "localhost:4000/tag/daily/activity/aggregated?start_date=2026-09-20&end_date=2026-09-26" -H "Authorization: Bearer $LITELLM_MASTER_KEY" | jq -c '{keys: [.results[].breakdown.api_keys | keys[]] | unique, api_key_limit: .metadata.api_key_limit, total_api_keys: .metadata.total_api_keys}'"api_key_limit":2,"total_api_keys":4Tag UI, gpt-4o expanded
gpt-4oUser Usage requests
/user/daily/activity?start_date=2026-09-18&end_date=2026-09-25&page_size=1000&page=1&timezone=0After (a29426e)
Tag model top keys route
for e in tag organization customer agent; do curl -s "localhost:4000/$e/daily/activity/aggregated/model_top_keys?model=gpt-4o-mini&start_date=2026-09-20&end_date=2026-09-26" -H "Authorization: Bearer $LITELLM_MASTER_KEY" | jq -c "{route: \"$e\", limit, keys: [.api_keys[]? | {alias: .key_alias, spend}]}"; donecurl -s "localhost:4000/tag/daily/activity/aggregated/model_top_keys?model=gpt-4o&start_date=2026-09-20&end_date=2026-09-26" -H "Authorization: Bearer $LITELLM_MASTER_KEY" | jq -c '{limit, keys: [.api_keys[] | {alias: .key_alias, spend}]}'{"limit":5,"keys":[{"alias":"key-1","spend":100},{"alias":"key-2","spend":90},{"alias":"key-3","spend":5},{"alias":"key-4","spend":4}]}curl -s "localhost:4000/tag/daily/activity/aggregated/model_top_keys?model=gpt-4o&tags=tag-b&start_date=2026-09-20&end_date=2026-09-26" -H "Authorization: Bearer $LITELLM_MASTER_KEY" | jq -c '{limit, keys: [.api_keys[] | {alias: .key_alias, spend}]}'{"limit":5,"keys":[{"alias":"key-3","spend":5},{"alias":"key-4","spend":4}]}curl -s -w " HTTP %{http_code}\n" "localhost:4000/tag/daily/activity/aggregated/model_top_keys?model=gpt-4o&start_date=2026-09-27&end_date=2026-09-20" -H "Authorization: Bearer $LITELLM_MASTER_KEY"{"detail":{"error":"end_date must be on or after start_date"}} HTTP 400Tag aggregate key cap
curl -s "localhost:4000/user/daily/activity/aggregated?user_id=lit8647-user&start_date=2026-09-20&end_date=2026-09-26" -H "Authorization: Bearer $LITELLM_MASTER_KEY" | jq -c '.metadata | {page, total_pages, has_more, api_key_limit, total_api_keys}'{"page":1,"total_pages":1,"has_more":false,"api_key_limit":2,"total_api_keys":4}(the aggregate stays capped by design; per-model ranking now comes from the route above)Tag UI, gpt-4o expanded
gpt-4o/tag/daily/activity/aggregated/model_top_keys?start_date=2026-09-18&end_date=2026-09-25&timezone=0&model=gpt-4o&group_by=model_groupgpt-4o-minilists key-3 $50.00User Usage requests
/user/daily/activity/aggregated?start_date=2026-09-18&end_date=2026-09-25&timezone=0and, on expandinggpt-4o,/user/daily/activity/aggregated/model_top_keys?...&model=gpt-4o&group_by=model_group. No/user/daily/activity?page=requestkey-4issues/user/daily/activity/aggregated/search?...&search=key-4and shows key-4 $4.00User export (92c7bba)
curl -s -w "\nHTTP %{http_code}\n" -H "Authorization: Bearer $LITELLM_MASTER_KEY" "localhost:4000/user/daily/activity/export?start_date=2026-09-18&end_date=2026-09-25&export_type=daily_with_keys&user_id=lit8647-user"Reviewer fix: Spend Per User empty for admins (c20e66b, 5a1e4d0, faa1a23)
Yuneng flagged that
/user/daily/activity/aggregatednever asked for the per-user rollup, so the admin all-users view rendered "No user spend data". c20e66b folds the per-user rollup plus user email/alias intobreakdown.entitieson that route. 5a1e4d0 makes that rollup key-free for the user route (one row per user per day,GROUP BY date, user_id, no per-key grouping set), so it scales with the user count only, and types the resolver asMapping[str, _UserEmailMetadata]. Because that rollup no longer carries per-key rows, faa1a23 routes User Usage exports through/user/daily/activity/exporteven when no key truncation applies, so the daily-with-keys and daily-with-models CSVs keep every key (verified live: the export CSV lists key-1 100, key-2 90, key-3 55, key-4 4). 2add5a5, the current head, covers the key-free rollup end to end in tests. Same page, same data, same URL (http://localhost:3000/usage, User Usage, all users, 2026-09-18 to 2026-09-25), proxy withUSAGE_TOP_API_KEYS_LIMIT=2Before (92c7bba, Spend Per User chart and table empty despite $249 total spend):
After (c20e66b, lit8647-user at $249; the Spend Per User table only reads the per-user metrics, which 5a1e4d0 leaves unchanged):
Before, same request against the old head:
curl -sS -H "Authorization: Bearer $LITELLM_MASTER_KEY" "localhost:4001/user/daily/activity/aggregated?start_date=2026-09-18&end_date=2026-09-25&timezone=0" | jq '.results[0].breakdown.entities'{}(HTTP 200,metadata.total_spend249,api_key_limit2)After:
curl -s -H "Authorization: Bearer $LITELLM_MASTER_KEY" "localhost:4000/user/daily/activity/aggregated?start_date=2026-09-18&end_date=2026-09-25" | jq '.results[0].breakdown.entities'(at 5a1e4d0; at c20e66b the same bucket also carried the four keys under
api_key_breakdown, which the dashboard never read)The second point (the aggregated route caps keys at the global top
USAGE_TOP_API_KEYS_LIMIT) is intentional and matches Team and Tag usage: the truncation notice, key search, per-model top keys and export reach the keys past the capType
🆕 New Feature
Caveats (if any)
Medium
401from the sibling customer routes rather than403Low
ENTITY_FETCH_FNSandENTITY_AGGREGATED_FETCH_FNSkeep their pre-existinganycallable types when moved intoentityFetchFns.tsFinal Attestation
Link to Devin session: https://app.devin.ai/sessions/cfa33299feca496383a95283e4e365a4
Open in Devin Desktop: https://app.devin.ai/desktop/session/cfa33299feca496383a95283e4e365a4?variant=devin
Requested by: @yassin-berriai
Link to Devin session: https://app.devin.ai/sessions/c0361ef9973f4f409b51af4495e3099f
Open in Devin Desktop: https://app.devin.ai/desktop/session/c0361ef9973f4f409b51af4495e3099f?variant=devin