This commit is contained in:
Ambuj Upadhyay 2026-08-27 16:37:56 -04:00 committed by GitHub
commit bf63b5ca29
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 104 additions and 22 deletions

View file

@ -3057,12 +3057,13 @@ def register_model(
existing_model.pop(_cost_field, None)
## override / add new keys to the existing model cost dictionary
updated_dictionary = _update_dictionary(existing_model, value)
litellm.model_cost.setdefault(model_cost_key, {}).update(updated_dictionary)
if updated_dictionary:
litellm.model_cost.setdefault(model_cost_key, {}).update(updated_dictionary)
# Invalidate case-insensitive lookup map since model_cost was modified
_invalidate_model_cost_lowercase_map()
# Invalidate case-insensitive lookup map since model_cost was modified
_invalidate_model_cost_lowercase_map()
verbose_logger.debug("added/updated model=%s in litellm.model_cost: %s", model_cost_key, model_cost_key)
verbose_logger.debug("added/updated model=%s in litellm.model_cost: %s", model_cost_key, model_cost_key)
# add new model names to provider lists
if value.get("litellm_provider") == "openai":
if key not in litellm.open_ai_chat_completion_models:

View file

@ -1,3 +1,4 @@
from typing import Final
import json
from pathlib import Path
@ -1072,10 +1073,11 @@ def test_tiered_pricing_only_deployment_selects_router_model_id():
entry = litellm.model_cost[router_model_id]
assert entry.get("input_cost_per_token") is None
assert entry.get("tiered_pricing") is not None
# The stripped shared alias must not carry tiered pricing.
assert (
litellm.model_cost["dashscope/qwen-tier-only-test"].get("tiered_pricing") is None
)
# The stripped shared alias must not carry tiered pricing. After
# stripping, this deployment contributes nothing to the shared key, so
# ``register_model`` may not have materialized it at all.
shared_alias_entry: Final = litellm.model_cost.get("dashscope/qwen-tier-only-test")
assert shared_alias_entry is None or shared_alias_entry.get("tiered_pricing") is None
selected = _select_model_name_for_cost_calc(
model="dashscope/qwen-tier-only-test",

View file

@ -11,6 +11,7 @@ calculations for DB-sourced models with prompt caching pricing.
import copy
import os
from typing import Final
import pytest
@ -610,25 +611,28 @@ def test_register_model_router_add_deployment_custom_pricing_applies():
)
try:
# ``add_deployment`` runs as part of ``Router.__init__``; the
# registered entry must not block ``_check_provider_match`` for
# the deployment's provider.
# ``add_deployment`` runs as part of ``Router.__init__``; the custom
# pricing is registered under the deployment's ``model_info.id`` (the
# shared backend key deliberately excludes per-deployment pricing).
# That entry must not block ``_check_provider_match`` for the
# deployment's provider.
from litellm.utils import _check_provider_match
registered_keys = [
k for k in (deployment_model, model_key) if k in litellm.model_cost
]
assert registered_keys, (
"Router.add_deployment did not register custom pricing for "
registered: Final = litellm.model_cost.get("deployment-28336")
assert registered is not None, (
"Router.add_deployment did not register custom pricing under "
"the deployment id for "
f"{model_key} / {deployment_model}"
)
for k in registered_keys:
assert (
_check_provider_match(litellm.model_cost[k], "openai") is True
), f"custom pricing for {k} was dropped by _check_provider_match"
assert registered["input_cost_per_token"] == 0.00042
assert registered["output_cost_per_token"] == 0.00084
assert (
_check_provider_match(registered, "openai") is True
), "custom pricing for deployment-28336 was dropped by _check_provider_match"
finally:
litellm.model_cost.pop(model_key, None)
litellm.model_cost.pop(deployment_model, None)
litellm.model_cost.pop("deployment-28336", None)
del router
@ -793,3 +797,72 @@ def test_embedding_direct_sdk_custom_pricing_still_registers_shared_key():
finally:
litellm.model_cost.pop(model_key, None)
_invalidate_model_cost_lowercase_map()
def test_register_model_empty_payload_does_not_materialize_entry():
"""An empty payload for a model with no built-in entry carries no
information, and ``register_model`` must not create a bare key for it in
``litellm.model_cost``. Key existence is what cost lookup treats as
"mapped", so an empty entry flips ``completion_cost`` for an unmapped
model from a loud "model isn't mapped" error to a silent $0.0.
"""
from litellm.utils import _runtime_registered_model_cost
model_key: Final = "deepinfra/fake-unmapped-model-empty-registration"
assert model_key not in litellm.model_cost
with pytest.raises(Exception, match="isn't mapped"):
litellm.completion_cost(model=model_key, prompt="hi", completion="there")
try:
litellm.register_model({model_key: {}}) # mutable-ok: registration payload under test
assert model_key not in litellm.model_cost, (
"register_model materialized an empty model_cost entry for an "
"unmapped model registered with an empty payload"
)
with pytest.raises(Exception, match="isn't mapped"):
litellm.completion_cost(model=model_key, prompt="hi", completion="there")
finally:
litellm.model_cost.pop(model_key, None)
_runtime_registered_model_cost.pop(model_key, None)
_invalidate_model_cost_lowercase_map()
def test_router_init_without_pricing_keeps_unmapped_model_cost_loud():
"""``Router.__init__`` registers every deployment's backend key through
``register_model`` (``_register_deployment_in_model_cost``, shared-backend
path). For a deployment whose model is not in the cost map and that
carries no pricing or ``model_info``, that registration used to
materialize an empty entry after which ``litellm.completion_cost`` for
the unmapped model silently returned 0.0 process-wide instead of raising,
even for calls that never touch the router.
"""
from litellm import Router
backend_model: Final = "deepinfra/fake-unmapped-router-model-empty-reg"
stripped_key: Final = backend_model.split("/", 1)[1]
assert backend_model not in litellm.model_cost
router: Final = Router(
model_list=[ # mutable-ok: Router constructor payload
{ # mutable-ok: deployment entry under test
"model_name": "my-unpriced-model",
"litellm_params": {"model": backend_model, "api_key": "fake-key"}, # mutable-ok: deployment params
}
]
)
try:
assert backend_model not in litellm.model_cost, (
"Router.__init__ materialized an empty model_cost entry for an "
"unmapped deployment with no pricing"
)
with pytest.raises(Exception, match="isn't mapped"):
litellm.completion_cost(
model=backend_model, prompt="hi", completion="there"
)
finally:
del router
litellm.model_cost.pop(backend_model, None)
litellm.model_cost.pop(stripped_key, None)
_invalidate_model_cost_lowercase_map()

View file

@ -1131,23 +1131,29 @@ def test_repointing_a_deployment_drops_its_previous_backend_key(monkeypatch):
dict(litellm_utils._runtime_registered_model_cost),
)
# ``mode`` is a cost-map schema field, so it flows through to the shared
# backend key. Without at least one such field the shared payload is empty
# and ``register_model`` (correctly) registers nothing for the backend key,
# which would leave this test nothing to observe.
router = Router(
model_list=[
{
"model_name": "moving-target",
"litellm_params": {"model": "hosted_vllm/old-backend"},
"model_info": {"id": "moving-target-id"},
"model_info": {"id": "moving-target-id", "mode": "chat"},
}
],
)
saved_model_cost = litellm.model_cost
try:
assert "hosted_vllm/old-backend" in litellm.model_cost
router.upsert_deployment(
deployment=Deployment(
model_name="moving-target",
litellm_params=LiteLLM_Params(model="hosted_vllm/new-backend"),
model_info=ModelInfo(id="moving-target-id"),
model_info=ModelInfo(id="moving-target-id", mode="chat"),
)
)