mirror of
https://github.com/BerriAI/litellm.git
synced 2026-08-28 05:25:59 +00:00
fix(cost): inherit the backend output rate when a deployment's tiers omit one
This commit is contained in:
parent
c3e38a0b52
commit
34918d34f9
2 changed files with 86 additions and 0 deletions
|
|
@ -7635,6 +7635,37 @@ class Router:
|
|||
if backend_value is not None:
|
||||
model_info[field] = backend_value
|
||||
|
||||
@staticmethod
|
||||
def _inherit_builtin_tiered_output_rate(
|
||||
model_info: dict, backend_model: str, custom_llm_provider: str | None
|
||||
) -> None:
|
||||
"""Fill a missing entry-level output rate on a deployment entry whose tier
|
||||
table omits one, from the backend model's built-in cost map entry.
|
||||
|
||||
A deployment's custom pricing is registered as its own standalone
|
||||
``litellm.model_cost`` entry holding only the supplied fields, and the
|
||||
tiered-cost output fallback reads that same entry, so a tier table that
|
||||
spells out only input-side rates would bill every completion at 0.
|
||||
|
||||
A user-specified ``output_cost_per_token`` always wins. No-op without a
|
||||
tier table, when every tier declares its own output rate, or when the
|
||||
backend model has no canonical entry.
|
||||
"""
|
||||
tiers: Final = model_info.get("tiered_pricing")
|
||||
if not isinstance(tiers, list) or not tiers:
|
||||
return
|
||||
if model_info.get("output_cost_per_token") is not None:
|
||||
return
|
||||
if all(isinstance(tier, dict) and "output_cost_per_token" in tier for tier in tiers):
|
||||
return
|
||||
try:
|
||||
backend_info: Final = litellm.get_model_info(model=backend_model, custom_llm_provider=custom_llm_provider)
|
||||
except Exception: # noqa: BLE001 # get_model_info raises plain Exception for an unmapped backend model
|
||||
return
|
||||
backend_rate: Final = backend_info.get("output_cost_per_token")
|
||||
if backend_rate is not None:
|
||||
model_info["output_cost_per_token"] = backend_rate
|
||||
|
||||
def _create_deployment(
|
||||
self,
|
||||
deployment_info: dict,
|
||||
|
|
@ -7670,6 +7701,11 @@ class Router:
|
|||
backend_model=deployment.litellm_params.model,
|
||||
custom_llm_provider=deployment.litellm_params.custom_llm_provider,
|
||||
)
|
||||
Router._inherit_builtin_tiered_output_rate(
|
||||
model_info=_model_info,
|
||||
backend_model=deployment.litellm_params.model,
|
||||
custom_llm_provider=deployment.litellm_params.custom_llm_provider,
|
||||
)
|
||||
|
||||
## REGISTER MODEL INFO IN LITELLM MODEL COST MAP
|
||||
Router._register_deployment_in_model_cost(
|
||||
|
|
@ -8368,6 +8404,11 @@ class Router:
|
|||
backend_model=deployment.litellm_params.model,
|
||||
custom_llm_provider=deployment.litellm_params.custom_llm_provider,
|
||||
)
|
||||
Router._inherit_builtin_tiered_output_rate(
|
||||
model_info=_model_info_dict,
|
||||
backend_model=deployment.litellm_params.model,
|
||||
custom_llm_provider=deployment.litellm_params.custom_llm_provider,
|
||||
)
|
||||
|
||||
# Register custom pricing in litellm.model_cost.
|
||||
# Mirrors _create_deployment() logic to ensure dynamically-added deployments
|
||||
|
|
@ -8598,6 +8639,11 @@ class Router:
|
|||
backend_model=deployment.litellm_params.model,
|
||||
custom_llm_provider=deployment.litellm_params.custom_llm_provider,
|
||||
)
|
||||
Router._inherit_builtin_tiered_output_rate(
|
||||
model_info=model_info,
|
||||
backend_model=deployment.litellm_params.model,
|
||||
custom_llm_provider=deployment.litellm_params.custom_llm_provider,
|
||||
)
|
||||
return model_info
|
||||
|
||||
@staticmethod
|
||||
|
|
|
|||
|
|
@ -734,6 +734,46 @@ def test_generic_cost_per_token_tier_without_an_output_rate_bills_the_model_rate
|
|||
litellm.model_cost.pop(model, None)
|
||||
|
||||
|
||||
def test_router_deployment_with_input_only_tiers_bills_completions_at_the_backend_rate():
|
||||
"""Regression: the router registers a deployment's custom pricing as a standalone
|
||||
model_cost entry holding only the supplied fields, so an input-only tier table left
|
||||
the output-rate fallback nothing to read and billed every completion at 0."""
|
||||
from litellm import Router
|
||||
|
||||
model_id = "litellm-test-router-tiered-input-only"
|
||||
backend_model = "anthropic/claude-haiku-4-5"
|
||||
backend_output_rate = litellm.get_model_info(backend_model)["output_cost_per_token"]
|
||||
Router(
|
||||
model_list=[
|
||||
{
|
||||
"model_name": "tiered-input-only",
|
||||
"litellm_params": {
|
||||
"model": backend_model,
|
||||
"api_key": "sk-test",
|
||||
"tiered_pricing": [
|
||||
{"range": [0, 3000], "input_cost_per_token": 3.25e-07},
|
||||
{"range": [3000, 128000], "input_cost_per_token": 8.125e-07},
|
||||
],
|
||||
},
|
||||
"model_info": {"id": model_id},
|
||||
}
|
||||
]
|
||||
)
|
||||
|
||||
try:
|
||||
usage = Usage(prompt_tokens=21, completion_tokens=4, total_tokens=25)
|
||||
prompt_cost, completion_cost = generic_cost_per_token(
|
||||
model=model_id,
|
||||
usage=usage,
|
||||
custom_llm_provider="anthropic",
|
||||
)
|
||||
assert round(prompt_cost, 12) == round(21 * 3.25e-07, 12)
|
||||
assert round(completion_cost, 12) == round(4 * backend_output_rate, 12)
|
||||
assert backend_output_rate > 0
|
||||
finally:
|
||||
litellm.model_cost.pop(model_id, None)
|
||||
|
||||
|
||||
def test_generic_cost_per_token_tiered_pricing_bills_reasoning_at_tier_rate():
|
||||
"""Regression: a tier's output_cost_per_reasoning_token must price reasoning tokens
|
||||
on the generic path and in the logged breakdown, not the tier's plain output rate."""
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue