fix(dashscope): treat an explicit 0.0 tier cost as a real price, not missing (#30749)

_calculate_tiered_cost resolved a tier's per-token rate with
`tier.get(cost_key) or tier.get(fallback_cost_key, 0)`. The `or`
short-circuits on a falsy 0.0, so a tier that legitimately prices cached
reads (or reasoning tokens) at 0.0 was silently billed at the full
fallback rate, at both the in-range and overflow sites.

Add _resolve_tier_cost_per_token, which only falls back when the primary
key is absent (is None), mirroring the flat-pricing path that already
guards correctly. Uses the X | None annotation style to stay within the
ruff strict-rule budget.

Re-submit of #30653, which was reverted from litellm_internal_staging
because the previous Optional[str] annotation pushed the UP045 count over
the ruff-strict-budget.json ceiling.
This commit is contained in:
Yash Raj Pandey 2026-06-23 09:30:13 -04:00 • committed by Sameer Kankute
parent 341e4f2487
commit c5b7fc5d22
No known key found for this signature in database
2 changed files with 74 additions and 3 deletions

View file

@ -42,6 +42,23 @@ def _extract_token_breakdown(usage: Usage) -> TokenBreakdown:
return TokenBreakdown(text_tokens, cached_tokens, completion_tokens, reasoning_tokens)
def _resolve_tier_cost_per_token(
tier: dict, cost_key: str, fallback_cost_key: str | None
) -> 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],
@ -101,7 +118,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
@ -110,7 +129,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

View file

@ -8,7 +8,6 @@ Tests the cost calculation for Dashscope models including:
- Correctly calculates costs for token counts exceeding the highest defined tier.
"""
import json
import math
import os
import sys
@ -20,6 +19,7 @@ sys.path.insert(0, os.path.abspath("../../../.."))
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 @@ class TestDashscopeCostCalculator:
)
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