fix(cost): honor base_model and early-return cost paths with deployment cost_discount

cost_discount alone no longer marks a deployment as custom pricing, so cost-model selection keeps using base_model as the pricing basis. The search path and provider-reported response costs now apply the deployment discount with the same precedence as the token path.
This commit is contained in:
mateo-berri 2026-07-29 20:48:38 -07:00
parent 29b189e26b
commit cb0ba77cd1
4 changed files with 148 additions and 26 deletions

View file

@ -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,

View file

@ -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

View file

@ -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

View file

@ -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