Skip to content

feat(teams): per-member model scope + team default_team_member_models#24950

Merged
ishaan-berri merged 22 commits intolitellm_ishaan_april6from
worktree-rustling-wishing-kite
Apr 6, 2026
Merged

feat(teams): per-member model scope + team default_team_member_models#24950
ishaan-berri merged 22 commits intolitellm_ishaan_april6from
worktree-rustling-wishing-kite

Conversation

@ishaan-berri
Copy link
Copy Markdown
Contributor

Relevant issues

What this does

Two new team capabilities:

  1. default_team_member_models on a team — when a member is added without an explicit allowed_models, they automatically inherit this list. Set via POST /team/new or POST /team/update.

  2. allowed_models per team member — enforced at auth time. If set (non-empty), only those models are accessible. Empty = inherit all team models (existing behavior).

API changes

  • POST /team/new + POST /team/update accept default_team_member_models: string[]
  • POST /team/member_add accepts top-level allowed_models: string[] (falls back to team default if omitted)
  • POST /team/member_update accepts allowed_models: string[]
  • GET /team/info returns allowed_models per member in team_memberships[].litellm_budget_table

Schema changes

  • LiteLLM_TeamTable.default_team_member_models TEXT[] DEFAULT ARRAY[]
  • LiteLLM_BudgetTable.allowed_models TEXT[] DEFAULT ARRAY[]

UI changes

  • Members tab: "Model Scope" column — shows specific models or "(all team models)" in grey
  • Edit Member modal: "Allowed Models" multi-select, filtered to team's models
  • Settings tab: "Default Member Models" field

Auth enforcement

After team-level model check passes, a new check fires if the member's allowed_models is non-empty — returns 401 with "Team member not allowed to access model" if the requested model isn't in the list.

Pre-Submission checklist

  • I have Added testing in the tests/test_litellm/ directory, Adding at least 1 test is a hard requirement - see details
  • My PR passes all unit tests on make test-unit
  • My PR's scope is as isolated as possible, it only solves 1 specific problem
  • I have requested a Greptile review by commenting @greptileai and received a Confidence Score of at least 4/5 before requesting a maintainer review

Type

  • New Feature

Changes

  • schema.prisma / litellm-proxy-extras/schema.prisma: new fields on TeamTable and BudgetTable
  • litellm-proxy-extras/migrations/: migration SQL for the new columns
  • litellm/proxy/_types.py: allowed_models on request types
  • litellm/proxy/management_helpers/utils.py: add_new_member accepts + persists allowed_models
  • litellm/proxy/management_endpoints/common_utils.py: _upsert_budget_and_membership accepts allowed_models
  • litellm/proxy/management_endpoints/team_endpoints.py: member_add seeding, member_update + team/update persistence
  • litellm/proxy/auth/auth_checks.py: _check_team_member_model_access enforced in _run_common_checks
  • UI: TeamMemberTab, EditMembership, TeamInfo, networking.tsx

@vercel
Copy link
Copy Markdown

vercel Bot commented Apr 1, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
litellm Ready Ready Preview, Comment Apr 6, 2026 8:54pm

Request Review

@codspeed-hq
Copy link
Copy Markdown
Contributor

codspeed-hq Bot commented Apr 1, 2026

Merging this PR will not alter performance

✅ 16 untouched benchmarks


Comparing worktree-rustling-wishing-kite (430c404) with litellm_ishaan_april6 (8b0fadf)1

Open in CodSpeed

Footnotes

  1. No successful run was found on litellm_ishaan_april6 (39c1042) during the generation of this report, so b9a9f72 was used instead as the comparison base. There might be some changes unrelated to this pull request in this report.

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented Apr 1, 2026

Greptile Summary

This PR introduces per-member model scope for LiteLLM teams: a default_team_member_models field on the team seeds allowed_models for newly added members, and allowed_models on the member's budget row is enforced at auth time by the new _check_team_member_model_access helper in auth_checks.py. Schema migrations, Prisma model updates, and UI changes (member table "Model Scope" column, edit-member modal "Allowed Models" multi-select, and team settings "Default Member Models" field) are included alongside a separate fix that strips context-window bracket suffixes ([1m], [200k]) from Bedrock model names before cost lookup.

Key findings:

  • P0: Two unresolved git merge conflicts — one in test_bedrock_common_utils.py (causes Python SyntaxError, breaks the entire test file) and one in TeamInfo.tsx (causes TypeScript compile failure, breaks the dashboard build). The worktree-side assertion in the test file is also logically incorrect.
  • P1: No unit tests are provided for the new auth enforcement logic (_check_team_member_model_access) or the allowed_models seeding path, despite this being an explicit requirement in CLAUDE.md and the pre-submission checklist being unchecked.
  • The core backend logic is otherwise sound: the _upsert_budget_and_membership update-in-place path avoids double budget creation, the early-return guard in _check_team_member_model_access avoids cache overhead for members without restrictions, and llm_router is correctly threaded through to _can_object_call_model.

Confidence Score: 3/5

Not safe to merge — two P0 merge conflicts break Python test parsing and TypeScript compilation.

Score of 3 reflects two P0 blockers (unresolved conflict markers in a test file and in a UI interface definition) plus a P1 for missing unit tests covering the new auth-enforcement path. The underlying backend logic is sound.

tests/test_litellm/llms/bedrock/test_bedrock_common_utils.py (merge conflict + wrong assertion) and ui/litellm-dashboard/src/components/team/TeamInfo.tsx (merge conflict in TeamData interface)

Important Files Changed

Filename Overview
tests/test_litellm/llms/bedrock/test_bedrock_common_utils.py Contains unresolved git merge conflict (lines 37–43) causing SyntaxError; worktree-side assertion is also logically incorrect for the given input model.
ui/litellm-dashboard/src/components/team/TeamInfo.tsx Contains unresolved git merge conflict in the TeamData interface definition, preventing TypeScript compilation.
litellm/proxy/auth/auth_checks.py Adds _check_team_member_model_access with correct early-return guard and llm_router threading; no unit tests provided for the new enforcement path.
litellm/proxy/management_endpoints/common_utils.py _upsert_budget_and_membership updated to update-in-place when an existing budget is present; allowed_models=None vs [] semantics are documented.
litellm/proxy/management_endpoints/team_endpoints.py _process_team_members seeds allowed_models from default_team_member_models when omitted; team_member_update propagates allowed_models to both upsert paths.
litellm/proxy/management_helpers/utils.py add_new_member creates a budget entry when allowed_models is set, even without max_budget_in_team.
litellm-proxy-extras/litellm_proxy_extras/migrations/20260401000000_add_team_member_model_scope/migration.sql Idempotent migration using IF NOT EXISTS; both columns default to empty TEXT array.
litellm/proxy/_types.py default_team_member_models added to TeamBase and UpdateTeamRequest; allowed_models added to LiteLLM_BudgetTable and membership update types.
litellm/llms/bedrock/common_utils.py Adds safe context-window bracket suffix stripping via regex; formatting-only change to CommonBatchFilesUtils.prepare_request.
ui/litellm-dashboard/src/components/team/TeamMemberTab.tsx Adds Model Scope column; initializes allowed_models as [] so the !== undefined guard in teamMemberUpdateCall always fires.
ui/litellm-dashboard/src/components/team/EditMembership.tsx Adds multi-select field type using Ant Design Select mode=multiple; wired to allowed_models form field.
ui/litellm-dashboard/src/components/networking.tsx Member interface gains allowed_models; teamMemberUpdateCall includes it when !== undefined.
ui/litellm-dashboard/src/app/(dashboard)/teams/components/modals/CreateTeamModal.tsx Adds default_team_member_models multi-select scoped to team models with sensible fallback to full model list.
schema.prisma New fields default_team_member_models and allowed_models added consistently.
litellm/proxy/schema.prisma New fields added to match proxy-extras schema.
litellm-proxy-extras/litellm_proxy_extras/schema.prisma New fields with @default([]) annotations on BudgetTable and TeamTable.

