mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-13 23:11:40 +00:00
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:
parent
29b189e26b
commit
cb0ba77cd1
4 changed files with 148 additions and 26 deletions
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue