Conversation
…llback The Pydantic default for user_role was INTERNAL_USER, but all runtime provisioning paths (SSO, SCIM, JWT) fall back to INTERNAL_USER_VIEW_ONLY when no settings are saved. This caused the UI to show "Internal User" on fresh instances while new users actually got "Internal Viewer".
Asserts that GET /get/internal_user_settings returns INTERNAL_USER_VIEW_ONLY on a fresh DB with no saved settings, matching the runtime fallback in SSO/SCIM/JWT provisioning.
…endpoints.py Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
…-not-synced-with-ui fix: align DefaultInternalUserParams Pydantic default with runtime fallback
…ettings-antd chore(ui): migrate DefaultUserSettings buttons from Tremor to antd
Litellm update blog posts rss
Aggregated endpoint returns empty breakdown.entities; fall back to grouping breakdown.api_keys by team_id.
fix(ui): CSV export empty on Global Usage page
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Greptile SummaryThis PR bundles four independent improvements: fixes empty CSV exports on the Global Usage page by adding a Key findings:
Confidence Score: 3/5
|
| Filename | Overview |
|---|---|
| ui/litellm-dashboard/src/components/EntityUsageExport/utils.ts | Adds resolveEntities() and aggregateApiKeysIntoEntities() to fix empty CSV exports when the aggregated endpoint returns empty entities. The fallback logic itself is correct, but generateDailyWithModelsData has a pre-existing attribution bug (all API key metrics credited to every model) that is now newly reachable via the aggregated endpoint path; also resolveEntities is invoked twice per day in that function (flagged in prior review thread). |
| ui/litellm-dashboard/src/components/EntityUsageExport/utils.test.ts | Adds comprehensive tests for resolveEntities and the aggregated-endpoint fallback across all four export functions. Test logic is sound, but one inline comment mis-labels the key-to-team mapping (key1+key2/key3 instead of key1+key1b/key2). |
| litellm/litellm_core_utils/get_blog_posts.py | Replaces static JSON fetching with RSS XML parsing. Parser logic is clean and the fallback chain is preserved. XML security concern (entity expansion via configurable URL) was flagged in a prior thread. |
| tests/test_litellm/test_get_blog_posts.py | Tests updated to reflect the RSS feed approach. All network calls are properly mocked; no real network calls introduced. New parse_rss_to_posts tests cover happy path, multi-item, missing channel, and invalid XML cases. |
| litellm/proxy/_types.py | Default user_role for DefaultInternalUserParams changed from INTERNAL_USER to INTERNAL_USER_VIEW_ONLY to align Pydantic default with runtime SSO/SCIM/JWT fallback. Backwards-compatibility concern flagged in prior thread. |
| tests/test_litellm/proxy/ui_crud_endpoints/test_proxy_setting_endpoints.py | Adds a mock-only test asserting that a fresh DB returns INTERNAL_USER_VIEW_ONLY as the default role. No real network calls; test accurately validates the Pydantic default change. |
| ui/litellm-dashboard/src/components/DefaultUserSettings.tsx | Straightforward Tremor→antd Button migration. size="sm"→"small", icon prop updated to JSX element syntax, danger prop added for the delete button, type="primary" added for Save/Edit buttons. No logic changes. |
| litellm/init.py | Single-line change: default blog_posts_url updated from GitHub raw JSON to the docs RSS feed URL. |
Flowchart
%%{init: {'theme': 'neutral'}}%%
flowchart TD
A[CSV Export triggered] --> B{breakdown.entities populated?}
B -- Yes --> C[Use entities directly]
B -- No --> D[aggregateApiKeysIntoEntities]
D --> E{breakdown.api_keys present?}
E -- No --> F[Return empty object]
E -- Yes --> G[Group api_keys by metadata.team_id]
G --> H[Accumulate METRIC_KEYS per team]
H --> I[Build api_key_breakdown per team]
I --> J[Return reconstructed entities map]
C --> K[generateDailyData / generateDailyWithKeysData / generateDailyWithModelsData]
J --> K
K --> L[CSV / JSON output]
subgraph Blog Posts
M[get_blog_posts called] --> N{LITELLM_LOCAL_BLOG_POSTS=true?}
N -- Yes --> O[Load local blog_posts.json]
N -- No --> P{Cache valid?}
P -- Yes --> Q[Return cached posts]
P -- No --> R[httpx.GET RSS URL]
R --> S{Network error?}
S -- Yes --> O
S -- No --> T[ET.fromstring XML parse]
T --> U{Parse error?}
U -- Yes --> O
U -- No --> V[Extract up to max_posts items]
V --> W{validate_blog_posts}
W -- Empty list --> O
W -- Valid --> X[Cache and return posts]
end
Comments Outside Diff (1)
-
ui/litellm-dashboard/src/components/EntityUsageExport/utils.ts, line 248-272 (link)modelDatacaptured but never used — all API key metrics attributed to every modelIn
generateDailyWithModelsData, the outer loop iterates over every model inday.breakdown.models, butmodelData(the per-model metrics) is destructured and then never referenced inside the loop body. Instead, the inner loop sums the API key's total metrics into every model bucket:Object.entries(day.breakdown.models || {}).forEach(([model, modelData]: [string, any]) => { // modelData is never read Object.entries(apiKeyBreakdown).forEach(([apiKey, apiKeyData]: [string, any]) => { dailyEntityModels[entity][model].spend += apiKeyData.metrics.spend || 0; // ... }); });
If an entity has M api keys and there are N models, each api key's full spend/requests are added N times across all models. The resulting CSV rows for "Daily with Models" will show inflated — and identical — numbers for every model rather than a per-model breakdown.
This logic was present before this PR, but the new
resolveEntitiesfallback now allows the aggregated endpoint's data to flow through this path, making the bug newly reachable for deployments using that endpoint. The fix would be to attribute only the fraction of each API key's usage that belongs to each model, or to source per-model metrics frommodelDatainstead of from the API key totals.
Last reviewed commit: 302292c
| ] | ||
| ] = Field( | ||
| default=LitellmUserRoles.INTERNAL_USER, | ||
| default=LitellmUserRoles.INTERNAL_USER_VIEW_ONLY, |
There was a problem hiding this comment.
Backwards-incompatible permission downgrade without a feature flag
The default for user_role is changed from INTERNAL_USER to INTERNAL_USER_VIEW_ONLY. While the PR description explains this aligns the Pydantic default with the SSO/SCIM/JWT runtime fallback, it is still a backwards-incompatible change for users who rely on DefaultInternalUserParams in non-SSO provisioning code paths.
Any deployment that has default_internal_user_params configured without an explicit user_role key was previously getting INTERNAL_USER (write access) as the Pydantic default. After upgrading, those same deployments will silently get INTERNAL_USER_VIEW_ONLY (read-only) for all newly provisioned users — with no opt-out mechanism.
Per the repository's policy on backwards-incompatible changes, this should be guarded by a user-controlled flag (e.g., check whether the user has explicitly set user_role in their config before applying the new default, or expose a flag like litellm.new_user_default_role). Without such a flag, admins who upgrade will find their new users unexpectedly can't perform write operations.
| default=LitellmUserRoles.INTERNAL_USER_VIEW_ONLY, | |
| default=LitellmUserRoles.INTERNAL_USER_VIEW_ONLY, |
(If this change is intentional and the SSO/SCIM/JWT paths were the only ones ever used in practice, please add a note in the PR description and a BREAKING CHANGE entry in the changelog so upgrading users are explicitly aware.)
Rule Used: What: avoid backwards-incompatible changes without... (source)
| Object.entries(dailyEntityModels).forEach(([entity, models]) => { | ||
| const entityData = day.breakdown.entities?.[entity]; | ||
| const entityData = resolveEntities(day.breakdown)[entity]; |
There was a problem hiding this comment.
resolveEntities called twice per day in generateDailyWithModelsData
resolveEntities(day.breakdown) is invoked once at line 248 (inside the entity loop) and again on line 276 (to look up a single entity by key). In the fallback path, each call iterates over all api_keys and builds a new aggregated object, so this effectively processes the same keys twice per day result.
Consider caching the resolved entities per day:
const resolvedEntities = resolveEntities(day.breakdown);
Object.entries(resolvedEntities).forEach(([entity, entityData]: [string, any]) => {
// ... existing entity loop body ...
});
Object.entries(dailyEntityModels).forEach(([entity, models]) => {
const entityData = resolvedEntities[entity];
// ...
});This eliminates the redundant re-computation and makes the intent clearer.
|
|
||
| Extracts title, description, date (YYYY-MM-DD), and url from each <item>. | ||
| """ | ||
| root = ET.fromstring(xml_text) |
There was a problem hiding this comment.
XML entity expansion risk with user-configurable URL
xml.etree.ElementTree.fromstring() does not protect against "billion laughs" / XML entity expansion attacks. Since LITELLM_BLOG_POSTS_URL is user-configurable via environment variable, a malicious XML payload at a custom URL could cause excessive memory/CPU consumption.
Consider replacing the standard library parser with defusedxml, which is already a common dependency in Python security-conscious projects:
import defusedxml.ElementTree as ETThis is a drop-in replacement — no other code changes are needed. The rest of the parsing logic remains identical.
| const breakdown = aggregatedSpendData.results[0].breakdown; | ||
| const result = resolveEntities(breakdown); | ||
|
|
||
| // Two teams: team-1 (key1+key2) and team-2 (key3) |
There was a problem hiding this comment.
Misleading comment — wrong key names for each team
The comment states team-1 (key1+key2) and team-2 (key3), but the assertions directly below and the api_key_breakdown preservation test confirm the actual layout is:
team-1→key1+key1bteam-2→key2
key2 and key3 are swapped, and key1b is missing from the description. This could mislead anyone debugging or extending these tests.
| // Two teams: team-1 (key1+key2) and team-2 (key3) | |
| // Two teams: team-1 (key1+key1b) and team-2 (key2) |
daily branch
All tests passing: https://app.circleci.com/pipelines/github/BerriAI/litellm?branch=litellm_ryan_march_16
#23819
#23791
#23787
#23666
static JSON