fix(proxy): apply zero-cost budget exemption to model_group_alias

_is_model_cost_zero() called _is_cost_explicitly_configured(), which scanned
Router.model_list for an exact model_name match. Names defined in
Router.model_group_alias are not model_name entries, so the scan never matched
an alias and the function returned False for every aliased model group.

That False reads as "the zero cost was defaulted, not configured", so a model
whose input_cost_per_token and output_cost_per_token are explicitly 0 had budget
enforced against it when requested through an alias, while the same model
requested by its own name was exempt. Router.get_model_group_info(), called a
few lines earlier in the same function for the cost itself, does resolve the
alias, so the two lookups disagreed about what the name means.

The file already had an alias-aware version of the check,
_group_declares_explicit_cost(), added for model_has_no_cost_mapping(); it
resolves the group through Router.get_model_list(), which includes
model_group_alias. Call that and drop the duplicate.

_has_ptu_flat_cost() scanned model_list the same way, and it runs after the
check above, so resolving one without the other would let an aliased PTU group
- which carries an explicit zero per-token price alongside a flat capacity cost
- through as free. Resolve it through Router.get_model_list() too.
This commit is contained in:
fedaeho 2026-09-01 02:17:41 +09:00
parent 4ba8517134
commit 9f6a48cec3
5 changed files with 118 additions and 30 deletions

View file

@ -433,7 +433,7 @@ def _is_model_cost_zero(model: str | list[str] | None, llm_router: Router | None
# not from defaulted sparse auto-registration entries.
# See: https://github.com/BerriAI/litellm/issues/24770
safe_name = str(model_name).replace("\n", "").replace("\r", "")
if not _is_cost_explicitly_configured(model_name, llm_router):
if not _group_declares_explicit_cost(model=model_name, llm_router=llm_router):
verbose_proxy_logger.debug(
"Model %s has zero cost but no explicit cost "
"configuration in model_cost entry — treating as unknown "
@ -480,38 +480,18 @@ def _has_ptu_flat_cost(model: str, llm_router: "Router") -> bool:
Such a deployment carries an explicit zero per-token price so the flat cost is not charged
twice, which otherwise reads here as a free model and waives every budget check for it.
Resolved through ``Router.get_model_list()`` so a model_group_alias pointing at a PTU group
is covered; scanning ``model_list`` by exact name never matches an alias, and the caller
treats a False here as "no flat cost to worry about".
"""
for deployment in llm_router.model_list:
if deployment.get("model_name") != model:
continue
for deployment in llm_router.get_model_list(model_name=model) or ():
model_info = deployment.get("model_info") or _NO_MODEL_INFO
if model_info.get("ptu_count") is not None and model_info.get("cost_per_ptu_per_hour") is not None:
return True
return False
def _is_cost_explicitly_configured(model: str, llm_router: "Router") -> bool:
"""
Check if any deployment in the model group has cost fields explicitly
set in its litellm.model_cost entry.
When Router._create_deployment() registers a model not in the global
cost map, it creates a sparse entry like {"id": "<hash>"} with no cost
fields. _get_model_info_helper() then defaults missing costs to 0.
This function detects that scenario by checking the raw model_cost entry.
"""
for deployment in llm_router.model_list:
if deployment.get("model_name") != model:
continue
model_id = deployment.get("model_info", {}).get("id")
if model_id is None:
continue
raw_entry = litellm.model_cost.get(model_id, {})
if "input_cost_per_token" in raw_entry or "output_cost_per_token" in raw_entry:
return True
return False
_EMPTY_COST_ENTRY: Final[Mapping[str, object]] = MappingProxyType({})
@ -563,7 +543,7 @@ def _model_group_has_pricing(model: str, llm_router: "Router") -> bool:
def _group_declares_explicit_cost(model: str, llm_router: "Router") -> bool:
"""
Alias-aware counterpart to ``_is_cost_explicitly_configured``, which resolves the model group
Whether any deployment in the model group prices itself explicitly. Resolves the model group
the same way ``_model_group_has_pricing`` does. A deployment that prices itself through its
``model_info`` block lands in the cost map under its deployment id rather than in its
litellm_params, and reaching that entry through the router's own resolution keeps an alias

View file

@ -9277,7 +9277,7 @@ class Router:
A strategy-router alias is never the deployment actually called or
billed, so custom pricing configured on it must not become a cost-map
price: an explicit zero would let ``_is_cost_explicitly_configured``
price: an explicit zero would let ``_group_declares_explicit_cost``
treat the alias as a genuinely free model and waive budget checks for
requests that route to (and bill as) a real deployment.
"""

View file

@ -3090,7 +3090,7 @@ def register_model(
# = 0 when they are absent from the raw entry. Writing those zeros
# back flips a sparse entry from "no cost keys" (priced via name)
# to "cost keys = 0" (free), which makes
# ``_is_cost_explicitly_configured`` return True and silently
# ``_group_declares_explicit_cost`` return True and silently
# disables budget enforcement on the next re-registration.
_raw_entry = litellm.model_cost.get(model_cost_key)
if _raw_entry is None:

View file

@ -211,3 +211,111 @@ class TestUnmappedModelBudgetEnforcement:
result = _is_model_cost_zero(model="paid-model", llm_router=mock_router)
assert result is False
def test_model_group_alias_to_free_model_bypasses_budget(self):
"""An explicitly free model reached through model_group_alias should
bypass budget, same as when it is called by its own name."""
router = Router(
model_list=[
{
"model_name": "free-model",
"litellm_params": {
"model": "openai/nonexistent-but-free-model",
"api_key": "sk-fake",
"input_cost_per_token": 0.0,
"output_cost_per_token": 0.0,
},
"model_info": {"id": "free-model-id"},
},
],
model_group_alias={"free-model-alias": "free-model"},
)
assert _is_model_cost_zero(model="free-model", llm_router=router) is True
result = _is_model_cost_zero(model="free-model-alias", llm_router=router)
assert result is True, "Alias of an explicitly free model should bypass budget (return True)"
def test_model_group_alias_to_paid_model_enforces_budget(self):
"""An alias must not waive budget checks for a paid model group."""
router = Router(
model_list=[
{
"model_name": "paid-model",
"litellm_params": {
"model": "openai/gpt-4o-mini",
"api_key": "sk-fake",
},
},
],
model_group_alias={"paid-model-alias": "paid-model"},
)
result = _is_model_cost_zero(model="paid-model-alias", llm_router=router)
assert result is False, "Alias of a paid model should enforce budget"
def test_hidden_model_group_alias_enforces_budget(self):
"""A hidden alias has no resolvable model group, so budget stays enforced."""
router = Router(
model_list=[
{
"model_name": "free-model",
"litellm_params": {
"model": "openai/nonexistent-but-free-model",
"api_key": "sk-fake",
"input_cost_per_token": 0.0,
"output_cost_per_token": 0.0,
},
"model_info": {"id": "free-model-id"},
},
],
model_group_alias={"hidden-alias": {"model": "free-model", "hidden": True}},
)
result = _is_model_cost_zero(model="hidden-alias", llm_router=router)
assert result is False, "Hidden alias should enforce budget"
def test_dangling_model_group_alias_enforces_budget(self):
"""An alias pointing at a model group that does not exist must not bypass budget."""
router = Router(
model_list=[
{
"model_name": "free-model",
"litellm_params": {
"model": "openai/nonexistent-but-free-model",
"api_key": "sk-fake",
"input_cost_per_token": 0.0,
"output_cost_per_token": 0.0,
},
"model_info": {"id": "free-model-id"},
},
],
model_group_alias={"dangling-alias": "no-such-model-group"},
)
result = _is_model_cost_zero(model="dangling-alias", llm_router=router)
assert result is False, "Alias to a missing model group should enforce budget"
def test_model_group_alias_to_ptu_flat_cost_enforces_budget(self):
"""A PTU deployment bills reserved capacity as a flat cost and carries an explicit
zero per-token price. Reaching it through an alias must still enforce budget."""
router = Router(
model_list=[
{
"model_name": "ptu-model",
"litellm_params": {
"model": "azure/gpt-4o",
"api_key": "sk-fake",
"api_base": "https://example.openai.azure.com",
"input_cost_per_token": 0.0,
"output_cost_per_token": 0.0,
},
"model_info": {
"id": "ptu-model-id",
"input_cost_per_token": 0.0,
"output_cost_per_token": 0.0,
"ptu_count": 100,
"cost_per_ptu_per_hour": 2.0,
},
},
],
model_group_alias={"ptu-model-alias": "ptu-model"},
)
assert _is_model_cost_zero(model="ptu-model", llm_router=router) is False
result = _is_model_cost_zero(model="ptu-model-alias", llm_router=router)
assert result is False, "Alias of a PTU flat-cost model should enforce budget"

View file

@ -13,7 +13,7 @@ already-present sparse entry (e.g. router model id with only
``{"id": ..., "db_model": True}``), the synthesized zeros get written
back, and the entry flips from "no cost keys" "cost keys = 0".
That defeats ``_is_cost_explicitly_configured`` (added in #24949), which
That defeats ``_group_declares_explicit_cost`` (added in #24949), which
checks whether the cost keys are present in the raw entry after the
write-back they are. ``_is_model_cost_zero`` then returns ``True`` and
``common_checks`` skips every tag / key / team / user / org budget check