diff --git a/litellm/llms/dashscope/cost_calculator.py b/litellm/llms/dashscope/cost_calculator.py index 8bb7f605b82..2bfaad21259 100644 --- a/litellm/llms/dashscope/cost_calculator.py +++ b/litellm/llms/dashscope/cost_calculator.py @@ -46,6 +46,23 @@ def _extract_token_breakdown(usage: Usage) -> TokenBreakdown: ) +def _resolve_tier_cost_per_token( + tier: dict, cost_key: str, fallback_cost_key: Optional[str] +) -> float: + """Resolve a tier's per-token cost. + + An explicit 0.0 is a real price (e.g. a free-cache-read tier) and must not + be treated as "missing". Only fall back to ``fallback_cost_key`` when the + primary key is absent. This mirrors the flat-pricing path in + ``_calculate_prompt_cost`` / ``_calculate_completion_cost``, which already + guards with ``is None``. + """ + cost = tier.get(cost_key) + if cost is None and fallback_cost_key is not None: + cost = tier.get(fallback_cost_key) + return float(cost) if cost is not None else 0.0 + + def _calculate_tiered_cost( tokens: int, tiered_pricing: List[dict], @@ -105,7 +122,9 @@ def _calculate_tiered_cost( if tier_end > tier_start: tokens_in_tier = tier_end - tier_start - cost_per_token = tier.get(cost_key) or tier.get(fallback_cost_key, 0) + cost_per_token = _resolve_tier_cost_per_token( + tier, cost_key, fallback_cost_key + ) total_cost += tokens_in_tier * cost_per_token tokens_processed = tier_end @@ -114,7 +133,9 @@ def _calculate_tiered_cost( if tokens_processed < tokens and sorted_tiers: last_tier = sorted_tiers[-1] remaining_tokens = tokens - tokens_processed - cost_per_token = last_tier.get(cost_key) or last_tier.get(fallback_cost_key, 0) + cost_per_token = _resolve_tier_cost_per_token( + last_tier, cost_key, fallback_cost_key + ) total_cost += remaining_tokens * cost_per_token return total_cost diff --git a/tests/test_litellm/llms/dashscope/test_dashscope_cost_calculator.py b/tests/test_litellm/llms/dashscope/test_dashscope_cost_calculator.py index 05279202e8e..0554a68f107 100644 --- a/tests/test_litellm/llms/dashscope/test_dashscope_cost_calculator.py +++ b/tests/test_litellm/llms/dashscope/test_dashscope_cost_calculator.py @@ -8,7 +8,6 @@ - Correctly calculates costs for token counts exceeding the highest defined tier. """ -import json import math import os import sys @@ -20,6 +19,7 @@ import litellm from litellm.llms.dashscope.cost_calculator import ( + _calculate_tiered_cost, cost_per_token as dashscope_cost_per_token, ) from litellm.types.utils import Usage, PromptTokensDetailsWrapper @@ -157,3 +157,53 @@ def test_dashscope_tiered_pricing_exceeding_highest_tier(self): ) assert math.isclose(prompt_cost, expected_prompt_cost, rel_tol=1e-10) + + def test_dashscope_tiered_zero_cost_not_overridden_by_fallback(self): + """ + A tier may legitimately price cached reads (or reasoning tokens) at 0.0, + e.g. a free-cache-read tier. The tiered calculator must treat an explicit + 0.0 as a real price, not as "missing" and fall back to the full input + rate. This mirrors the flat-pricing path, which already guards on `None`. + """ + tiered_pricing = [ + { + "range": [0, 256000], + "input_cost_per_token": 2e-7, + "cache_read_input_token_cost": 0.0, # free cache reads in this tier + } + ] + + cost = _calculate_tiered_cost( + tokens=10000, + tiered_pricing=tiered_pricing, + cost_key="cache_read_input_token_cost", + fallback_cost_key="input_cost_per_token", + ) + + # 10,000 cached tokens priced at $0.0/token must cost $0.0, not the + # input rate (which would be 10000 * 2e-7 = $0.002). + assert cost == 0.0 + + def test_dashscope_tiered_zero_cost_applies_to_overflow_tokens(self): + """ + The same 0.0-is-real-price rule must hold on the overflow path that + charges tokens exceeding the highest defined tier at the last tier's + rate. + """ + tiered_pricing = [ + { + "range": [0, 100], + "input_cost_per_token": 2e-7, + "cache_read_input_token_cost": 0.0, + } + ] + + # 150 cached tokens: 100 in-tier + 50 overflow, both at $0.0. + cost = _calculate_tiered_cost( + tokens=150, + tiered_pricing=tiered_pricing, + cost_key="cache_read_input_token_cost", + fallback_cost_key="input_cost_per_token", + ) + + assert cost == 0.0