mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-10 03:28:53 +00:00
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:
parent
ba28c59b16
commit
8985913f87
2 changed files with 28 additions and 4 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue