Repository navigation
feat(usage): search team keys beyond the top-N in the Team usage view - #42857
Conversation
The Key Activity panel on the Usage page only searched the top-spend keys the aggregated endpoint loads. A new /user/daily/activity/aggregated/search route matches verification tokens by hash, alias, or user id and aggregates spend for the matches; the panel debounces to it when the loaded set is truncated. Entity panels stay local-only. Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
…ote results Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Adds GET /team/daily/activity/aggregated/search, which resolves a search term against the verification tokens the caller may see (team membership and own-key scoping applied inside the Prisma where before take) and aggregates litellm_dailyteamspend for the matched hashes. The Team usage view passes a searchKeys callback to KeyActivityPanel so keys outside the loaded top-N are found server side Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
|
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! |
…rch call under max-params Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
… where Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
…atrix Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
… take Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
|
bugbot run |
…mary Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
… global spend rollup code from rc/1.104.0 (#43385) * Revert "feat(proxy): server-side Team Usage export beyond the top-N key cap (#42996)" This reverts commit 77eccac. * Revert "feat(usage): search team keys beyond the top-N in the Team usage view (#42857)" This reverts commit c2eb549. * Revert "feat(usage): search keys beyond the top-N usage subset (#42827)" This reverts commit 5a8ec13. * Revert "Merge pull request #41324 from BerriAI/litellm_daily_global_spend_table" Removes the daily global spend rollup job and its reads. Keeps the LiteLLM_DailyGlobalSpend model and migration so databases that already applied it are untouched and no new proxy-extras version is needed. This reverts commit 87694c2, reversing changes made to its first parent. * Revert "Merge pull request #41293 from BerriAI/litellm_usage_key_free_aggregate_split" Restores the aggregated usage query without the top-N key cap, so the Usage pages, per-key widgets and exports cover every key again. CacheLeakageCard keeps the date picker removal from #42055. This reverts commit 2e46b10, reversing changes made to its first parent. * chore: update Next.js build artifacts (2026-09-27 00:33 UTC, node v24.19.0)
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
…sage view (#42857)" (#43377) * Revert "feat(usage): search team keys beyond the top-N in the Team usage view (#42857)" * revert: "feat(usage): search keys beyond the top-N usage subset (#42827)" (#43378) * Revert "feat(usage): search keys beyond the top-N usage subset (#42827)" * revert: "feat(proxy): add LiteLLM_DailyGlobalSpend key-free rollup for the usage dashboard (#41324)" (#43595) * Revert "Merge pull request #41324 from BerriAI/litellm_daily_global_spend_table" * Revert "Merge pull request #41293 from BerriAI/litellm_usage_key_free_aggregate_split" (#43596) Co-authored-by: yassin <yassin@berri.ai> --------- Co-authored-by: yassin <yassin@berri.ai> Co-authored-by: devin-ai-integration[bot] <158243242+devin-ai-integration[bot]@users.noreply.github.com> --------- Co-authored-by: devin-ai-integration[bot] <158243242+devin-ai-integration[bot]@users.noreply.github.com> --------- Co-authored-by: devin-ai-integration[bot] <158243242+devin-ai-integration[bot]@users.noreply.github.com>
What's the problem?
The Team usage page only loads the top 100 keys by spend for the selected teams and the key search box filters that loaded page in the browser. A team key that has spend in the range but sits below the top 100 can never be found, even when you paste its exact hash or alias. This is the Team view sibling of LIT-8497 (fixed for the user view in #42827)
What's the solution?
Add a server side key search for the Team usage view that resolves the search term against every key the caller is allowed to see and aggregates only the matching keys, and have the Team usage page call it instead of filtering the loaded page
How does it fix it?
A new
GET /team/daily/activity/aggregated/searchendpoint takes the same team, date range, exclusion and timezone parameters as the aggregated endpoint plus asearchterm. It matches an exact token hash or a case-insensitive substring of the key alias or owning user id, with the caller's team membership and own-key restriction applied inside the same database filter before the result is limited, so a non-admin can never crowd out their visible matches with keys they may not see. The matched hashes then go through the existing team aggregation, so the response shape is identical to the aggregated endpoint. The route is registered for admins, internal users and view-only users alongside the existing team activity routes, and the Team usage page passes asearchKeyscallback to the key activity panel, which already knows how to swap in remote results (added in #42827)How does the product experience change?
Before: on Usage > Team Usage, typing an alias or hash of a low-spend team key into the API Keys search returned nothing. After: the same search finds the key and shows its spend, tokens and request counts for the selected range
What caveats are there, if any?
This PR is stacked on #42827's branch because the key panel's
searchKeyssupport only exists there. GitHub retargets it to main once that merges. Excluded teams (exclude_team_ids, which the dashboard always sets tolitellm-dashboard) are filtered inside the same database query as the search term, so their keys cannot use up the 100 match limit.%and_in the search term act as SQL LIKE wildcards inside the caller's scope (same as the user sibling), which turns into a match-all within scope rather than a leakLinear ticket
Resolves LIT-8509
How did you test this?
Live proxy on :4000 from this branch and a second proxy on :4100 from the base commit, both against the same real Postgres and Redis and a scripted upstream, seeded with two teams and six keys with real daily spend rows
Before, on the unfixed base:
After, searching a team alias that only one of the three keys in the team carries:
The base vs head A/B kept
/team/daily/activity/aggregatedand/team/daily/activitybyte-identical for master, a team member and a view-only member. On the head, a member without the team activity permission searching the same alias gets200with empty results and finds their own key when searching their user id, requesting a foreign team returns404 "User does not belong to Team= lit8509-t2", a team admin sees the whole team,exclude_team_idsdrops that team's matches, an empty search is422, a 5000 character search and a reversed or 500 day range return200 [],400,400, and 20 concurrent searches return one identical bodyIntegration test
tests/integration/spend/test_team_daily_activity_key_search.py(real proxy, Postgres, Redis, scripted upstream) failed on the unfixed code withassert 404 == 200and passes on this branch (3 passed, including a case where a higher-spend key in an excluded team carries the same alias and only the included team's key comes back). Unit tests intest_team_endpoints.pyandtest_route_checks.pywere mutation checked: dropping the own-key filter from the where, removing thetakelimit, removing the empty-match early return, and dropping the excluded-team filter each made exactly one test fail (4 of 4 killed)Admin UI, both dashboards logged in as admin against a Postgres seeded with 118 team keys so the needle key sits below the top 100 by spend. Steps: open http://localhost:3000/ui/ then Usage in the sidebar, pick Team Usage in the Usage View selector, open the Key Activity tab, type
lit8509-needlein the API Keys search boxBefore (dashboard on the base commit): the search only filters the 100 loaded keys and reports no match
After (this branch): the same search finds both needle keys with their spend and request counts
Link to Devin session: https://app.devin.ai/sessions/921009cdef674333b88e29e14f3debdc
Open in Devin Desktop: https://app.devin.ai/desktop/session/921009cdef674333b88e29e14f3debdc?variant=devin
Requested by: @yassin-berriai