mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-28 01:32:17 +00:00
fix(cost): treat a zero rate as a price when selecting a cost metric
`select_cost_metric_for_model` picked its metric by truthiness:
if model_info.get("input_cost_per_character"):
return "cost_per_character"
elif model_info.get("input_cost_per_token"):
return "cost_per_token"
else:
raise ValueError(f"Model {...} does not have ...")
A rate of 0 is a valid price -- it declares the model free -- but it is falsy,
so a free model fell through to the raise and reported that fields it had
explicitly set were missing:
{"key": "my-free-tts", "input_cost_per_character": 0}
-> ValueError: Model my-free-tts does not have 'input_cost_per_character'
or 'input_cost_per_token'
The single caller is the speech/aspeech branch of `completion_cost`
(cost_calculator.py), so a TTS model priced at 0 could not be costed at all,
and the error was indistinguishable from a genuinely unpriced model.
Selection now tests presence with `is not None`. This matches `tier_rate` in
this same package, which already documents the convention: "A rate that is
explicitly present wins over the fallback, an explicit zero included, so a tier
can declare a token type free."
Zero-cost models are a supported concept elsewhere in the codebase (the
router's `_zero_cost_cache`, the auth layer's zero-cost budget bypass), so a
free TTS model is a configuration users can reasonably expect to work.
Behaviour is unchanged for priced models, for a model with both fields set
(character pricing still wins), and for a model with neither -- which still
raises, as does one that sets both to an explicit None.
Adds tests/test_litellm/test_zero_cost_metric_selection.py: 9 tests, 4 of which
fail without this change.
This commit is contained in:
parent
5c034fda74
commit
416d15d67a
2 changed files with 71 additions and 2 deletions
|
|
@ -137,10 +137,16 @@ def select_cost_metric_for_model(
|
|||
"""
|
||||
Select 'cost_per_character' if model_info has 'input_cost_per_character'
|
||||
Select 'cost_per_token' if model_info has 'input_cost_per_token'
|
||||
|
||||
Presence is what selects the metric, not truthiness: a rate of ``0`` is a
|
||||
valid price, declaring the model free. Testing truthiness treated such a
|
||||
model as unpriced and raised below, reporting that fields it had explicitly
|
||||
set were missing. This matches ``tier_rate`` in this package, where an
|
||||
explicit zero already wins over the fallback.
|
||||
"""
|
||||
if model_info.get("input_cost_per_character"):
|
||||
if model_info.get("input_cost_per_character") is not None:
|
||||
return "cost_per_character"
|
||||
elif model_info.get("input_cost_per_token"):
|
||||
elif model_info.get("input_cost_per_token") is not None:
|
||||
return "cost_per_token"
|
||||
else:
|
||||
raise ValueError(
|
||||
|
|
|
|||
63
tests/test_litellm/test_zero_cost_metric_selection.py
Normal file
63
tests/test_litellm/test_zero_cost_metric_selection.py
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
"""Tests for cost-metric selection on models priced at zero.
|
||||
|
||||
`select_cost_metric_for_model` chose a metric by truthiness, so a rate of 0 --
|
||||
a valid price declaring the model free -- was indistinguishable from an absent
|
||||
one, and the speech cost path raised ValueError claiming fields the model had
|
||||
explicitly set were missing.
|
||||
"""
|
||||
|
||||
import pytest
|
||||
|
||||
from litellm.litellm_core_utils.llm_cost_calc.utils import (
|
||||
select_cost_metric_for_model,
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"model_info, expected",
|
||||
[
|
||||
({"key": "tts-1", "input_cost_per_token": 1.5e-05}, "cost_per_token"),
|
||||
({"key": "tts-1", "input_cost_per_character": 1.5e-05}, "cost_per_character"),
|
||||
# character pricing wins when both are present
|
||||
(
|
||||
{
|
||||
"key": "tts-1",
|
||||
"input_cost_per_character": 1.5e-05,
|
||||
"input_cost_per_token": 3.0e-05,
|
||||
},
|
||||
"cost_per_character",
|
||||
),
|
||||
],
|
||||
)
|
||||
def test_priced_models_select_their_metric(model_info, expected):
|
||||
assert select_cost_metric_for_model(model_info) == expected
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"model_info, expected",
|
||||
[
|
||||
({"key": "free-tts", "input_cost_per_character": 0}, "cost_per_character"),
|
||||
({"key": "free-tts", "input_cost_per_token": 0}, "cost_per_token"),
|
||||
({"key": "free-tts", "input_cost_per_character": 0.0}, "cost_per_character"),
|
||||
(
|
||||
{"key": "free-tts", "input_cost_per_character": 0, "input_cost_per_token": 0},
|
||||
"cost_per_character",
|
||||
),
|
||||
],
|
||||
)
|
||||
def test_zero_is_a_price_not_an_absent_field(model_info, expected):
|
||||
# A zero rate declares the model free. Selecting on truthiness dropped
|
||||
# through to the ValueError below, so a free TTS model could not be costed.
|
||||
assert select_cost_metric_for_model(model_info) == expected
|
||||
|
||||
|
||||
def test_missing_cost_fields_still_raise():
|
||||
with pytest.raises(ValueError, match="does not have"):
|
||||
select_cost_metric_for_model({"key": "unpriced-tts"})
|
||||
|
||||
|
||||
def test_explicit_none_is_treated_as_absent():
|
||||
with pytest.raises(ValueError, match="does not have"):
|
||||
select_cost_metric_for_model(
|
||||
{"key": "unpriced-tts", "input_cost_per_character": None, "input_cost_per_token": None}
|
||||
)
|
||||
Loading…
Add table
Reference in a new issue