fix: sort tiered token-cost thresholds numerically (#30375)

* fix: sort tiered token-cost thresholds numerically

_get_token_base_cost iterated input_cost_per_token_above_<N>_tokens keys with a
lexicographic sort, so for tiers whose thresholds have different digit lengths
(e.g. 90k vs 128k) a request crossing both was billed at the lower tier that
sorted first. Sort by the parsed numeric threshold instead, so the highest tier
the request actually crosses is applied.

* refactor: reuse _parse_above_token_threshold for inline threshold parse

---------

Co-authored-by: Eric (GabiDevFamily) <271972409+santino18727-debug@users.noreply.github.com>
This commit is contained in:
santino18727-debug 2026-06-17 13:01:00 +02:00 • committed by GitHub
parent ba28c59b16
commit 8985913f87
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 28 additions and 4 deletions

View file

@ -191,6 +191,11 @@ def _get_service_tier_cost_key(base_key: str, service_tier: Optional[str]) -> st
return base_key
def _parse_above_token_threshold(key: str) -> float:
threshold_str = key.split("_above_")[1].split("_tokens")[0]
return float(threshold_str.replace("k", "")) * (1000 if "k" in threshold_str else 1)
def _get_token_base_cost(
model_info: ModelInfo, usage: Usage, service_tier: Optional[str] = None
) -> Tuple[float, float, float, float, float]:
@ -256,15 +261,13 @@ def _get_token_base_cost(
# Only sort the threshold keys (typically 1-2 keys instead of 66+)
threshold: Optional[float] = None
for key in sorted(threshold_keys, reverse=True):
for key in sorted(threshold_keys, key=_parse_above_token_threshold, reverse=True):
value = model_info.get(key)
if value is not None:
try:
# Handle both formats: _above_128k_tokens and _above_128_tokens
threshold_str = key.split("_above_")[1].split("_tokens")[0]
threshold = float(threshold_str.replace("k", "")) * (
1000 if "k" in threshold_str else 1
)
threshold = _parse_above_token_threshold(key)
if usage.prompt_tokens > threshold:
# Prefer a service_tier-specific above-threshold key when available,
# e.g. input_cost_per_token_priority_above_200k_tokens for Gemini

View file

@ -35,6 +35,7 @@ sys.path.insert(
from litellm.litellm_core_utils.llm_cost_calc.utils import (
PromptTokensDetailsResult,
_calculate_input_cost,
_get_token_base_cost,
calculate_cache_writing_cost,
generic_cost_per_token,
)
@ -298,6 +299,26 @@ def test_generic_cost_per_token_above_200k_tokens():
)
def test_get_token_base_cost_picks_highest_crossed_tier():
"""Regression test for #30345.
With graduated tiers at 90k and 128k whose keys have different digit lengths, a request
crossing both must be billed at the highest tier it crosses (128k), not the lower one that
happens to sort first lexicographically.
"""
model_info = {
"input_cost_per_token": 1e-6,
"output_cost_per_token": 2e-6,
"input_cost_per_token_above_90k_tokens": 5e-6,
"input_cost_per_token_above_128k_tokens": 9e-6,
}
usage = Usage(prompt_tokens=150_000, completion_tokens=10, total_tokens=150_010)
prompt_base_cost = _get_token_base_cost(model_info, usage)[0]
assert prompt_base_cost == 9e-6
def test_generic_cost_per_token_gpt54_above_272k_tokens():
"""GPT-5.4/5.4-pro: prompts >272K input tokens priced at 2x input, 1.5x output."""
model = "gpt-5.4"