From d066f502da8db5df23faeaf844e375854bfb7b00 Mon Sep 17 00:00:00 2001 From: Rupesh Poojary Date: Fri, 23 Oct 2026 17:34:36 +0530 Subject: [PATCH 1/2] fix(cost_calculator): honor base_model's own provider in cost-logging metadata too completion_cost() already resolves the correct provider for billing when base_model points at a priced row under a different provider than the deployment's transport (e.g. a chatgpt/ subscription deployment priced via an openai/ or zai/ base_model, fixed in #46318). The diagnostic model_map_information field built in litellm_logging.py never got the same correction: it calls get_model_info() with the raw transport provider, which _check_provider_match rejects, so model_map_value stays None even though spend is now computed correctly. Reuses the same _base_model_pricing_provider helper for this lookup too. Fixes #47908 (the remaining part after #46318) --- litellm/litellm_core_utils/litellm_logging.py | 13 +++++++++- .../test_litellm_logging.py | 26 +++++++++++++++++++ 2 files changed, 38 insertions(+), 1 deletion(-) diff --git a/litellm/litellm_core_utils/litellm_logging.py b/litellm/litellm_core_utils/litellm_logging.py index d75d35e4531..53bdf671b32 100644 --- a/litellm/litellm_core_utils/litellm_logging.py +++ b/litellm/litellm_core_utils/litellm_logging.py @@ -51,6 +51,7 @@ from litellm.constants import ( from litellm.cost_calculator import ( RealtimeAPITokenUsageProcessor, ResponsesWebSocketTokenUsageProcessor, + _base_model_pricing_provider, get_usage_object, pricing_entry_for_cost_calc, select_model_name_for_cost_calc, @@ -6693,10 +6694,20 @@ class StandardLoggingPayloadSetup: if model_cost_name is None: model_cost_information = StandardLoggingModelInformation(model_map_key="", model_map_value=None) else: + # When base_model points at a priced row under a different provider than the deployment's own + # transport (e.g. a chatgpt/ subscription deployment priced via an openai/ base_model), completion_cost + # already resolves the correct provider for billing. Without the same correction here, get_model_info + # rejects the match on provider mismatch and this diagnostic field stays None even though spend is + # computed correctly. + pricing_provider: Final = ( + _base_model_pricing_provider(base_model, custom_llm_provider) + if base_model is not None and custom_pricing is not True + else None + ) try: _model_cost_information: Final = litellm.get_model_info( model=pricing_model_name or model_cost_name, - custom_llm_provider=custom_llm_provider, + custom_llm_provider=pricing_provider or custom_llm_provider, api_base=api_base, ) model_cost_information = StandardLoggingModelInformation( diff --git a/tests/unit/litellm_core_utils/test_litellm_logging.py b/tests/unit/litellm_core_utils/test_litellm_logging.py index bbfae2229ce..2a8b797e12b 100644 --- a/tests/unit/litellm_core_utils/test_litellm_logging.py +++ b/tests/unit/litellm_core_utils/test_litellm_logging.py @@ -11974,6 +11974,32 @@ def test_get_model_cost_information_custom_pricing_uses_base_model(): assert result["model_map_value"] is not None assert result["model_map_key"] != "invoke_test_claude" +@pytest.mark.usefixtures("_vcr_outcome_gate", "drain_logging_worker", "isolate_litellm_state", "setup_and_teardown") +def test_get_model_cost_information_base_model_cross_provider(): + """Regression test for #47908: a deployment whose transport provider differs from the priced + base_model's own registered provider must still populate model_map_value, not just completion_cost's + return value, since the two used to disagree (spend computed correctly, this field stayed None).""" + litellm.register_model( + model_cost={ + "custom-provider/base-model-cross-provider-test": { + "input_cost_per_token": 1.5e-07, + "output_cost_per_token": 5e-07, + "litellm_provider": "custom-provider", + "mode": "chat", + } + } + ) + + result = StandardLoggingPayloadSetup.get_model_cost_information( + base_model="custom-provider/base-model-cross-provider-test", + custom_pricing=False, + custom_llm_provider="openai", + init_response_obj={}, + ) + assert result["model_map_value"] is not None + assert result["model_map_value"]["input_cost_per_token"] == 1.5e-07 + assert result["model_map_value"]["litellm_provider"] == "custom-provider" + @pytest.mark.usefixtures("_vcr_outcome_gate", "drain_logging_worker", "isolate_litellm_state", "setup_and_teardown") def test_standard_logging_payload_uses_deployment_when_no_base_model(): """metadata["deployment"] is used for cost-map lookup when base_model is not set.""" From f0811a01659c8d28a93fb02cc1fa7c485edd13f1 Mon Sep 17 00:00:00 2001 From: Rupesh Poojary Date: Sun, 25 Oct 2026 14:14:10 +0530 Subject: [PATCH 2/2] refactor(cost_calculator): make base_model_pricing_provider public for cross-module use Importing the underscore-prefixed helper from litellm_logging added one reportPrivateUsage and breached the basedpyright ceiling. --- litellm/cost_calculator.py | 4 ++-- litellm/litellm_core_utils/litellm_logging.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/litellm/cost_calculator.py b/litellm/cost_calculator.py index 550bbfcde98..d22582a93cb 100644 --- a/litellm/cost_calculator.py +++ b/litellm/cost_calculator.py @@ -916,7 +916,7 @@ def _declares_rates(model_info: ModelInfo | None) -> bool: return model_info is not None and cost_map_key_prices_anything(model_info["key"]) -def _base_model_pricing_provider(base_model: str, custom_llm_provider: str | None) -> str | None: +def base_model_pricing_provider(base_model: str, custom_llm_provider: str | None) -> str | None: """The provider to price an explicit base_model under, when the deployment's own provider has no rates for it (e.g. chatgpt/ subscription rows) but the base model's own row does""" if custom_llm_provider is None or _declares_rates(_cost_map_model_info(base_model, custom_llm_provider)): @@ -1622,7 +1622,7 @@ def completion_cost( _configured_search_cost_per_query(litellm_logging_obj) if custom_pricing else None ) base_model_provider: Final = ( - _base_model_pricing_provider( + base_model_pricing_provider( base_model, _get_provider_for_cost_calc( model=model, diff --git a/litellm/litellm_core_utils/litellm_logging.py b/litellm/litellm_core_utils/litellm_logging.py index 53bdf671b32..1c94a28e46e 100644 --- a/litellm/litellm_core_utils/litellm_logging.py +++ b/litellm/litellm_core_utils/litellm_logging.py @@ -51,7 +51,7 @@ from litellm.constants import ( from litellm.cost_calculator import ( RealtimeAPITokenUsageProcessor, ResponsesWebSocketTokenUsageProcessor, - _base_model_pricing_provider, + base_model_pricing_provider, get_usage_object, pricing_entry_for_cost_calc, select_model_name_for_cost_calc, @@ -6700,7 +6700,7 @@ class StandardLoggingPayloadSetup: # rejects the match on provider mismatch and this diagnostic field stays None even though spend is # computed correctly. pricing_provider: Final = ( - _base_model_pricing_provider(base_model, custom_llm_provider) + base_model_pricing_provider(base_model, custom_llm_provider) if base_model is not None and custom_pricing is not True else None )