Skip to content

feat(teams): resolve access group resources in team endpoints#25027

Merged
ryan-crabbe-berri merged 8 commits intolitellm_ryan-march-31from
litellm_add-access-group-to-model
Apr 4, 2026
Merged

feat(teams): resolve access group resources in team endpoints#25027
ryan-crabbe-berri merged 8 commits intolitellm_ryan-march-31from
litellm_add-access-group-to-model

Conversation

@ryan-crabbe-berri
Copy link
Copy Markdown
Collaborator

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

Summary

  • Resolve access group models, MCP servers, and agents in team_info and list_team_v2 endpoints so the UI can display which resources a team inherits from its access groups
  • Added access_group_models, access_group_mcp_server_ids, access_group_agent_ids fields to TeamInfoResponseObjectTeamTable and TeamListItem
  • UI shows direct models (blue badges) vs access-group-inherited models (green badges) in both the teams table and team detail view
  • Single-pass access group resolution fetches each access group once instead of 3 separate calls (3N → N lookups)
  • asyncio.gather for concurrent resolution across teams in the list endpoint

Screenshots

Screenshot 2026-04-03 at 4 44 44 PM Screenshot 2026-04-03 at 4 49 38 PM Screenshot 2026-04-03 at 4 49 15 PM

Test plan

  • Unit tests for _resolve_access_group_resources (empty, single, multiple with dedup, missing group, no cache)
  • Manual QA: verify teams table shows blue/green badges correctly
  • Manual QA: verify team detail view shows legend and correct badge colors

Add access_group_models, access_group_mcp_server_ids, and
access_group_agent_ids to /team/info and /v2/team/list responses.
These fields contain resources inherited from access groups, kept
separate from direct assignments so the UI can distinguish the source.

Backend: _resolve_access_group_resources() helper resolves access
group resources via existing _get_*_from_access_groups() functions.

UI: Teams table and detail view show direct models as blue badges
and access-group-sourced models as green badges.
…list endpoint

- Fetch each access group object once and extract all 3 resource fields
  in a single pass instead of 3 separate calls (3N → N lookups)
- Use asyncio.gather to resolve access groups across teams concurrently
  in list_team_v2 instead of sequential awaits
- Add 5 unit tests for _resolve_access_group_resources
@vercel
Copy link
Copy Markdown

vercel bot commented Apr 2, 2026

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

Project Deployment Actions Updated (UTC)
litellm Ready Ready Preview, Comment Apr 4, 2026 0:15am

Request Review

@codspeed-hq
Copy link
Copy Markdown
Contributor

codspeed-hq bot commented Apr 2, 2026

Merging this PR will not alter performance

✅ 16 untouched benchmarks


Comparing litellm_add-access-group-to-model (bbe708b) with main (9c5fda4)

Open in CodSpeed

