From 9e1071acf8517f71bd25934eaba8b12a4b99356e Mon Sep 17 00:00:00 2001 From: AaronHowell <111272993+AaronHowell@users.noreply.github.com> Date: Wed, 28 Oct 2026 16:03:55 +0800 Subject: [PATCH] fix(proxy): preserve explicit model prices when metadata echoes null Co-authored-by: Bytechoreographer --- .../model_management_endpoints.py | 6 +- .../test_model_management_endpoints.py | 58 ++++++++++++++++++- 2 files changed, 61 insertions(+), 3 deletions(-) diff --git a/litellm/proxy/management_endpoints/model_management_endpoints.py b/litellm/proxy/management_endpoints/model_management_endpoints.py index e625ea6f9f2..355dd835cd8 100644 --- a/litellm/proxy/management_endpoints/model_management_endpoints.py +++ b/litellm/proxy/management_endpoints/model_management_endpoints.py @@ -1164,8 +1164,10 @@ def update_db_model( merged_litellm_params.pop(field, None) if updated_patch.model_info: for field in updated_patch.model_info.model_fields_set: - if field in SPECIAL_MODEL_INFO_PARAMS and getattr(updated_patch.model_info, field) is None: - merged_model_info.pop(field, None) + if field not in SPECIAL_MODEL_INFO_PARAMS or getattr(updated_patch.model_info, field) is not None: + continue + merged_model_info.pop(field, None) + if updated_patch.litellm_params is None or field not in updated_patch.litellm_params.model_fields_set: merged_litellm_params.pop(field, None) for field in _explicitly_cleared_ptu_fields(updated_patch.model_info): merged_model_info.pop(field, None) 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 9ff2a6de9dd..a92ef0f7629 100644 --- a/tests/unit/proxy/management_endpoints/test_model_management_endpoints.py +++ b/tests/unit/proxy/management_endpoints/test_model_management_endpoints.py @@ -40,7 +40,14 @@ from litellm.proxy.management_endpoints.model_management_endpoints import ( ) from litellm.proxy.utils import PrismaClient from litellm.router import Router -from litellm.types.router import Deployment, LiteLLM_Params, ModelInfo, updateDeployment, updateLiteLLMParams +from litellm.types.router import ( + SPECIAL_MODEL_INFO_PARAMS, + Deployment, + LiteLLM_Params, + ModelInfo, + updateDeployment, + updateLiteLLMParams, +) async def _passthrough_row(update_data): @@ -3807,6 +3814,55 @@ class TestUpdateDBModelClearPricing: cannot be cleared via this path. """ + @pytest.mark.parametrize("field", tuple(field for field in SPECIAL_MODEL_INFO_PARAMS if field != "tiered_pricing")) + @pytest.mark.parametrize("price", (0.000009, 0.0)) + @pytest.mark.parametrize("existing_price", (None, 0.000002)) + def test_explicit_price_beats_echoed_model_info_null( + self, field: str, price: float, existing_price: float | None + ) -> None: + from litellm.proxy.management_endpoints.model_management_endpoints import update_db_model + + db_model: Final = Deployment( + model_name="price-test", + litellm_params=LiteLLM_Params.model_validate({"model": "openai/*", field: existing_price}), + model_info=ModelInfo(id="dep-pricing-test"), + ) + result: Final = update_db_model( + db_model=db_model, + updated_patch=updateDeployment( + litellm_params=updateLiteLLMParams.model_validate({field: price}), + model_info=ModelInfo.model_validate({"id": "dep-pricing-test", field: None}), + ), + ) + + params: Final = json.loads(result["litellm_params"]) + info: Final = json.loads(result["model_info"]) + assert params[field] == price + assert field not in info + + @pytest.mark.parametrize("field", tuple(field for field in SPECIAL_MODEL_INFO_PARAMS if field != "tiered_pricing")) + def test_model_info_null_still_clears_when_params_omit_that_field(self, field: str) -> None: + from litellm.proxy.management_endpoints.model_management_endpoints import update_db_model + + db_model: Final = Deployment( + model_name="price-test", + litellm_params=LiteLLM_Params.model_validate({"model": "openai/*", field: 0.000002}), + model_info=ModelInfo(id="dep-pricing-test"), + ) + result: Final = update_db_model( + db_model=db_model, + updated_patch=updateDeployment( + litellm_params=updateLiteLLMParams(tpm=100), + model_info=ModelInfo.model_validate({"id": "dep-pricing-test", field: None}), + ), + ) + + params: Final = json.loads(result["litellm_params"]) + info: Final = json.loads(result["model_info"]) + assert field not in params + assert field not in info + assert params["tpm"] == 100 + def test_clear_input_cost_removes_from_both_blobs(self): from litellm.proxy.management_endpoints.model_management_endpoints import ( update_db_model,