fix(cost_calculator): apply discount and margin to image and video costs

This commit is contained in:
Devin AI 2026-07-27 15:42:00 +00:00
parent 24123269cc
commit c235ab295c
2 changed files with 196 additions and 40 deletions

View file

@ -1105,6 +1105,57 @@ def _store_cost_breakdown_in_logging_obj(
pass
def _apply_cost_adjustments(
base_cost: float,
custom_llm_provider: str | None,
litellm_logging_obj: LitellmLoggingObject | None,
input_cost: float,
output_cost: float,
) -> float:
"""
Apply configured discount + margin to a provider cost and record the breakdown.
Args:
base_cost: Provider cost before discount/margin
custom_llm_provider: The LLM provider name
litellm_logging_obj: Logging object the cost breakdown is stored on
input_cost: Portion of base_cost attributable to input
output_cost: Portion of base_cost attributable to output
Returns:
The cost after discount and margin
"""
discounted_cost, discount_percent, discount_amount = _apply_cost_discount(
base_cost=base_cost,
custom_llm_provider=custom_llm_provider,
)
(
final_cost,
margin_percent,
margin_fixed_amount,
margin_total_amount,
) = _apply_cost_margin(
base_cost=discounted_cost,
custom_llm_provider=custom_llm_provider,
)
_store_cost_breakdown_in_logging_obj(
litellm_logging_obj=litellm_logging_obj,
prompt_tokens_cost_usd_dollar=input_cost,
completion_tokens_cost_usd_dollar=output_cost,
cost_for_built_in_tools_cost_usd_dollar=0.0,
total_cost_usd_dollar=final_cost,
original_cost=base_cost,
discount_percent=discount_percent,
discount_amount=discount_amount,
margin_percent=margin_percent,
margin_fixed_amount=margin_fixed_amount,
margin_total_amount=margin_total_amount,
)
return final_cost
def completion_cost(
completion_response=None,
model: Optional[str] = None,
@ -1335,7 +1386,7 @@ def completion_cost(
completion_response, ImageResponse
):
### IMAGE GENERATION COST CALCULATION ###
return CostCalculatorUtils.route_image_generation_cost_calculator(
_image_cost = CostCalculatorUtils.route_image_generation_cost_calculator(
model=model,
custom_llm_provider=custom_llm_provider,
completion_response=completion_response,
@ -1345,6 +1396,13 @@ def completion_cost(
optional_params=optional_params,
call_type=call_type,
)
return _apply_cost_adjustments(
base_cost=_image_cost,
custom_llm_provider=custom_llm_provider,
litellm_logging_obj=litellm_logging_obj,
input_cost=0.0,
output_cost=_image_cost,
)
elif call_type in _VIDEO_CALL_TYPES:
### VIDEO GENERATION COST CALCULATION ###
# Extract custom model_info for deployment-specific pricing
@ -1375,21 +1433,35 @@ def completion_cost(
video_generation_cost,
)
return video_generation_cost(
_video_cost = video_generation_cost(
model=model,
duration_seconds=duration_seconds,
custom_llm_provider=custom_llm_provider,
model_info=_video_model_info,
video_resolution=video_resolution,
)
return _apply_cost_adjustments(
base_cost=_video_cost,
custom_llm_provider=custom_llm_provider,
litellm_logging_obj=litellm_logging_obj,
input_cost=0.0,
output_cost=_video_cost,
)
# Fallback to default video cost calculation if no duration available
return default_video_cost_calculator(
_video_cost = default_video_cost_calculator(
model=model,
duration_seconds=0.0, # Default to 0 if no duration available
custom_llm_provider=custom_llm_provider,
model_info=_video_model_info,
video_resolution=video_resolution,
)
return _apply_cost_adjustments(
base_cost=_video_cost,
custom_llm_provider=custom_llm_provider,
litellm_logging_obj=litellm_logging_obj,
input_cost=0.0,
output_cost=_video_cost,
)
elif call_type in _SPEECH_CALL_TYPES:
prompt_characters = litellm.utils._count_characters(text=prompt)
elif call_type in _TRANSCRIPTION_CALL_TYPES:
@ -1446,46 +1518,13 @@ def completion_cost(
)
# Return the total cost (prompt_cost + completion_cost, but for search it's just prompt_cost)
_final_cost = prompt_cost + completion_cost_result
# Apply discount
original_cost = _final_cost
(
_final_cost,
discount_percent,
discount_amount,
) = _apply_cost_discount(
base_cost=_final_cost,
return _apply_cost_adjustments(
base_cost=prompt_cost + completion_cost_result,
custom_llm_provider=custom_llm_provider,
)
# Apply margin from module-level config if configured
(
_final_cost,
margin_percent,
margin_fixed_amount,
margin_total_amount,
) = _apply_cost_margin(
base_cost=_final_cost,
custom_llm_provider=custom_llm_provider,
)
# Store cost breakdown in logging object if available
_store_cost_breakdown_in_logging_obj(
litellm_logging_obj=litellm_logging_obj,
prompt_tokens_cost_usd_dollar=prompt_cost,
completion_tokens_cost_usd_dollar=completion_cost_result,
cost_for_built_in_tools_cost_usd_dollar=0.0,
total_cost_usd_dollar=_final_cost,
original_cost=original_cost,
discount_percent=discount_percent,
discount_amount=discount_amount,
margin_percent=margin_percent,
margin_fixed_amount=margin_fixed_amount,
margin_total_amount=margin_total_amount,
input_cost=prompt_cost,
output_cost=completion_cost_result,
)
return _final_cost
elif call_type == _AREALTIME_CALL_TYPE and isinstance(
completion_response, LiteLLMRealtimeStreamLoggingObject
):

View file

@ -3509,3 +3509,120 @@ def test_combine_usage_objects_sums_mirrored_cache_write_fields_once():
assert combined_pair.prompt_tokens_details is not None
assert combined_pair.prompt_tokens_details.cache_write_tokens == 100
assert combined_pair.prompt_tokens_details.cache_creation_tokens == 100
def test_image_generation_cost_applies_discount_and_margin(monkeypatch):
"""
Regression for https://github.com/BerriAI/litellm/issues/34731: image generation
returned the raw provider cost, bypassing cost_discount_config / cost_margin_config.
"""
from litellm.types.utils import ImageObject, ImageResponse
monkeypatch.setenv("LITELLM_LOCAL_MODEL_COST_MAP", "True")
monkeypatch.setattr(litellm, "model_cost", litellm.get_model_cost_map(url=""))
response = ImageResponse(
created=1234567890,
data=[ImageObject(url="https://example.com/image.png")],
quality="standard",
size="1024x1024",
)
cost_kwargs = dict(
completion_response=response,
model="dall-e-3",
custom_llm_provider="openai",
call_type="image_generation",
size="1024-x-1024",
)
monkeypatch.setattr(litellm, "cost_discount_config", {})
monkeypatch.setattr(litellm, "cost_margin_config", {})
base_cost = completion_cost(**cost_kwargs)
assert base_cost > 0
monkeypatch.setattr(litellm, "cost_discount_config", {"openai": 0.05})
monkeypatch.setattr(litellm, "cost_margin_config", {"openai": 0.10})
adjusted_cost = completion_cost(**cost_kwargs)
assert adjusted_cost == pytest.approx(base_cost * 0.95 * 1.10, rel=1e-9)
def test_video_generation_cost_applies_discount_and_margin(monkeypatch):
"""
Regression for https://github.com/BerriAI/litellm/issues/34731: video generation
returned the raw provider cost, bypassing cost_discount_config / cost_margin_config.
"""
from unittest.mock import MagicMock
response = MagicMock()
response.usage = MagicMock()
response.usage.duration_seconds = 10.0
response.usage.video_resolution = None
type(response)._hidden_params = {}
logging_obj = MagicMock()
logging_obj.litellm_params = {"metadata": {"model_info": {"output_cost_per_video_per_second": 0.05}}}
cost_kwargs = dict(
completion_response=response,
model="openai/hunyuanvideo",
call_type="create_video",
custom_llm_provider="openai",
custom_pricing=True,
litellm_logging_obj=logging_obj,
)
monkeypatch.setattr(litellm, "cost_discount_config", {})
monkeypatch.setattr(litellm, "cost_margin_config", {})
assert completion_cost(**cost_kwargs) == pytest.approx(0.5)
monkeypatch.setattr(litellm, "cost_discount_config", {"openai": 0.05})
monkeypatch.setattr(litellm, "cost_margin_config", {"openai": {"percentage": 0.10, "fixed_amount": 0.01}})
adjusted_cost = completion_cost(**cost_kwargs)
discounted = 0.5 * 0.95
assert adjusted_cost == pytest.approx(discounted + discounted * 0.10 + 0.01, rel=1e-9)
def test_video_generation_cost_records_discount_breakdown(monkeypatch):
"""
The discount/margin breakdown must reach the logging object for media endpoints too,
so spend logs can show original vs charged cost.
"""
from unittest.mock import MagicMock
from litellm.litellm_core_utils.litellm_logging import Logging
response = MagicMock()
response.usage = MagicMock()
response.usage.duration_seconds = 10.0
response.usage.video_resolution = None
type(response)._hidden_params = {}
logging_obj = Logging(
model="openai/hunyuanvideo",
messages=[],
stream=False,
call_type="create_video",
start_time=None,
litellm_call_id="test-call-id",
function_id="test-function-id",
)
logging_obj.litellm_params = {"metadata": {"model_info": {"output_cost_per_video_per_second": 0.05}}}
monkeypatch.setattr(litellm, "cost_discount_config", {"openai": 0.05})
monkeypatch.setattr(litellm, "cost_margin_config", {})
cost = completion_cost(
completion_response=response,
model="openai/hunyuanvideo",
call_type="create_video",
custom_llm_provider="openai",
custom_pricing=True,
litellm_logging_obj=logging_obj,
)
assert cost == pytest.approx(0.475)
assert logging_obj.cost_breakdown is not None
assert logging_obj.cost_breakdown["original_cost"] == pytest.approx(0.5)
assert logging_obj.cost_breakdown["discount_amount"] == pytest.approx(0.025)
assert logging_obj.cost_breakdown["total_cost"] == pytest.approx(0.475)