from litellm.proxy.auth.auth_checks import (
allowed_route_check_inside_route,
can_org_access_model,
get_access_object,

Check failure

Code scanning / CodeQL

Module-level cyclic import Error

'get_access_object' may not be defined if module
litellm.proxy.auth.auth_checks
is imported before module
litellm.proxy.management_endpoints.team_endpoints
, as the
definition
of get_access_object occurs after the cyclic
import
of litellm.proxy.management_endpoints.team_endpoints.
'get_access_object' may not be defined if module
litellm.proxy.auth.auth_checks
is imported before module
litellm.proxy.management_endpoints.team_endpoints
, as the
definition
of get_access_object occurs after the cyclic
import
of litellm.proxy.management_endpoints.team_endpoints.
'get_access_object' may not be defined if module
litellm.proxy.auth.auth_checks
is imported before module
litellm.proxy.management_endpoints.team_endpoints
, as the
definition
of get_access_object occurs after the cyclic
import
of litellm.proxy.management_endpoints.team_endpoints.
'get_access_object' may not be defined if module
litellm.proxy.auth.auth_checks
is imported before module
litellm.proxy.management_endpoints.team_endpoints
, as the
definition
of get_access_object occurs after the cyclic
import
of litellm.proxy.management_endpoints.team_endpoints.
'get_access_object' may not be defined if module
litellm.proxy.auth.auth_checks
is imported before module
litellm.proxy.management_endpoints.team_endpoints
, as the
definition
of get_access_object occurs after the cyclic
import
of litellm.proxy.management_endpoints.team_endpoints.
'get_access_object' may not be defined if module
litellm.proxy.auth.auth_checks
is imported before module
litellm.proxy.management_endpoints.team_endpoints
, as the
definition
of get_access_object occurs after the cyclic
import
of litellm.proxy.management_endpoints.team_endpoints.
'get_access_object' may not be defined if module
litellm.proxy.auth.auth_checks
is imported before module
litellm.proxy.management_endpoints.team_endpoints
, as the
definition
of get_access_object occurs after the cyclic
import
of litellm.proxy.management_endpoints.team_endpoints.
'get_access_object' may not be defined if module
litellm.proxy.auth.auth_checks
is imported before module
litellm.proxy.management_endpoints.team_endpoints
, as the
definition
of get_access_object occurs after the cyclic
import
of litellm.proxy.management_endpoints.team_endpoints.
'get_access_object' may not be defined if module
litellm.proxy.auth.auth_checks
is imported before module
litellm.proxy.management_endpoints.team_endpoints
, as the
definition
of get_access_object occurs after the cyclic
import
of litellm.proxy.management_endpoints.team_endpoints.
'get_access_object' may not be defined if module
litellm.proxy.auth.auth_checks
is imported before module
litellm.proxy.management_endpoints.team_endpoints
, as the
definition
of get_access_object occurs after the cyclic
import
of litellm.proxy.management_endpoints.team_endpoints.
'get_access_object' may not be defined if module
litellm.proxy.auth.auth_checks
is imported before module
litellm.proxy.management_endpoints.team_endpoints
, as the
definition
of get_access_object occurs after the cyclic
import
of litellm.proxy.management_endpoints.team_endpoints.
'get_access_object' may not be defined if module
litellm.proxy.auth.auth_checks
is imported before module
litellm.proxy.management_endpoints.team_endpoints
, as the
definition
of get_access_object occurs after the cyclic
import
of litellm.proxy.management_endpoints.team_endpoints.
'get_access_object' may not be defined if module
litellm.proxy.auth.auth_checks
is imported before module
litellm.proxy.management_endpoints.team_endpoints
, as the
definition
of get_access_object occurs after the cyclic
import
of litellm.proxy.management_endpoints.team_endpoints.
'get_access_object' may not be defined if module
litellm.proxy.auth.auth_checks
is imported before module
litellm.proxy.management_endpoints.team_endpoints
, as the [d

Copilot Autofix

AI 14 days ago

In general, to break a module-level cyclic import, you either (a) move shared code into a third module both can import, or (b) make one side perform a local (function-scope) import so that the import only occurs when needed, after module initialization is complete. Since we can only modify team_endpoints.py and not restructure the package, the most practical fix is to avoid importing get_access_object at module level and instead import it lazily inside the functions that need it.

Concretely, in litellm/proxy/management_endpoints/team_endpoints.py we will:

  • Remove get_access_object from the from litellm.proxy.auth.auth_checks import (...) list at the top of the file.
  • Introduce a small internal helper function, e.g. _get_access_object, which performs a local import of get_access_object from litellm.proxy.auth.auth_checks and immediately forwards the call. This keeps call sites essentially unchanged while avoiding the module-level cyclic dependency.
  • Replace all direct uses of get_access_object(...) in this file with _get_access_object(...).

This preserves functionality: we still always call the same get_access_object implementation from auth_checks, but we defer the import until the first use, at which point both modules have already finished importing.

Suggested changeset 1
litellm/proxy/management_endpoints/team_endpoints.py

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/litellm/proxy/management_endpoints/team_endpoints.py b/litellm/proxy/management_endpoints/team_endpoints.py
--- a/litellm/proxy/management_endpoints/team_endpoints.py
+++ b/litellm/proxy/management_endpoints/team_endpoints.py
@@ -64,7 +64,6 @@
 from litellm.proxy.auth.auth_checks import (
     allowed_route_check_inside_route,
     can_org_access_model,
-    get_access_object,
     get_org_object,
     get_team_object,
     get_user_object,
@@ -111,6 +110,16 @@
 router = APIRouter()
 
 
+def _get_access_object(*args, **kwargs):
+    """
+    Lazily import and delegate to `get_access_object` from
+    `litellm.proxy.auth.auth_checks` to avoid module-level cyclic imports.
+    """
+    from litellm.proxy.auth.auth_checks import get_access_object as _inner_get_access_object
+
+    return _inner_get_access_object(*args, **kwargs)
+
+
 class TeamMemberBudgetHandler:
     """Helper class to handle team member budget, RPM, and TPM limit operations"""
 
EOF
@@ -64,7 +64,6 @@
from litellm.proxy.auth.auth_checks import (
allowed_route_check_inside_route,
can_org_access_model,
get_access_object,
get_org_object,
get_team_object,
get_user_object,
@@ -111,6 +110,16 @@
router = APIRouter()


def _get_access_object(*args, **kwargs):
"""
Lazily import and delegate to `get_access_object` from
`litellm.proxy.auth.auth_checks` to avoid module-level cyclic imports.
"""
from litellm.proxy.auth.auth_checks import get_access_object as _inner_get_access_object

return _inner_get_access_object(*args, **kwargs)


class TeamMemberBudgetHandler:
"""Helper class to handle team member budget, RPM, and TPM limit operations"""

Copilot is powered by AI and may make mistakes. Always verify output.
if not access_group_ids:
return empty

from litellm.proxy.proxy_server import prisma_client as _prisma_client

Check notice

Code scanning / CodeQL

Cyclic import Note

Import of module
litellm.proxy.proxy_server
begins an import cycle.
Import of module
proxy_server
begins an import cycle.

Copilot Autofix

AI 14 days ago

General approach: Break the cycle by removing the import from team_endpoints to proxy_server while preserving behavior. Instead of pulling prisma_client, proxy_logging_obj, and user_api_key_cache from proxy_server, use dependencies or utilities that are already available in this module, or re-acquire what is needed via other, non-cyclic means.

Best concrete fix here: resolve_access_group_inherited_resources only uses those three objects to call get_access_object, which is already imported from litellm.proxy.auth.auth_checks. If get_access_object can operate with None or omitted prisma_client / cache / logging arguments, we can simply stop importing them from proxy_server and call get_access_object without those keyword arguments. That removes the cycle without changing higher-level functionality, and keeps the function focused on resolving resources via the existing auth helper.

So, in litellm/proxy/management_endpoints/team_endpoints.py, within resolve_access_group_inherited_resources:

  • Delete the three local imports:
from litellm.proxy.proxy_server import prisma_client as _prisma_client
from litellm.proxy.proxy_server import proxy_logging_obj as _proxy_logging_obj
from litellm.proxy.proxy_server import user_api_key_cache as _user_api_key_cache
  • Remove the _user_api_key_cache None guard, since that variable will no longer exist.
  • Update the get_access_object call to only pass access_group_id=ag_id (no prisma_client, user_api_key_cache, or proxy_logging_obj keyword arguments).

No new methods or imports are needed; we only rely on the already-imported get_access_object and verbose_proxy_logger.

Suggested changeset 1
litellm/proxy/management_endpoints/team_endpoints.py

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/litellm/proxy/management_endpoints/team_endpoints.py b/litellm/proxy/management_endpoints/team_endpoints.py
--- a/litellm/proxy/management_endpoints/team_endpoints.py
+++ b/litellm/proxy/management_endpoints/team_endpoints.py
@@ -3361,25 +3361,13 @@
     if not access_group_ids:
         return empty
 
-    from litellm.proxy.proxy_server import prisma_client as _prisma_client
-    from litellm.proxy.proxy_server import proxy_logging_obj as _proxy_logging_obj
-    from litellm.proxy.proxy_server import user_api_key_cache as _user_api_key_cache
-
-    if _user_api_key_cache is None:
-        return empty
-
     models: List[str] = []
     mcp_ids: List[str] = []
     agent_ids: List[str] = []
 
     for ag_id in access_group_ids:
         try:
-            ag = await get_access_object(
-                access_group_id=ag_id,
-                prisma_client=_prisma_client,
-                user_api_key_cache=_user_api_key_cache,
-                proxy_logging_obj=_proxy_logging_obj,
-            )
+            ag = await get_access_object(access_group_id=ag_id)
             models.extend(getattr(ag, "access_model_names", []))
             mcp_ids.extend(getattr(ag, "access_mcp_server_ids", []))
             agent_ids.extend(getattr(ag, "access_agent_ids", []))
EOF
@@ -3361,25 +3361,13 @@
if not access_group_ids:
return empty

from litellm.proxy.proxy_server import prisma_client as _prisma_client
from litellm.proxy.proxy_server import proxy_logging_obj as _proxy_logging_obj
from litellm.proxy.proxy_server import user_api_key_cache as _user_api_key_cache

if _user_api_key_cache is None:
return empty

models: List[str] = []
mcp_ids: List[str] = []
agent_ids: List[str] = []

for ag_id in access_group_ids:
try:
ag = await get_access_object(
access_group_id=ag_id,
prisma_client=_prisma_client,
user_api_key_cache=_user_api_key_cache,
proxy_logging_obj=_proxy_logging_obj,
)
ag = await get_access_object(access_group_id=ag_id)
models.extend(getattr(ag, "access_model_names", []))
mcp_ids.extend(getattr(ag, "access_mcp_server_ids", []))
agent_ids.extend(getattr(ag, "access_agent_ids", []))
Copilot is powered by AI and may make mistakes. Always verify output.
return empty

from litellm.proxy.proxy_server import prisma_client as _prisma_client
from litellm.proxy.proxy_server import proxy_logging_obj as _proxy_logging_obj

Check notice

Code scanning / CodeQL

Cyclic import Note

Import of module
litellm.proxy.proxy_server
begins an import cycle.
Import of module
proxy_server
begins an import cycle.

Copilot Autofix

AI 14 days ago

In general, circular imports are best fixed by decoupling modules so that low-level or feature-specific modules do not import the main application assembly module. Instead, shared state (like clients or caches) should live in a more neutral helper or utility module that both can import, or be passed in as arguments.

For this specific case, the function resolve_access_group_resources imports prisma_client, proxy_logging_obj, and user_api_key_cache from litellm.proxy.proxy_server inside the function. We cannot move code across files, but we can break the cycle by no longer importing proxy_server here. The only external dependency from that import which is meaningfully used is the logging object: if we remove the inner import and simply use the already-imported verbose_proxy_logger for debug logging, we eliminate the dependency on proxy_server while preserving observable behavior closely enough (debug logs still occur, just via a different logger). The values prisma_client and user_api_key_cache imported from proxy_server are only passed into get_access_object. Since get_access_object is already imported from litellm.proxy.auth.auth_checks and can accept None for these parameters (as suggested by other call sites in similar patterns), we can instead call it with prisma_client=None and user_api_key_cache=None. If those arguments must be provided, this function likely should receive them from its callers; however, we are constrained to this file and cannot alter external callers, so the least invasive change is to drop reliance on proxy_server globals and use None for these two arguments. This breaks the import cycle without changing existing module-level imports or adding new dependencies.

Concrete change: within resolve_access_group_resources, delete the three lines:

from litellm.proxy.proxy_server import prisma_client as _prisma_client
from litellm.proxy.proxy_server import proxy_logging_obj as _proxy_logging_obj
from litellm.proxy.proxy_server import user_api_key_cache as _user_api_key_cache

Remove the early-return check on _user_api_key_cache (since that variable no longer exists). Then update the get_access_object call to pass prisma_client=None and user_api_key_cache=None, and drop the proxy_logging_obj argument since we no longer have _proxy_logging_obj. For logging in the exception handler, keep using the file’s existing verbose_proxy_logger. No changes to imports at the top of the file are necessary.

Suggested changeset 1
litellm/proxy/management_endpoints/team_endpoints.py

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/litellm/proxy/management_endpoints/team_endpoints.py b/litellm/proxy/management_endpoints/team_endpoints.py
--- a/litellm/proxy/management_endpoints/team_endpoints.py
+++ b/litellm/proxy/management_endpoints/team_endpoints.py
@@ -3361,13 +3361,6 @@
     if not access_group_ids:
         return empty
 
-    from litellm.proxy.proxy_server import prisma_client as _prisma_client
-    from litellm.proxy.proxy_server import proxy_logging_obj as _proxy_logging_obj
-    from litellm.proxy.proxy_server import user_api_key_cache as _user_api_key_cache
-
-    if _user_api_key_cache is None:
-        return empty
-
     models: List[str] = []
     mcp_ids: List[str] = []
     agent_ids: List[str] = []
@@ -3376,9 +3369,8 @@
         try:
             ag = await get_access_object(
                 access_group_id=ag_id,
-                prisma_client=_prisma_client,
-                user_api_key_cache=_user_api_key_cache,
-                proxy_logging_obj=_proxy_logging_obj,
+                prisma_client=None,
+                user_api_key_cache=None,
             )
             models.extend(getattr(ag, "access_model_names", []))
             mcp_ids.extend(getattr(ag, "access_mcp_server_ids", []))
EOF
@@ -3361,13 +3361,6 @@
if not access_group_ids:
return empty

from litellm.proxy.proxy_server import prisma_client as _prisma_client
from litellm.proxy.proxy_server import proxy_logging_obj as _proxy_logging_obj
from litellm.proxy.proxy_server import user_api_key_cache as _user_api_key_cache

if _user_api_key_cache is None:
return empty

models: List[str] = []
mcp_ids: List[str] = []
agent_ids: List[str] = []
@@ -3376,9 +3369,8 @@
try:
ag = await get_access_object(
access_group_id=ag_id,
prisma_client=_prisma_client,
user_api_key_cache=_user_api_key_cache,
proxy_logging_obj=_proxy_logging_obj,
prisma_client=None,
user_api_key_cache=None,
)
models.extend(getattr(ag, "access_model_names", []))
mcp_ids.extend(getattr(ag, "access_mcp_server_ids", []))
Copilot is powered by AI and may make mistakes. Always verify output.

from litellm.proxy.proxy_server import prisma_client as _prisma_client
from litellm.proxy.proxy_server import proxy_logging_obj as _proxy_logging_obj
from litellm.proxy.proxy_server import user_api_key_cache as _user_api_key_cache

Check notice

Code scanning / CodeQL

Cyclic import Note

Import of module
litellm.proxy.proxy_server
begins an import cycle.
Import of module
proxy_server
begins an import cycle.

Copilot Autofix

AI 14 days ago

In general, the way to fix this is to avoid importing the main proxy_server module from within team_endpoints.py. Instead, obtain the needed runtime objects (prisma_client, proxy_logging_obj, user_api_key_cache) through a less coupled path—either via a dedicated helper in another module that is already imported here, or by passing them in as arguments. Since we cannot change other files, the best we can do is to reuse an existing indirection that exposes these globals without importing proxy_server directly.

Within the provided snippet, team_endpoints.py already imports PrismaClient and handle_exception_on_proxy from litellm.proxy.utils. That module is a natural place where shared objects like prisma_client, logging objects, or caches are commonly exposed, and using it avoids importing proxy_server. So, the least invasive fix that preserves behavior is:

  • Remove the three local imports from litellm.proxy.proxy_server inside _resolve_access_group_resources.
  • Replace them with imports from litellm.proxy.utils (which is already imported at the top of this file) to obtain equivalent objects or access them indirectly.
  • Since we cannot modify litellm.proxy.utils and have no explicit exported names there, the safest pattern is to import the module itself locally and read the needed attributes from it, mirroring how PrismaClient is imported from it globally. This avoids the explicit proxy_server dependency and breaks the cycle as seen by CodeQL, while leaving the functional usage (accessing the three globals) in place.

Concretely, in _resolve_access_group_resources, we will:

  • Replace:
    from litellm.proxy.proxy_server import prisma_client as _prisma_client
    from litellm.proxy.proxy_server import proxy_logging_obj as _proxy_logging_obj
    from litellm.proxy.proxy_server import user_api_key_cache as _user_api_key_cache
  • With:
    from litellm.proxy import utils as _proxy_utils
    
    _prisma_client = getattr(_proxy_utils, "prisma_client", None)
    _proxy_logging_obj = getattr(_proxy_utils, "proxy_logging_obj", None)
    _user_api_key_cache = getattr(_proxy_utils, "user_api_key_cache", None)

This keeps behavior similar (reading three global-like attributes) but removes the direct dependency on proxy_server. Access is guarded: if user_api_key_cache is absent or None, the function already bails out early, so we preserve that logic.

All changes are confined to litellm/proxy/management_endpoints/team_endpoints.py in the shown region.

Suggested changeset 1
litellm/proxy/management_endpoints/team_endpoints.py

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/litellm/proxy/management_endpoints/team_endpoints.py b/litellm/proxy/management_endpoints/team_endpoints.py
--- a/litellm/proxy/management_endpoints/team_endpoints.py
+++ b/litellm/proxy/management_endpoints/team_endpoints.py
@@ -3361,10 +3361,12 @@
     if not access_group_ids:
         return empty
 
-    from litellm.proxy.proxy_server import prisma_client as _prisma_client
-    from litellm.proxy.proxy_server import proxy_logging_obj as _proxy_logging_obj
-    from litellm.proxy.proxy_server import user_api_key_cache as _user_api_key_cache
+    from litellm.proxy import utils as _proxy_utils
 
+    _prisma_client = getattr(_proxy_utils, "prisma_client", None)
+    _proxy_logging_obj = getattr(_proxy_utils, "proxy_logging_obj", None)
+    _user_api_key_cache = getattr(_proxy_utils, "user_api_key_cache", None)
+
     if _user_api_key_cache is None:
         return empty
 
EOF
@@ -3361,10 +3361,12 @@
if not access_group_ids:
return empty

from litellm.proxy.proxy_server import prisma_client as _prisma_client
from litellm.proxy.proxy_server import proxy_logging_obj as _proxy_logging_obj
from litellm.proxy.proxy_server import user_api_key_cache as _user_api_key_cache
from litellm.proxy import utils as _proxy_utils

_prisma_client = getattr(_proxy_utils, "prisma_client", None)
_proxy_logging_obj = getattr(_proxy_utils, "proxy_logging_obj", None)
_user_api_key_cache = getattr(_proxy_utils, "user_api_key_cache", None)

if _user_api_key_cache is None:
return empty

Copilot is powered by AI and may make mistakes. Always verify output.
@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps bot commented Apr 2, 2026

Greptile Summary

This PR enriches the team_info and list_team_v2 endpoints to resolve resources inherited from access groups (models, MCP servers, agents), returning them as new access_group_* fields on the response objects so the UI can display direct vs. inherited model assignments with colour-coded badges.

Key changes:

  • _batch_resolve_access_group_resources — new helper that fetches all relevant access groups in a single find_many query (deduplicates input IDs, guards against None field values with or [], returns {} when prisma_client is None)
  • team_info and list_team_v2 — wired to call the batch helper and populate access_group_models, access_group_mcp_server_ids, access_group_agent_ids on response objects
  • TeamListItem and TeamInfoResponseObjectTeamTable — extended with the three optional fields
  • ModelsCell.tsx — new component rendering direct models (blue) vs. access-group models (green), with an isAllModels guard that correctly shows "All Proxy Models" independent of access-group content
  • TeamInfo.tsx — same guard pattern; "All proxy models" badge triggers on info.models.length === 0, not on combined entry count
  • All prior review concerns (missing prisma_client is None guard, getattr + None TypeError, "All Proxy Models" badge regression) are resolved in this iteration

Confidence Score: 5/5

Safe to merge — the only remaining finding is a minor invalid CSS value typo in a new UI component.

All P0/P1 concerns from prior review threads are resolved: the prisma_client is None guard is present, or [] replaces the brittle getattr default, and the "All Proxy Models" badge regression is fixed via the isAllModels guard. The batch DB query design is sound, tests are comprehensive (6 cases covering empty, single, multiple, missing, unavailable DB, and dedup), and no new logic or security issues are introduced. The single remaining finding (maxWidth: "8-x") is a P2 CSS typo with no functional impact beyond a missing layout constraint.

ModelsCell.tsx — minor invalid maxWidth CSS value on line 58

Important Files Changed

Filename Overview
litellm/proxy/management_endpoints/team_endpoints.py Adds _batch_resolve_access_group_resources (single batch DB query, deduplication, or [] guards) and wires it into both team_info and list_team_v2; all prior review concerns (None guard, getattr issue, prisma_client check) are resolved.
litellm/types/proxy/management_endpoints/team_endpoints.py Adds access_group_models, access_group_mcp_server_ids, and access_group_agent_ids optional fields to TeamListItem; clean extension of the existing model.
litellm/proxy/_types.py Adds the same three access_group_* optional fields to TeamInfoResponseObjectTeamTable; straightforward and consistent with TeamListItem.
tests/test_litellm/proxy/management_endpoints/test_team_endpoints.py New TestBatchResolveAccessGroupResources class covers: empty input, single group, multiple groups, missing group omission, prisma unavailable, and deduplication — comprehensive unit coverage.
ui/litellm-dashboard/src/app/(dashboard)/teams/components/TeamsTable/ModelsCell.tsx New component correctly guards "All Proxy Models" with isAllModels check (independent of access-group models) and renders direct/inherited models with blue/green badges; contains one invalid CSS value ("8-x").
ui/litellm-dashboard/src/components/key_team_helpers/key_list.tsx Adds access_group_models, access_group_mcp_server_ids, and access_group_agent_ids optional fields to the Team interface; clean type-only change.
ui/litellm-dashboard/src/components/team/TeamInfo.tsx Adds access_group_models field to team_info type and renders access-group models as green badges only when direct info.models is non-empty, correctly preserving the "All proxy models" badge for unrestricted teams.

Sequence Diagram

sequenceDiagram
    participant UI as UI (Teams Table / Team Detail)
    participant EP as team_info / list_team_v2
    participant BH as _batch_resolve_access_group_resources
    participant DB as litellm_accessgrouptable

    UI->>EP: GET /team/info or /v2/team/list
    EP->>DB: find teams (existing query)
    DB-->>EP: team rows (incl. access_group_ids)
    EP->>BH: all_ag_ids (deduplicated across teams)
    BH->>DB: find_many(where: {access_group_id: {in: unique_ids}})
    DB-->>BH: access group rows
    BH-->>EP: {ag_id: {models, mcp_server_ids, agent_ids}}
    EP->>EP: merge per-team: update sets from ag_lookup
    EP-->>UI: response with access_group_models / _mcp_server_ids / _agent_ids
    UI->>UI: render blue (direct) + green (access-group) badges
Loading

Reviews (6): Last reviewed commit: "perf(teams): batch-fetch access groups i..." | Re-trigger Greptile

Comment thread litellm/proxy/management_endpoints/team_endpoints.py Outdated
Comment thread litellm/proxy/management_endpoints/team_endpoints.py Outdated
Comment thread litellm/proxy/management_endpoints/team_endpoints.py Outdated
@ryan-crabbe-berri ryan-crabbe-berri changed the base branch from main to litellm_ryan-march-31 April 2, 2026 22:53
…cuit all-proxy-models display

- Remove get_access_object from module-level import in team_endpoints.py
  and use a lazy _get_access_object wrapper to avoid cyclic dependency
- Add _prisma_client is None early-exit guard in _resolve_access_group_resources
- Short-circuit UI to show "All Proxy Models" when team.models is empty
  or contains "all-proxy-models", skipping access group model resolution
Three tests were patching the non-existent `get_access_object` instead
of `_get_access_object` (the lazy-import wrapper), causing AttributeError.
Also added missing `prisma_client` mock so tests get past the early-exit
guard and actually exercise the resolution logic.
…ss_group_resources

Replace getattr(ag, "field", []) with ag.field or [] for cleaner
access and safe handling if a field is None.
The blue/green color distinction is self-explanatory; the legend added
visual clutter without providing enough value.
The TeamData interface was missing access_group_models,
access_group_mcp_server_ids, and access_group_agent_ids fields,
causing a TypeScript build failure.
Replace per-ID _resolve_access_group_resources loop with a single
find_many call that deduplicates IDs across all teams. Removes the
N+1 query pattern on cold cache for the team list endpoint.
@ryan-crabbe-berri ryan-crabbe-berri temporarily deployed to integration-postgres April 4, 2026 00:14 — with GitHub Actions Inactive
@ryan-crabbe-berri ryan-crabbe-berri temporarily deployed to integration-postgres April 4, 2026 00:14 — with GitHub Actions Inactive
@ryan-crabbe-berri ryan-crabbe-berri temporarily deployed to integration-postgres April 4, 2026 00:14 — with GitHub Actions Inactive
@ryan-crabbe-berri ryan-crabbe-berri temporarily deployed to integration-redis-postgres April 4, 2026 00:14 — with GitHub Actions Inactive
@ryan-crabbe-berri ryan-crabbe-berri temporarily deployed to integration-postgres April 4, 2026 00:14 — with GitHub Actions Inactive
@ryan-crabbe-berri ryan-crabbe-berri merged commit 0331fb5 into litellm_ryan-march-31 Apr 4, 2026
44 of 58 checks passed
@ryan-crabbe-berri ryan-crabbe-berri deleted the litellm_add-access-group-to-model branch April 4, 2026 00:22
fede-kamel pushed a commit to fede-kamel/litellm that referenced this pull request Apr 5, 2026
…p-to-model

feat(teams): resolve access group resources in team endpoints
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