Repository navigation
feat(agents): attach access groups to agents and enforce them for models, MCP servers and agent calls #41634
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from 3 commits
Commits
Show all changes
16 commits
Select commit
Hold shift + click to select a range
b84f8b6
feat(agents): attach access groups to agents and enforce them for mod…
yassin-berriai cf9c8fe
Merge remote-tracking branch 'origin/main' into litellm_agent_access_…
yassin-berriai 433c804
refactor(agents): mark Callable loader aliases for the type disciplin…
yassin-berriai 322262d
style(ui): format add_agent_form test with prettier
yassin-berriai d743e08
test(agents): inject the access group ceiling resolver instead of pat…
yassin-berriai 5b04560
refactor(agents): keep the model listing cap within the type-discipli…
yassin-berriai 3a86567
fix(agents): evict the cached agent access groups on every agent writ…
yassin-berriai 89330cd
refactor(agents): exhaust the agent access match and drop routine com…
yassin-berriai 18c31e6
fix(agents): import assert_never from typing_extensions for Python 3.10
yassin-berriai 1206fa8
refactor(agents): resolve attached access groups from the agent regis…
yassin-berriai 7229fc1
fix(agents): return on every branch of the agent access ceiling so Co…
yassin-berriai 25729c5
refactor(agents): combine key and team agent grants without a fall-th…
yassin-berriai 31caa98
Merge remote-tracking branch 'origin/main' into litellm_agent_access_…
yassin-berriai 85da9cb
Merge remote-tracking branch 'origin/main' into litellm_agent_access_…
yassin-berriai d338d3f
style(agents): tidy typing and docstring in access group ceiling helpers
yassin-berriai 82eef2f
fix(proxy): scope agent permissions to invoking caller
yassin-berriai File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
2 changes: 2 additions & 0 deletions
2
...s/litellm_proxy_extras/migrations/20260917000000_add_agent_access_group_ids/migration.sql
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,2 @@ | ||
| -- AlterTable | ||
| ALTER TABLE "LiteLLM_AgentsTable" ADD COLUMN IF NOT EXISTS "access_group_ids" TEXT[] DEFAULT ARRAY[]::TEXT[]; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,81 @@ | ||
| """ | ||
| Ceiling that an agent's attached access groups place on requests made with that agent's key. | ||
|
|
||
| Keys and teams use access groups as grants. An agent uses them the way it already uses its | ||
| ``object_permission``: the union of the attached groups caps what the agent's key can reach, | ||
| on top of whatever the key and team allow. A group that cannot be loaded contributes nothing, | ||
| so a missing or unreadable group can only narrow the agent, never widen it. | ||
| """ | ||
|
|
||
| import asyncio | ||
| from collections.abc import Awaitable, Callable | ||
| from dataclasses import dataclass | ||
| from typing import Final, TypeAlias | ||
|
|
||
| from fastapi import HTTPException | ||
|
|
||
| from litellm._logging import verbose_proxy_logger | ||
| from litellm.proxy._types import LiteLLM_AccessGroupTable | ||
| from litellm.types.agents import AgentResponse | ||
|
|
||
| AgentLoader: TypeAlias = Callable[[str], Awaitable[AgentResponse | None]] # mutable-ok: Callable parameter syntax | ||
| LoadedAccessGroup: TypeAlias = LiteLLM_AccessGroupTable | None | ||
| AccessGroupLoader: TypeAlias = Callable[[str], Awaitable[LoadedAccessGroup]] # mutable-ok: Callable parameter syntax | ||
|
|
||
|
|
||
| @dataclass(frozen=True, slots=True) | ||
| class AgentAccessGroupCeiling: | ||
| """Everything the agent's attached access groups allow. An empty set denies that resource kind.""" | ||
|
|
||
| access_group_ids: tuple[str, ...] | ||
| models: frozenset[str] | ||
| mcp_server_ids: frozenset[str] | ||
| agent_ids: frozenset[str] | ||
|
|
||
|
|
||
| async def _load_agent(agent_id: str) -> AgentResponse | None: | ||
| from litellm.proxy.common_utils.registry_read_through import get_agent_with_read_through | ||
|
|
||
| return await get_agent_with_read_through(agent_id) | ||
|
|
||
|
|
||
|
devin-ai-integration[bot] marked this conversation as resolved.
Resolved
|
||
| async def _load_access_group(access_group_id: str) -> LoadedAccessGroup: | ||
| from litellm.proxy.auth.auth_checks import get_access_object | ||
| from litellm.proxy.proxy_server import prisma_client, proxy_logging_obj, user_api_key_cache | ||
|
|
||
| if prisma_client is None: | ||
| verbose_proxy_logger.warning("Agent access group %s cannot be loaded without a DB", access_group_id) | ||
| return None | ||
| try: | ||
| return await get_access_object( | ||
| access_group_id=access_group_id, | ||
| prisma_client=prisma_client, | ||
| user_api_key_cache=user_api_key_cache, | ||
| proxy_logging_obj=proxy_logging_obj, | ||
|
greptile-apps[bot] marked this conversation as resolved.
Resolved
|
||
| ) | ||
| except HTTPException as e: | ||
| verbose_proxy_logger.warning( | ||
| "Agent access group %s could not be loaded, treating it as empty: %s", access_group_id, e.detail | ||
| ) | ||
| return None | ||
|
|
||
|
|
||
| async def resolve_agent_access_group_ceiling( | ||
| agent_id: str, | ||
| load_agent: AgentLoader = _load_agent, | ||
| load_access_group: AccessGroupLoader = _load_access_group, | ||
| ) -> AgentAccessGroupCeiling | None: | ||
| """``None`` when the agent has no access groups attached, so nothing is capped.""" | ||
| agent: Final = await load_agent(agent_id) | ||
| access_group_ids: Final = tuple(agent.access_group_ids or ()) if agent is not None else () | ||
| if not access_group_ids: | ||
| return None | ||
|
|
||
| loaded: Final = await asyncio.gather(*(load_access_group(group_id) for group_id in access_group_ids)) | ||
| groups: Final = tuple(group for group in loaded if group is not None) | ||
| return AgentAccessGroupCeiling( | ||
| access_group_ids=access_group_ids, | ||
| models=frozenset(model for group in groups for model in group.access_model_names), | ||
| mcp_server_ids=frozenset(server_id for group in groups for server_id in group.access_mcp_server_ids), | ||
| agent_ids=frozenset(target_id for group in groups for target_id in group.access_agent_ids), | ||
| ) | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.