Sequence Diagram

sequenceDiagram
    participant Client
    participant CommonChecks as common_checks()
    participant TeamCheck as can_team_access_model()
    participant MemberCheck as _check_team_member_model_access()
    participant Cache as user_api_key_cache / DB
    Client->>CommonChecks: request(model, team_object, valid_token)
    CommonChecks->>TeamCheck: check team-level model access
    TeamCheck-->>CommonChecks: OK / raise HTTP 401
    CommonChecks->>MemberCheck: check per-member model scope
    MemberCheck->>Cache: get_team_membership(user_id, team_id)
    Cache-->>MemberCheck: team_membership or None
    alt allowed_models is empty or None
        MemberCheck-->>CommonChecks: return (no restriction)
    else allowed_models is non-empty
        MemberCheck->>MemberCheck: _can_object_call_model(model, allowed_models, llm_router)
        alt model in allowed_models
            MemberCheck-->>CommonChecks: return OK
        else model not in allowed_models
            MemberCheck-->>CommonChecks: raise ProxyException HTTP 401
        end
    end
Loading

Comments Outside Diff (2)

  1. tests/test_litellm/llms/bedrock/test_bedrock_common_utils.py, line 37-43 (link)

    Unresolved git merge conflict — file will not parse

    This file contains raw conflict markers (<<<<<<< worktree-rustling-wishing-kite, =======, >>>>>>> main) that cause a Python SyntaxError on import, making every test in this file fail to run.

    Additionally, the worktree-side assertion is logically wrong: the input model is bedrock/us-gov.anthropic.claude-haiku-4-5-20251001-v1:0, but the conflict-side assertion expects "anthropic.claude-3-5-sonnet-20240620-v1:0". Resolve the conflict by accepting the main-side value:

  2. ui/litellm-dashboard/src/components/team/TeamInfo.tsx, line 96-103 (link)

    Unresolved git merge conflict in TeamData interface — TypeScript will not compile

    The TeamData interface definition contains raw conflict markers (<<<<<<< worktree-rustling-wishing-kite / >>>>>>> main). TypeScript will fail to compile this file, breaking the entire dashboard build.

    The correct resolution is to include all fields from both sides:

Reviews (5): Last reviewed commit: "Merge branch 'main' into worktree-rustli..." | Re-trigger Greptile

Comment on lines +429 to +441
# 2.2. If team member has per-member model scope, enforce it
if _model and team_object and valid_token and valid_token.user_id:
with tracer.trace(
"litellm.proxy.auth.common_checks.check_team_member_model_access"
):
await _check_team_member_model_access(
model=_model,
team_object=team_object,
valid_token=valid_token,
prisma_client=prisma_client,
user_api_key_cache=user_api_key_cache,
proxy_logging_obj=proxy_logging_obj,
)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 New unconditional get_team_membership cache lookup on every team+model request

_check_team_member_model_access is invoked on every request where _model, team_object, and valid_token.user_id are set — i.e. the vast majority of team key requests. Internally it calls get_team_membership, which hits the cache (or DB on miss) for every one of those requests, even when no member has allowed_models set.

For the common case (no per-member restrictions), this adds a cache round-trip per request with zero benefit. Consider guarding the entire block against a lightweight signal on the team object (e.g. a boolean flag has_member_model_restrictions on the cached team, or checking if team_object.default_team_member_models is non-empty as a heuristic), so that deployments not using this feature pay no cost.

Rule Used: What: Avoid creating new database requests or Rout... (source)

Comment on lines 3893 to +3896
if (formValues.rpm_limit !== undefined && formValues.rpm_limit !== null) {
requestBody.rpm_limit = formValues.rpm_limit;
}
if (formValues.allowed_models !== undefined) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 allowed_models is always sent, even when the user has not changed it

