From b7c43d948e020703d493b816d848f799a371b5c3 Mon Sep 17 00:00:00 2001 From: yuneng-jiang Date: Thu, 12 Mar 2026 17:41:55 -0700 Subject: [PATCH 1/2] Fix public model hub not showing config-defined models after save get_config() internally calls _update_config_from_db which overwrites litellm.public_model_groups with the stale DB value. Moving the in-memory assignment to after get_config()/save_config() ensures the new value persists. Co-Authored-By: Claude Opus 4.6 --- .../model_management_endpoints.py | 9 ++- .../test_model_management_endpoints.py | 58 +++++++++++++++++++ 2 files changed, 64 insertions(+), 3 deletions(-) diff --git a/litellm/proxy/management_endpoints/model_management_endpoints.py b/litellm/proxy/management_endpoints/model_management_endpoints.py index 248b34c3dfc..cd7e3d15402 100644 --- a/litellm/proxy/management_endpoints/model_management_endpoints.py +++ b/litellm/proxy/management_endpoints/model_management_endpoints.py @@ -1186,9 +1186,8 @@ async def update_public_model_groups( }, ) - litellm.public_model_groups = request.model_groups - - # Load existing config + # Load existing config first (this may overwrite in-memory litellm settings + # from DB values via _update_config_from_db), so set the in-memory value AFTER config = await proxy_config.get_config() # Update config with new settings @@ -1200,6 +1199,10 @@ async def update_public_model_groups( # Save the updated config await proxy_config.save_config(new_config=config) + # Set in-memory value AFTER get_config() and save_config() to avoid + # get_config() overwriting with stale DB value + litellm.public_model_groups = request.model_groups + verbose_proxy_logger.debug( f"Updated public model groups to: {request.model_groups} by user: {user_api_key_dict.user_id}" ) diff --git a/tests/test_litellm/proxy/management_endpoints/test_model_management_endpoints.py b/tests/test_litellm/proxy/management_endpoints/test_model_management_endpoints.py index e70bc57e59b..d669c878d04 100644 --- a/tests/test_litellm/proxy/management_endpoints/test_model_management_endpoints.py +++ b/tests/test_litellm/proxy/management_endpoints/test_model_management_endpoints.py @@ -453,6 +453,64 @@ class TestClearCache: ) +class TestUpdatePublicModelGroups: + """Test that update_public_model_groups correctly sets litellm.public_model_groups + even when get_config() overwrites it with stale DB values.""" + + @pytest.mark.asyncio + async def test_public_model_groups_set_after_get_config(self): + """ + Regression test: get_config() internally calls _update_config_from_db which + sets litellm.public_model_groups to the old DB value. The endpoint must set + the in-memory value AFTER get_config() so the new value is not overwritten. + """ + import litellm + from litellm.proxy.management_endpoints.model_management_endpoints import ( + update_public_model_groups, + UpdatePublicModelGroupsRequest, + ) + + old_db_models = ["db-model-1", "db-model-2"] + new_models = ["db-model-1", "db-model-2", "config-model-1", "config-model-2"] + + # Simulate get_config() overwriting litellm.public_model_groups with old DB value + async def mock_get_config(*args, **kwargs): + # This simulates _update_config_from_db calling setattr(litellm, "public_model_groups", old_value) + litellm.public_model_groups = old_db_models + return {"litellm_settings": {"public_model_groups": old_db_models}} + + mock_proxy_config = MagicMock() + mock_proxy_config.get_config = mock_get_config + mock_proxy_config.save_config = AsyncMock() + + admin_user = UserAPIKeyAuth( + user_id="admin", user_role=LitellmUserRoles.PROXY_ADMIN + ) + + request = UpdatePublicModelGroupsRequest(model_groups=new_models) + + original_value = getattr(litellm, "public_model_groups", None) + try: + with patch( + "litellm.proxy.proxy_server.proxy_config", + mock_proxy_config, + ), patch( + "litellm.proxy.proxy_server.store_model_in_db", + True, + ): + result = await update_public_model_groups( + request=request, + user_api_key_dict=admin_user, + ) + + # After the endpoint completes, the in-memory value must reflect + # the NEW models, not the stale DB value + assert litellm.public_model_groups == new_models + assert result["public_model_groups"] == new_models + finally: + litellm.public_model_groups = original_value + + class TestTeamModelUpdate: """Test team model update handles team_id consistently with model creation""" From db0819ad26f6d8ff80e42097843b94e829768fe3 Mon Sep 17 00:00:00 2001 From: yuneng-jiang Date: Thu, 12 Mar 2026 17:45:03 -0700 Subject: [PATCH 2/2] Fix same stale-overwrite bug in update_useful_links Apply the same fix: move litellm.public_model_groups_links assignment to after get_config()/save_config() so it is not overwritten by the stale DB value read. Co-Authored-By: Claude Opus 4.6 --- .../model_management_endpoints.py | 9 ++-- .../test_model_management_endpoints.py | 47 +++++++++++++++++++ 2 files changed, 53 insertions(+), 3 deletions(-) diff --git a/litellm/proxy/management_endpoints/model_management_endpoints.py b/litellm/proxy/management_endpoints/model_management_endpoints.py index cd7e3d15402..0fbe2f8ab18 100644 --- a/litellm/proxy/management_endpoints/model_management_endpoints.py +++ b/litellm/proxy/management_endpoints/model_management_endpoints.py @@ -1256,9 +1256,8 @@ async def update_useful_links( }, ) - litellm.public_model_groups_links = request.useful_links - - # Load existing config + # Load existing config first (this may overwrite in-memory litellm settings + # from DB values via _update_config_from_db), so set the in-memory value AFTER config = await proxy_config.get_config() # Update config with new settings @@ -1270,6 +1269,10 @@ async def update_useful_links( # Save the updated config await proxy_config.save_config(new_config=config) + # Set in-memory value AFTER get_config() and save_config() to avoid + # get_config() overwriting with stale DB value + litellm.public_model_groups_links = request.useful_links + verbose_proxy_logger.debug( f"Updated useful links to: {request.useful_links} by user: {user_api_key_dict.user_id}" ) diff --git a/tests/test_litellm/proxy/management_endpoints/test_model_management_endpoints.py b/tests/test_litellm/proxy/management_endpoints/test_model_management_endpoints.py index d669c878d04..f3c89003105 100644 --- a/tests/test_litellm/proxy/management_endpoints/test_model_management_endpoints.py +++ b/tests/test_litellm/proxy/management_endpoints/test_model_management_endpoints.py @@ -510,6 +510,53 @@ class TestUpdatePublicModelGroups: finally: litellm.public_model_groups = original_value + @pytest.mark.asyncio + async def test_useful_links_set_after_get_config(self): + """ + Regression test: same stale-overwrite bug as public_model_groups applies + to update_useful_links / public_model_groups_links. + """ + import litellm + from litellm.proxy.management_endpoints.model_management_endpoints import ( + update_useful_links, + ) + from litellm.types.proxy.management_endpoints.model_management_endpoints import ( + UpdateUsefulLinksRequest, + ) + + old_links = {"Old Doc": "https://old.example.com"} + new_links = {"New Doc": "https://new.example.com", "API Ref": "https://api.example.com"} + + async def mock_get_config(*args, **kwargs): + litellm.public_model_groups_links = old_links + return {"litellm_settings": {"public_model_groups_links": old_links}} + + mock_proxy_config = MagicMock() + mock_proxy_config.get_config = mock_get_config + mock_proxy_config.save_config = AsyncMock() + + admin_user = UserAPIKeyAuth( + user_id="admin", user_role=LitellmUserRoles.PROXY_ADMIN + ) + + request = UpdateUsefulLinksRequest(useful_links=new_links) + + original_value = getattr(litellm, "public_model_groups_links", None) + try: + with patch( + "litellm.proxy.proxy_server.proxy_config", + mock_proxy_config, + ): + result = await update_useful_links( + request=request, + user_api_key_dict=admin_user, + ) + + assert litellm.public_model_groups_links == new_links + assert result["useful_links"] == new_links + finally: + litellm.public_model_groups_links = original_value + class TestTeamModelUpdate: """Test team model update handles team_id consistently with model creation"""