Skip to content

Track per-member total spend on team memberships - #26195

Merged
ryan-crabbe-berri merged 3 commits into
litellm_internal_stagingfrom
litellm_team_member_total_spend
Apr 23, 2026
Merged

Track per-member total spend on team memberships#26195
ryan-crabbe-berri merged 3 commits into
litellm_internal_stagingfrom
litellm_team_member_total_spend

Conversation

@ryan-crabbe-berri

@ryan-crabbe-berri ryan-crabbe-berri commented Apr 21, 2026

Copy link
Copy Markdown
Contributor

Summary

Adds total_spend to LiteLLM_TeamMembership — a non-resetting counter that parallels the existing cycle-scoped spend, incremented in the same atomic update_many call and untouched by the budget reset job. Gives the Teams > Members UI a way to show lifetime spend per member instead of only current-cycle spend.

Also exposes budget_reset_at on LiteLLM_BudgetTableFull (not the base class) so /team/info responses can show when a member's budget next resets. Keeping it off LiteLLM_BudgetTable matters — that base class's model_fields.keys() is the user-input allowlist across 9 write paths, and request models like NewOrganizationRequest inherit from it. Putting the field on the base would let a caller pin their own reset date and evade cycling. LiteLLM_TeamMembership.litellm_budget_table is typed as Union[Full, Base] so Pydantic picks Full on Prisma reads (surfacing budget_reset_at) and Base on existing auth/test constructions (which only carry user-settable fields).

Migration is additive with DEFAULT 0.0 and no backfill — existing members start at 0 and accrue forward.

Test cases

  • test_reset_budget_for_team_members_preserves_total_spend — regression guard. Asserts the reset job writes exactly {"spend": 0} and specifically excludes total_spend, so any future refactor that adds "total_spend": 0 to the reset data dict trips this test.
  • test_commit_spend_updates_to_db_increments_team_member_spend_and_total_spend — verifies the writer increments both spend and total_spend by the same response_cost in a single update_many call.
  • Existing auth tests (test_team_member_budget, test_auth_hot_path_network_requests, test_handle_jwt, test_auth_checks, test_zero_cost_model_budget_bypass) pass unchanged — the Union type keeps fixtures working without forcing them to provide created_at.
  • Live smoke: /team/info response includes team_memberships[*].total_spend and team_memberships[*].litellm_budget_table.budget_reset_at.

Adds total_spend column to LiteLLM_TeamMembership that accumulates
continuously and is not zeroed by the budget cycle reset job. This
enables UI surfaces to distinguish current-cycle spend (the existing
spend column, which resets) from lifetime spend per team member.

Also exposes budget_reset_at on LiteLLM_BudgetTable so /team/info
callers can see when a member's budget window next resets. The field
was already stored in the DB but stripped by the response Pydantic
model.

Includes regression tests that:
- Guard the reset job against ever writing total_spend: 0
- Verify the spend writer increments both spend and total_spend in
  one UPDATE statement.
@greptile-apps

greptile-apps Bot commented Apr 21, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR adds a non-resetting total_spend counter to LiteLLM_TeamMembership, incremented atomically with the existing cycle-scoped spend in a single update_many call and explicitly excluded from the budget reset job. It also exposes budget_reset_at on /team/info responses by changing litellm_budget_table to Union[LiteLLM_BudgetTableFull, LiteLLM_BudgetTable], keeping the field out of user-settable write paths. The migration is additive (NOT NULL DEFAULT 0.0), two regression-guard tests are included, and the security boundary between LiteLLM_BudgetTable (user-input allowlist) and LiteLLM_BudgetTableFull (DB-read response) is preserved correctly.

Confidence Score: 5/5

Safe to merge — migration is additive, security boundaries are correct, both new code paths are covered by targeted tests.

All remaining findings are P2 or lower. The core logic (atomic double-increment, reset-job exclusion, Union discriminator, additive migration) is correct and well-tested. The budget_reset_at security concern flagged in prior review threads is properly addressed by keeping the field in LiteLLM_BudgetTableFull.