In TeamMemberTab, the member object is pre-populated with allowed_models: membership?.litellm_budget_table?.allowed_models || []. Because [] is not undefined, the check formValues.allowed_models !== undefined will always be true, so allowed_models is included in every teamMemberUpdateCall payload — even when the admin only edits max_budget_in_team.

Until the server-side _upsert_budget_and_membership properly updates rather than replaces the budget row, this guarantees that a round-trip from the Edit Member modal will lose any server-side allowed_models that were set to a value the front-end hadn't fetched yet. More importantly, once the server side is fixed, the client should only send fields the user actually changed.

Suggested change
if (formValues.rpm_limit !== undefined && formValues.rpm_limit !== null) {
requestBody.rpm_limit = formValues.rpm_limit;
}
if (formValues.allowed_models !== undefined) {
if (formValues.allowed_models !== undefined && formValues.allowed_models !== null) {
requestBody.allowed_models = formValues.allowed_models;
}

Comment on lines +3120 to +3125
_can_object_call_model(
model=model,
llm_router=None,
models=member_allowed_models,
object_type="team",
)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P1 Model access groups not resolved in per-member model checks

_can_object_call_model is called with llm_router=None. Inside that function, the llm_router is needed to resolve named access groups (e.g. a group called "gpt-4-group"). The team-level check (can_team_access_model, a few lines above at line 414) correctly passes the router, but the new member-level check does not.

In practice, if a team admin sets a member's allowed_models=["gpt-4-access-group"] and a request comes in for gpt-4, the group will never be expanded, the check will always fail, and the member will be permanently locked out even though they have valid access.

The llm_router argument is already available in common_checks via the outer function scope — it should be threaded into _check_team_member_model_access and forwarded to _can_object_call_model the same way can_team_access_model uses it.

…reating new one

When a member already has a budget_id, patch only the fields the caller
provided rather than always creating a fresh budget record.  The old
code ignored existing_budget_id entirely, so updating only allowed_models
silently dropped the stored max_budget / tpm_limit / rpm_limit values.
Without the router, _can_object_call_model cannot resolve wildcard model
names (e.g. openai/*) or access-group names in allowed_models, causing
legitimate requests to be denied.  Thread the existing llm_router from
_run_common_checks through to the new member-scope check.
@CLAassistant
Copy link
Copy Markdown

CLAassistant commented Apr 1, 2026

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you all sign our Contributor License Agreement before we can accept your contribution.
1 out of 4 committers have signed the CLA.

✅ ishaan-jaff
❌ github-actions[bot]
❌ yuneng-berri
❌ ishaan-berri
You have signed the CLA already but the status is still pending? Let us recheck it.

Groups default_team_member_models, member budget/key duration, and
tpm/rpm defaults into a single collapsible section. The model picker
is filtered to only show the models selected for the team, and the
copy distinguishes it from the team-level Models field.
…m form

Moves default_team_member_models + per-member budget/key/tpm/rpm fields
into a collapsible "Team Member Settings" panel. Keeps the top-level
form focused on team-wide settings (team models, team budget, tpm/rpm).
@codecov
Copy link
Copy Markdown

codecov Bot commented Apr 1, 2026

Codecov Report

❌ Patch coverage is 82.97872% with 8 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
litellm/proxy/auth/auth_checks.py 57.14% 6 Missing ⚠️
litellm/proxy/management_endpoints/common_utils.py 87.50% 2 Missing ⚠️

📢 Thoughts on this report? Let us know!

@ishaan-berri ishaan-berri changed the base branch from main to litellm_ishaan_april6 April 6, 2026 20:48
@ishaan-berri ishaan-berri merged commit 414d396 into litellm_ishaan_april6 Apr 6, 2026
47 of 60 checks passed
@ishaan-berri ishaan-berri deleted the worktree-rustling-wishing-kite branch April 6, 2026 20:48
@ishaan-berri ishaan-berri mentioned this pull request Apr 7, 2026
5 tasks
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.

4 participants