From cc692e8514b453cb1ae65b09d0c6fa5e52655c01 Mon Sep 17 00:00:00 2001 From: moe-berri Date: Wed, 28 Oct 2026 13:56:09 -0700 Subject: [PATCH] fix(proxy): clarify atomic model provider updates --- .../model_management_endpoints.py | 6 +++- .../test_model_management_endpoints.py | 28 +++++++++++++++++++ 2 files changed, 33 insertions(+), 1 deletion(-) diff --git a/litellm/proxy/management_endpoints/model_management_endpoints.py b/litellm/proxy/management_endpoints/model_management_endpoints.py index 3caec435b7b..7f6e5ef38f6 100644 --- a/litellm/proxy/management_endpoints/model_management_endpoints.py +++ b/litellm/proxy/management_endpoints/model_management_endpoints.py @@ -434,7 +434,11 @@ def _raise_on_unloadable_patch(db_model: Deployment, patch_data: updateDeploymen Router.resolve_deployment_provider(reloaded.litellm_params) except Exception as e: # noqa: BLE001 # the router drops the deployment on any exception from this check raise ProxyException( - message=f"The router cannot load this deployment, so the update was not saved: {e}", + message=( + "The router cannot load this deployment, so the update was not saved. " + "Use a provider-prefixed model or include model and custom_llm_provider together in the same update. " + f"Provider error: {e}" + ), type=ProxyErrorTypes.validation_error.value, code=status.HTTP_400_BAD_REQUEST, param="litellm_params.model", diff --git a/tests/unit/proxy/management_endpoints/test_model_management_endpoints.py b/tests/unit/proxy/management_endpoints/test_model_management_endpoints.py index 54d4d684431..09a480b86f2 100644 --- a/tests/unit/proxy/management_endpoints/test_model_management_endpoints.py +++ b/tests/unit/proxy/management_endpoints/test_model_management_endpoints.py @@ -10229,6 +10229,34 @@ class TestPatchModelRejectsParamsTheRouterDrops: assert exc_info.value.param == "litellm_params.model" assert "@cf/cloudflare/clef-flash" in exc_info.value.message + def test_model_and_provider_can_be_updated_together_after_model_only_rejection( + self, monkeypatch: pytest.MonkeyPatch + ) -> None: + from litellm.proxy.management_endpoints.model_management_endpoints import ( + _raise_on_unloadable_patch, + update_db_model, + ) + from litellm.proxy.proxy_server import proxy_config + + monkeypatch.setenv("LITELLM_SALT_KEY", "fake-atomic-model-patch-test-salt") + stored: Final = self._stored(model="cloudflare/clef-flash") + with pytest.raises(ProxyException) as exc_info: + _raise_on_unloadable_patch( + stored, updateDeployment(litellm_params=updateLiteLLMParams(model="atomic-test-model")) + ) + assert exc_info.value.code == "400" + assert "include model and custom_llm_provider together in the same update" in exc_info.value.message + + combined: Final = updateDeployment( + litellm_params=updateLiteLLMParams(model="atomic-test-model", custom_llm_provider="openai") + ) + _raise_on_unloadable_patch(stored, combined) + saved: Final = json.loads(update_db_model(db_model=stored, updated_patch=combined)["litellm_params"]) + reloaded: Final = {key: proxy_config.resolve_db_litellm_param(key, value) for key, value in saved.items()} + assert reloaded["model"] == "atomic-test-model" + assert reloaded["custom_llm_provider"] == "openai" + assert self._router_loads(reloaded) + def test_patch_not_touching_routing_params_is_never_judged(self, monkeypatch: pytest.MonkeyPatch) -> None: from litellm.proxy.management_endpoints.model_management_endpoints import _raise_on_unloadable_patch