fix(router): list cache prices at the input rate billing charges when a deployment has none

This commit is contained in:
mateo-berri 2026-10-29 04:52:04 -07:00
parent cd52bb0960
commit 9f7fc215f6
2 changed files with 90 additions and 3 deletions

View file

@ -959,14 +959,28 @@ _get_cost_per_unit = get_cost_per_unit
_HALF_PRICED_BATCH_KEYS: Final = frozenset(
{f"input_cost_per_token{_BATCH_KEY_SUFFIX}", f"output_cost_per_token{_BATCH_KEY_SUFFIX}"}
)
_TIER_KEY_SUFFIXES: Final = ("", *SERVICE_TIER_COST_KEY_SUFFIXES)
_CACHE_KEYS_BILLED_AT_INPUT_RATE: Final = MappingProxyType(
{
**{f"cache_read_input_token_cost{suffix}": f"input_cost_per_token{suffix}" for suffix in _TIER_KEY_SUFFIXES},
**{
f"cache_creation_input_token_cost{suffix}": f"input_cost_per_token{suffix}" for suffix in _TIER_KEY_SUFFIXES
},
}
)
def billed_rate(model_info: Mapping[str, object], cost_key: str) -> float | None:
rate: Final = get_cost_per_unit(model_info, cost_key, default_value=None)
if rate is not None or cost_key not in _HALF_PRICED_BATCH_KEYS:
if rate is not None:
return rate
standard_rate: Final = get_cost_per_unit(model_info, cost_key.removesuffix(_BATCH_KEY_SUFFIX), default_value=None)
return None if standard_rate is None else standard_rate / 2
if cost_key in _HALF_PRICED_BATCH_KEYS:
standard_rate: Final = get_cost_per_unit(
model_info, cost_key.removesuffix(_BATCH_KEY_SUFFIX), default_value=None
)
return None if standard_rate is None else standard_rate / 2
input_key: Final = _CACHE_KEYS_BILLED_AT_INPUT_RATE.get(cost_key)
return None if input_key is None else get_cost_per_unit(model_info, input_key, default_value=None)
def deployment_pricing(model_info: ModelInfo | None) -> ModelInfo | None:

View file

@ -2802,6 +2802,79 @@ def test_model_group_info_lists_tier_and_batch_prices_at_the_most_any_deployment
assert tuple(listed[key] * tokens for key in listed_keys) == pytest.approx(highest_bills)
def test_model_group_info_lists_cache_prices_at_the_most_any_deployment_bills(monkeypatch: pytest.MonkeyPatch):
from litellm.utils import _invalidate_model_cost_lowercase_map
backends: Final = ("made-up-cached-backend", "made-up-uncached-backend")
monkeypatch.setattr(
litellm,
"model_cost",
{
**litellm.model_cost,
backends[0]: {
"litellm_provider": "openai",
"mode": "chat",
"input_cost_per_token": 1e-06,
"output_cost_per_token": 4e-06,
"input_cost_per_token_priority": 2e-06,
"cache_read_input_token_cost": 1e-07,
"cache_read_input_token_cost_priority": 2e-07,
"cache_creation_input_token_cost": 1.25e-06,
},
backends[1]: {
"litellm_provider": "openai",
"mode": "chat",
"input_cost_per_token": 3e-06,
"output_cost_per_token": 1.2e-05,
"input_cost_per_token_priority": 6e-06,
},
},
)
_invalidate_model_cost_lowercase_map()
router: Final = litellm.Router(
model_list=[
{
"model_name": "mixed-cache-group",
"litellm_params": {"model": f"openai/{backend}", "api_key": "fake"},
"model_info": {"id": backend},
}
for backend in backends
]
)
tokens: Final = 1_000_000
def highest_prompt_bill(cache_read_tokens: int, cache_creation_tokens: int, service_tier: str | None) -> float:
return max(
litellm.cost_per_token(
model=backend,
custom_llm_provider="openai",
prompt_tokens=tokens,
completion_tokens=0,
cache_read_input_tokens=cache_read_tokens,
cache_creation_input_tokens=cache_creation_tokens,
service_tier=service_tier,
)[0]
for backend in backends
)
highest_bills: Final = (
highest_prompt_bill(tokens, 0, None),
highest_prompt_bill(tokens, 0, "priority"),
highest_prompt_bill(0, tokens, None),
)
info: Final = router.get_model_group_info(model_group="mixed-cache-group")
assert info is not None
listed: Final = info.model_dump()
listed_keys: Final = (
"cache_read_input_token_cost",
"cache_read_input_token_cost_priority",
"cache_creation_input_token_cost",
)
assert tuple(listed[key] * tokens for key in listed_keys) == pytest.approx(highest_bills)
@pytest.mark.parametrize(("team_id", "listed_cache_read_cost"), [(None, 1e-07), ("team-b", 1e-07), ("team-a", 9e-06)])
def test_model_group_info_lists_a_team_deployment_price_only_to_that_team(
monkeypatch: pytest.MonkeyPatch, team_id: str | None, listed_cache_read_cost: float