diff --git a/litellm/cost_calculator.py b/litellm/cost_calculator.py index eddead2a530..f1cc430a350 100644 --- a/litellm/cost_calculator.py +++ b/litellm/cost_calculator.py @@ -1039,6 +1039,28 @@ def _apply_deployment_cost_discount( return final_cost, cost_discount, discount_amount +def _apply_discounts( + base_cost: float, + custom_llm_provider: "str | None", + litellm_logging_obj: "LitellmLoggingObject | None", +) -> "tuple[float, float, float]": + """ + Apply the deployment-level ``cost_discount`` when present, otherwise fall back to + the provider-level ``litellm.cost_discount_config``. + Returns (final_cost, discount_percent, discount_amount). + """ + deployment_cost_discount = _get_deployment_cost_discount(litellm_logging_obj) + if deployment_cost_discount is not None: + return _apply_deployment_cost_discount( + base_cost=base_cost, + cost_discount=deployment_cost_discount, + ) + return _apply_cost_discount( + base_cost=base_cost, + custom_llm_provider=custom_llm_provider, + ) + + def _apply_cost_margin( base_cost: float, custom_llm_provider: Optional[str], @@ -1515,9 +1537,10 @@ def completion_cost( _final_cost, discount_percent, discount_amount, - ) = _apply_cost_discount( + ) = _apply_discounts( base_cost=_final_cost, custom_llm_provider=custom_llm_provider, + litellm_logging_obj=litellm_logging_obj, ) # Apply margin from module-level config if configured @@ -1669,28 +1692,15 @@ def completion_cost( _final_cost += sum(additional_costs.values()) original_cost = _final_cost - _deployment_cost_discount = _get_deployment_cost_discount(litellm_logging_obj) - if _deployment_cost_discount is not None: - ( - _final_cost, - discount_percent, - discount_amount, - ) = _apply_deployment_cost_discount( - base_cost=_final_cost, - cost_discount=_deployment_cost_discount, - ) - elif litellm.cost_discount_config: - ( - _final_cost, - discount_percent, - discount_amount, - ) = _apply_cost_discount( - base_cost=_final_cost, - custom_llm_provider=custom_llm_provider, - ) - else: - discount_percent = 0.0 - discount_amount = 0.0 + ( + _final_cost, + discount_percent, + discount_amount, + ) = _apply_discounts( + base_cost=_final_cost, + custom_llm_provider=custom_llm_provider, + litellm_logging_obj=litellm_logging_obj, + ) # Apply margin from module-level config if configured if litellm.cost_margin_config: @@ -1841,7 +1851,14 @@ def response_cost_calculator( response_object._hidden_params["optional_params"] = optional_params provider_response_cost = get_response_cost_from_hidden_params(response_object._hidden_params) if provider_response_cost is not None: - return provider_response_cost + deployment_cost_discount = _get_deployment_cost_discount(litellm_logging_obj) + if deployment_cost_discount is None: + return provider_response_cost + discounted_provider_cost, _, _ = _apply_deployment_cost_discount( + base_cost=provider_response_cost, + cost_discount=deployment_cost_discount, + ) + return discounted_provider_cost response_cost = completion_cost( completion_response=response_object, diff --git a/litellm/litellm_core_utils/litellm_logging.py b/litellm/litellm_core_utils/litellm_logging.py index 83d6fcc0bee..b882d5dcf9d 100644 --- a/litellm/litellm_core_utils/litellm_logging.py +++ b/litellm/litellm_core_utils/litellm_logging.py @@ -218,7 +218,8 @@ _STANDARD_LOGGING_METADATA_KEYS: frozenset = frozenset(StandardLoggingMetadata._ ### GLOBAL VARIABLES ### # Cache custom pricing keys as frozenset for O(1) lookups instead of looping through 49 keys -_CUSTOM_PRICING_KEYS: frozenset = frozenset(CustomPricingLiteLLMParams.model_fields.keys()) +_COST_ADJUSTMENT_KEYS: frozenset = frozenset({"cost_discount"}) +_CUSTOM_PRICING_KEYS: frozenset = frozenset(CustomPricingLiteLLMParams.model_fields.keys()) - _COST_ADJUSTMENT_KEYS sentry_sdk_instance = None capture_exception = None @@ -4482,7 +4483,9 @@ def use_custom_pricing_for_model(litellm_params: Optional[dict]) -> bool: """ Check if the model uses custom pricing - Returns True if any of `SPECIAL_MODEL_INFO_PARAMS` are present in `litellm_params` or `model_info` + Returns True if any custom pricing field is present in `litellm_params` or `model_info`. + `cost_discount` is excluded: it adjusts an already-priced cost, it does not define + pricing, so it must not override `base_model` during cost-model selection. """ if litellm_params is None: return False diff --git a/tests/test_litellm/litellm_core_utils/test_litellm_logging.py b/tests/test_litellm/litellm_core_utils/test_litellm_logging.py index edc257f4c3f..a1045886b24 100644 --- a/tests/test_litellm/litellm_core_utils/test_litellm_logging.py +++ b/tests/test_litellm/litellm_core_utils/test_litellm_logging.py @@ -203,6 +203,26 @@ def test_use_custom_pricing_not_detected_litellm_metadata_no_pricing(): assert use_custom_pricing_for_model(litellm_params) is False +def test_use_custom_pricing_ignores_cost_discount(): + """cost_discount adjusts an already-priced cost; alone it must not flag custom pricing, + otherwise cost-model selection would bypass base_model.""" + from litellm.litellm_core_utils.litellm_logging import use_custom_pricing_for_model + + discount_only = { + "metadata": { + "model_info": {"id": "some-id", "cost_discount": 0.5, "base_model": "azure/gpt-4o"}, + }, + } + assert use_custom_pricing_for_model(discount_only) is False + + discount_with_pricing = { + "metadata": { + "model_info": {"id": "some-id", "cost_discount": 0.5, "input_cost_per_token": 0.0003}, + }, + } + assert use_custom_pricing_for_model(discount_with_pricing) is True + + def test_response_cost_calculator_uses_router_model_id_from_litellm_metadata(): """_response_cost_calculator should extract router_model_id from litellm_params.litellm_metadata.model_info.id when the result object diff --git a/tests/test_litellm/test_cost_calculator.py b/tests/test_litellm/test_cost_calculator.py index 2b25e526e32..a7e055ac7d8 100644 --- a/tests/test_litellm/test_cost_calculator.py +++ b/tests/test_litellm/test_cost_calculator.py @@ -1954,6 +1954,88 @@ def test_model_info_rejects_out_of_range_cost_discount(): ModelInfo(id="bad-negative", cost_discount=-0.1) +def test_deployment_cost_discount_respects_base_model(): + """cost_discount must not flip custom-pricing model selection: base_model stays the pricing basis.""" + from litellm.litellm_core_utils.litellm_logging import use_custom_pricing_for_model + + logging_obj = _StubLoggingObj("gpt-4o-mini", cost_discount=0.5) + logging_obj.litellm_params["metadata"]["model_info"]["base_model"] = "gpt-4o" + custom_pricing = use_custom_pricing_for_model(logging_obj.litellm_params) + assert custom_pricing is False + + base_model_cost = completion_cost( + completion_response=_make_response(), + model="gpt-4o-mini", + custom_llm_provider="openai", + base_model="gpt-4o", + custom_pricing=False, + ) + + discounted_cost = completion_cost( + completion_response=_make_response(), + model="gpt-4o-mini", + custom_llm_provider="openai", + base_model="gpt-4o", + custom_pricing=custom_pricing, + litellm_logging_obj=logging_obj, + ) + + assert base_model_cost > 0 + assert discounted_cost == pytest.approx(base_model_cost * (1 - 0.5), rel=1e-9) + + +def test_deployment_cost_discount_applied_to_search_path(): + """The search early-return path applies the deployment discount with the same precedence as the token path.""" + base_cost = completion_cost( + completion_response=None, + model="tavily-search", + custom_llm_provider="tavily", + call_type="search", + optional_params={}, + ) + + discounted_cost = completion_cost( + completion_response=None, + model="tavily-search", + custom_llm_provider="tavily", + call_type="search", + optional_params={}, + litellm_logging_obj=_StubLoggingObj("tavily-search", cost_discount=0.5), + ) + + assert base_cost > 0 + assert discounted_cost == pytest.approx(base_cost * (1 - 0.5), rel=1e-9) + + +def test_deployment_cost_discount_applied_to_provider_reported_cost(): + """Provider-reported response cost is discounted instead of bypassing the deployment discount.""" + from litellm.cost_calculator import response_cost_calculator + + def make_response_with_provider_cost(): + response = _make_response() + response._hidden_params = {"additional_headers": {"llm_provider-x-litellm-response-cost": 0.02}} + return response + + undiscounted = response_cost_calculator( + response_object=make_response_with_provider_cost(), + model="gpt-4o-mini", + custom_llm_provider="openai", + call_type="completion", + optional_params={}, + ) + assert undiscounted == pytest.approx(0.02, rel=1e-9) + + discounted = response_cost_calculator( + response_object=make_response_with_provider_cost(), + model="gpt-4o-mini", + custom_llm_provider="openai", + call_type="completion", + optional_params={}, + litellm_logging_obj=_StubLoggingObj("gpt-4o-mini", cost_discount=0.5), + ) + assert discounted == pytest.approx(0.02 * (1 - 0.5), rel=1e-9) + + def test_cost_margin_percentage(): """ Test that percentage-based cost margin is applied correctly