From 7446a1736137e9f0a60221595b5b8ae1efd5f0c5 Mon Sep 17 00:00:00 2001 From: LABMDKPhan Date: Tue, 11 Aug 2026 16:12:59 +0700 Subject: [PATCH 1/2] fix(router): fold custom pricing from litellm_params into model_info When a deployment carries custom pricing fields (input_cost_per_token, cache_read_input_token_cost, etc.) in litellm_params, they were not propagated into model_info during _update_kwargs_with_deployment. This caused use_custom_pricing_for_model() to return False, resulting in $0 spend for azure_ai + anthropic_messages requests. Fix: iterate CustomPricingLiteLLMParams.model_fields and copy non-None values into model_info so downstream cost calculation picks them up. Fixes #23309, #24204 Co-Authored-By: Claude Opus 4.6 --- litellm/router.py | 11 ++++- .../test_router_helper_utils.py | 43 +++++++++++++++++++ 2 files changed, 52 insertions(+), 2 deletions(-) diff --git a/litellm/router.py b/litellm/router.py index feaf69a44ae..42548ae8419 100644 --- a/litellm/router.py +++ b/litellm/router.py @@ -3117,8 +3117,15 @@ class Router: self._merge_tools_from_deployment(deployment=deployment, kwargs=kwargs) model_info = deployment.get("model_info", {}).copy() - deployment_litellm_model_name = deployment["litellm_params"]["model"] - deployment_api_base = deployment["litellm_params"].get("api_base") + dep_litellm_params = deployment.get("litellm_params", {}) + if not isinstance(dep_litellm_params, dict): + dep_litellm_params = dep_litellm_params.model_dump(exclude_none=True) + for field in CustomPricingLiteLLMParams.model_fields: + val = dep_litellm_params.get(field) + if val is not None: + model_info[field] = val + deployment_litellm_model_name = dep_litellm_params["model"] + deployment_api_base = dep_litellm_params.get("api_base") deployment_model_name: Final = deployment["model_name"] if is_clientside_credential(request_kwargs=kwargs): deployment_pydantic_obj: Final = self._handle_clientside_credential( diff --git a/tests/router_unit_tests/test_router_helper_utils.py b/tests/router_unit_tests/test_router_helper_utils.py index 0655763d41b..921bb699d4b 100644 --- a/tests/router_unit_tests/test_router_helper_utils.py +++ b/tests/router_unit_tests/test_router_helper_utils.py @@ -405,6 +405,49 @@ def test_update_kwargs_with_deployment(model_list): assert all(field in kwargs["metadata"] for field in set_fields) +def test_update_kwargs_with_deployment_folds_custom_pricing(): + """Custom pricing fields from litellm_params should be folded into model_info + so that use_custom_pricing_for_model() detects them downstream. + + Regression test for azure_ai + anthropic_messages $0 spend bug (#23309, #24204). + """ + from litellm.litellm_core_utils.litellm_logging import use_custom_pricing_for_model + + model_list = [ + { + "model_name": "claude-sonnet-4", + "litellm_params": { + "model": "azure_ai/claude-sonnet-4-20250514", + "api_key": "fake-key", + "api_base": "https://example.ai.azure.com/v1", + "input_cost_per_token": 0.000003, + "output_cost_per_token": 0.000015, + "cache_read_input_token_cost": 0.0000003, + "cache_creation_input_token_cost": 0.00000375, + }, + }, + ] + router = Router(model_list=model_list) + kwargs: dict = {"metadata": {}} + deployment = router.get_deployment_by_model_group_name( + model_group_name="claude-sonnet-4" + ) + router._update_kwargs_with_deployment( + deployment=deployment, + kwargs=kwargs, + ) + + model_info = kwargs["metadata"]["model_info"] + model_info_dict = model_info if isinstance(model_info, dict) else model_info.model_dump() + assert model_info_dict.get("input_cost_per_token") == 0.000003 + assert model_info_dict.get("output_cost_per_token") == 0.000015 + assert model_info_dict.get("cache_read_input_token_cost") == 0.0000003 + assert model_info_dict.get("cache_creation_input_token_cost") == 0.00000375 + + litellm_params_with_metadata = {"metadata": {"model_info": model_info_dict}} + assert use_custom_pricing_for_model(litellm_params_with_metadata) is True + + def test_update_kwargs_with_default_litellm_params(model_list): """Test if the '_update_kwargs_with_default_litellm_params' function is working correctly""" router = Router( From ff3cd1a266ae040552044ae7a3320aabb65b9069 Mon Sep 17 00:00:00 2001 From: khoapmdx-oss Date: Wed, 12 Aug 2026 10:34:29 +0700 Subject: [PATCH 2/2] fix(lint): remove mutable-dict default to satisfy LIT002 gate Co-Authored-By: Claude Opus 4.6 --- litellm/router.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/litellm/router.py b/litellm/router.py index d617f758940..773511255c2 100644 --- a/litellm/router.py +++ b/litellm/router.py @@ -3234,7 +3234,7 @@ class Router: self._merge_tools_from_deployment(deployment=deployment, kwargs=kwargs) model_info = deployment.get("model_info", {}).copy() - dep_litellm_params = deployment.get("litellm_params", {}) + dep_litellm_params = deployment["litellm_params"] if not isinstance(dep_litellm_params, dict): dep_litellm_params = dep_litellm_params.model_dump(exclude_none=True) for field in CustomPricingLiteLLMParams.model_fields: