mirror of
https://github.com/BerriAI/litellm.git
synced 2026-08-28 05:25:59 +00:00
Merge b7e322e1e9 into 3746ba58d7
This commit is contained in:
commit
bf63b5ca29
4 changed files with 104 additions and 22 deletions
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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"),
|
||||
)
|
||||
)
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue