Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 23 additions & 2 deletions litellm/llms/dashscope/cost_calculator.py
Original file line number Diff line number Diff line change
Expand Up @@ -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],
Expand Down Expand Up @@ -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

Expand All @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
- Correctly calculates costs for token counts exceeding the highest defined tier.
"""

import json
import math
import os
import sys
Expand All @@ -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
Expand Down Expand Up @@ -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
Loading