fix(model_management): refresh in-memory router after POST /model/update (#26427)

This commit is contained in:
michelligabriele
2026-04-25 14:10:23 -07:00
committed by GitHub
parent 9c00f9776b
commit ae925baaa1
2 changed files with 85 additions and 0 deletions
@@ -1190,6 +1190,9 @@ async def update_model(
data=_data, # type: ignore
)
# Clear cache and reload models (uses config setting or defaults to preserving config models for DB updates)
await clear_cache()
## CREATE AUDIT LOG ##
asyncio.create_task(
create_object_audit_log(
@@ -487,6 +487,88 @@ class TestClearCache:
)
class TestUpdateModel:
"""
Tests for the update_model (POST /model/update) handler.
"""
@pytest.mark.asyncio
async def test_update_model_clears_cache_after_db_write(self):
"""
Regression test for the stale-router bug: POST /model/update must refresh
the in-memory router after persisting to LiteLLM_ProxyModelTable, otherwise
model-level guardrails (and any other litellm_params change) silently no-op
until the APScheduler reload tick fires ~30 s later.
"""
from litellm.proxy.management_endpoints.model_management_endpoints import (
update_model,
)
from litellm.types.router import (
ModelInfo,
updateDeployment,
updateLiteLLMParams,
)
model_id = "db-model-under-test"
existing_row = MagicMock()
existing_row.litellm_params = {
"model": "openai/gpt-4o-mini",
"api_key": "sk-existing",
}
existing_row.model_dump.return_value = {
"model_name": "gpt-4o-mini",
"litellm_params": existing_row.litellm_params,
"model_info": {"id": model_id},
}
existing_row.model_dump_json.return_value = "{}"
updated_row = MagicMock()
updated_row.model_dump_json.return_value = "{}"
mock_prisma = MagicMock()
mock_prisma.db.litellm_proxymodeltable.find_unique = AsyncMock(
return_value=existing_row
)
mock_prisma.db.litellm_proxymodeltable.update = AsyncMock(
return_value=updated_row
)
mock_router = MagicMock()
admin_user = UserAPIKeyAuth(
user_id="admin", user_role=LitellmUserRoles.PROXY_ADMIN
)
with (
patch("litellm.proxy.proxy_server.prisma_client", mock_prisma),
patch("litellm.proxy.proxy_server.llm_router", mock_router),
patch("litellm.proxy.proxy_server.store_model_in_db", True),
patch("litellm.proxy.proxy_server.premium_user", True),
patch(
"litellm.proxy.management_endpoints.model_management_endpoints.ModelManagementAuthChecks.can_user_make_model_call",
new=AsyncMock(return_value=None),
),
patch(
"litellm.proxy.management_endpoints.model_management_endpoints.encrypt_value_helper",
side_effect=lambda value: value,
),
patch(
"litellm.proxy.management_endpoints.model_management_endpoints.clear_cache",
new=AsyncMock(return_value=None),
) as mock_clear_cache,
):
await update_model(
model_params=updateDeployment(
litellm_params=updateLiteLLMParams(guardrails=["g1"]),
model_info=ModelInfo(id=model_id),
),
user_api_key_dict=admin_user,
)
mock_prisma.db.litellm_proxymodeltable.update.assert_awaited_once()
mock_clear_cache.assert_awaited_once_with()
class TestUpdatePublicModelGroups:
"""Test that update_public_model_groups correctly sets litellm.public_model_groups
even when get_config() overwrites it with stale DB values."""