fix(agents): preserve config agent_list and agent_access_groups when store_model_in_db is enabled

This commit is contained in:
Devin AI 2026-07-10 19:13:18 +00:00
parent b9008cca35
commit dccc433703
4 changed files with 50 additions and 6 deletions

View file

@ -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")

View file

@ -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))

View file

@ -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

View file

@ -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():
"""