From dccc4337037192c5e6526b1a588e1ddca55b16a9 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Fri, 10 Jul 2026 19:13:18 +0000 Subject: [PATCH] fix(agents): preserve config agent_list and agent_access_groups when store_model_in_db is enabled --- .../proxy/agent_endpoints/agent_registry.py | 8 +++- litellm/proxy/proxy_server.py | 5 +-- litellm/types/agents.py | 2 + .../agent_endpoints/test_agent_registry.py | 41 +++++++++++++++++++ 4 files changed, 50 insertions(+), 6 deletions(-) diff --git a/litellm/proxy/agent_endpoints/agent_registry.py b/litellm/proxy/agent_endpoints/agent_registry.py index 1373d055d4f..16a390d63e8 100644 --- a/litellm/proxy/agent_endpoints/agent_registry.py +++ b/litellm/proxy/agent_endpoints/agent_registry.py @@ -16,6 +16,7 @@ from litellm.types.agents import AgentConfig, AgentResponse, PatchAgentRequest class AgentRegistry: def __init__(self): self.agent_list: List[AgentResponse] = [] + self.config_agents: Optional[List[AgentConfig]] = None def reset_agent_list(self): self.agent_list = [] @@ -44,6 +45,7 @@ class AgentRegistry: return hashlib.sha256(json.dumps(agent_config, sort_keys=True).encode()).hexdigest() def load_agents_from_config(self, agent_config: Optional[List[AgentConfig]] = None): + self.config_agents = agent_config if agent_config is None: return None @@ -68,8 +70,10 @@ class AgentRegistry: ): self.reset_agent_list() - if agent_config: - for agent_config_item in agent_config: + resolved_config = agent_config if agent_config is not None else self.config_agents + + if resolved_config: + for agent_config_item in resolved_config: if not isinstance(agent_config_item, dict): raise ValueError("agent_config must be a list of dictionaries") diff --git a/litellm/proxy/proxy_server.py b/litellm/proxy/proxy_server.py index 4114bda47c9..7f6d8fbf12e 100644 --- a/litellm/proxy/proxy_server.py +++ b/litellm/proxy/proxy_server.py @@ -605,8 +605,6 @@ from fastapi.security import OAuth2PasswordBearer from fastapi.security.api_key import APIKeyHeader from fastapi.staticfiles import StaticFiles -from litellm.types.agents import AgentConfig - # import enterprise folder enterprise_router = APIRouter() try: @@ -1885,7 +1883,6 @@ config_passthrough_endpoints: Optional[List[Dict[str, Any]]] = None log_file = "api_log.json" worker_config = None master_key: Optional[str] = None -config_agents: Optional[List[AgentConfig]] = None otel_logging = False prisma_client: Optional[PrismaClient] = None shared_aiohttp_session: Optional["ClientSession"] = None # Global shared session for connection reuse @@ -6380,7 +6377,7 @@ class ProxyConfig: try: db_agents = await AGENT_REGISTRY.get_all_agents_from_db(prisma_client=prisma_client) - AGENT_REGISTRY.load_agents_from_db_and_config(db_agents=db_agents, agent_config=config_agents) + AGENT_REGISTRY.load_agents_from_db_and_config(db_agents=db_agents) except Exception as e: verbose_proxy_logger.exception( "litellm.proxy.proxy_server.py::ProxyConfig:_init_agents_in_db - {}".format(str(e)) diff --git a/litellm/types/agents.py b/litellm/types/agents.py index 254ed5c6c7b..57762c76e5c 100644 --- a/litellm/types/agents.py +++ b/litellm/types/agents.py @@ -181,6 +181,7 @@ class AgentConfig(TypedDict, total=False): agent_card_params: Required[AgentCard] litellm_params: Dict[str, Any] # allow for any future litellm params object_permission: AgentObjectPermission + agent_access_groups: Optional[List[str]] tpm_limit: Optional[int] rpm_limit: Optional[int] session_tpm_limit: Optional[int] @@ -217,6 +218,7 @@ class AgentResponse(BaseModel): litellm_params: Optional[Dict[str, Any]] = None agent_card_params: Dict[str, Any] object_permission: Optional[Dict[str, Any]] = None + agent_access_groups: Optional[List[str]] = None spend: Optional[float] = None tpm_limit: Optional[int] = None rpm_limit: Optional[int] = None diff --git a/tests/test_litellm/proxy/agent_endpoints/test_agent_registry.py b/tests/test_litellm/proxy/agent_endpoints/test_agent_registry.py index ddd9cc09c8e..77c1df84740 100644 --- a/tests/test_litellm/proxy/agent_endpoints/test_agent_registry.py +++ b/tests/test_litellm/proxy/agent_endpoints/test_agent_registry.py @@ -21,6 +21,47 @@ def _sample_agent_card_params() -> dict: } +def test_load_agents_from_db_and_config_preserves_config_agents_when_db_object_reload(): + """ + Regression for #32799: with store_model_in_db, _init_agents_in_db calls + load_agents_from_db_and_config() without passing the config agents. Config + agents registered at startup must not be wiped; they should be re-registered + from the config stored on the registry. + """ + registry = AgentRegistry() + + config_agent = { + "agent_name": "Config Agent", + "agent_card_params": _sample_agent_card_params(), + } + registry.load_agents_from_config([config_agent]) + assert [a.agent_name for a in registry.get_agent_list()] == ["Config Agent"] + + # Simulate the DB reload path with no db agents and no explicit config passed. + registry.load_agents_from_db_and_config(db_agents=[]) + + assert [a.agent_name for a in registry.get_agent_list()] == ["Config Agent"] + + +def test_load_agents_from_config_preserves_agent_access_groups(): + """ + Regression for #32799: agent_access_groups declared on config agents must be + retained on the AgentResponse so config agents can be scoped via access groups. + """ + registry = AgentRegistry() + + config_agent = { + "agent_name": "Scoped Agent", + "agent_card_params": _sample_agent_card_params(), + "agent_access_groups": ["group-a", "group-b"], + } + registry.load_agents_from_config([config_agent]) + + agents = registry.get_agent_list() + assert len(agents) == 1 + assert agents[0].agent_access_groups == ["group-a", "group-b"] + + @pytest.mark.asyncio async def test_update_agent_in_db_clears_static_headers_and_extra_headers_when_omitted(): """