No files require special attention.

Important Files Changed

Filename Overview
litellm-proxy-extras/litellm_proxy_extras/migrations/20260421135425_add_team_membership_total_spend/migration.sql Additive migration: adds total_spend DOUBLE PRECISION NOT NULL DEFAULT 0.0 to LiteLLM_TeamMembership — no backfill, no removals, safe to apply on existing data.
litellm/proxy/_types.py Adds total_spend: Optional[float] = 0.0 to LiteLLM_TeamMembership; changes litellm_budget_table to Union[LiteLLM_BudgetTableFull, LiteLLM_BudgetTable] so Pydantic surfaces budget_reset_at on Prisma reads while keeping it out of user-settable paths; adds docstring clarifying allowlist semantics on LiteLLM_BudgetTable.
litellm/proxy/db/db_spend_update_writer.py Increments total_spend alongside spend in the same atomic update_many batch call — the two counters stay in sync with no separate DB round-trip.
tests/test_litellm/proxy/common_utils/test_reset_budget_job.py Adds test_reset_budget_for_team_members_preserves_total_spend — a targeted regression guard that asserts the reset job writes exactly {"spend": 0} and never touches total_spend.
tests/test_litellm/proxy/db/test_db_spend_update_writer.py Adds test_commit_spend_updates_to_db_increments_team_member_spend_and_total_spend — verifies both spend and total_spend are incremented by the same response_cost in a single update_many call.

Sequence Diagram

sequenceDiagram
    participant R as Request
    participant W as DBSpendUpdateWriter
    participant DB as Prisma (LiteLLM_TeamMembership)
    participant J as ResetBudgetJob

    R->>W: response_cost accrued
    W->>DB: update_many(spend += cost, total_spend += cost)
    Note over DB: Both fields incremented atomically

    J->>DB: update_many(spend = 0)
    Note over DB: total_spend untouched by reset job

    DB-->>W: /team/info read
    Note over W: Pydantic Union picks LiteLLM_BudgetTableFull when created_at present
Loading

Reviews (7): Last reviewed commit: "fmt: apply black to _types.py" | Re-trigger Greptile

Comment thread litellm/proxy/_types.py Outdated
@ryan-crabbe-berri

Copy link
Copy Markdown
Contributor Author

@greptileai can you review

Comment thread litellm/proxy/management_endpoints/organization_endpoints.py
LiteLLM_BudgetTable is documented as "user-controllable params" and its
model_fields.keys() is used as the allowlist for extracting budget fields
from incoming API request bodies (management_helpers/utils.py:88,
organization_endpoints.py:112/255/537/549, project_endpoints.py:197/245/632,
customer_endpoints.py:598). Request models like NewOrganizationRequest
inherit from LiteLLM_BudgetTable, so anything on the base class becomes
user-settable — a caller could set budget_reset_at far in the future and
evade budget cycling.

Move budget_reset_at from the base class to LiteLLM_BudgetTableFull so it
appears on API responses without becoming writable, and type
LiteLLM_TeamMembership.litellm_budget_table as Union[Full, Base] so
Pydantic picks Full when the data has server-managed fields (/team/info
reads Prisma rows that include budget_reset_at and created_at) and Base
when callers construct with only user-settable fields (existing auth
tests and caches).
@codecov

codecov Bot commented Apr 21, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@ryan-crabbe-berri
ryan-crabbe-berri force-pushed the litellm_team_member_total_spend branch from ed38c1c to 9a6ddef Compare April 22, 2026 00:49
@ryan-crabbe-berri
ryan-crabbe-berri merged commit c4c1861 into litellm_internal_staging Apr 23, 2026
153 checks passed
@ryan-crabbe-berri
ryan-crabbe-berri deleted the litellm_team_member_total_spend branch April 23, 2026 01:20
fzowl pushed a commit to fzowl/litellm that referenced this pull request Jun 24, 2026
…al_spend

Track per-member total spend on team memberships
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants