fix(cost): treat a non-mapping off_peak_pricing value as never off-peak

A bare string or list under off_peak_pricing in YAML passed the truthy
guard and crashed _is_off_peak with AttributeError, breaking cost
calculation for that deployment. Malformed pieces of the block are
documented to not match rather than error, so guard the block itself the
same way and bill standard rates.
This commit is contained in:
mateo-berri 2026-09-01 12:25:39 -07:00
parent 8d0e7aee2f
commit 4875872fe5
2 changed files with 28 additions and 1 deletions

View file

@ -443,7 +443,7 @@ def _apply_off_peak_pricing(
off_peak_pricing falls back to the standard rate.
"""
off_peak: Final = model_info.get("off_peak_pricing")
if not off_peak or not _is_off_peak(off_peak, current_time):
if not isinstance(off_peak, Mapping) or not _is_off_peak(off_peak, current_time):
return prompt_base_cost, completion_base_cost, cache_read_cost
return (
_coerce_off_peak_rate(off_peak.get("input_cost_per_token"), prompt_base_cost),

View file

@ -648,6 +648,33 @@ def test_get_token_base_cost_applies_off_peak_pricing():
assert peak[4] == 1e-7
def test_get_token_base_cost_non_mapping_off_peak_block_bills_standard_rates():
"""A truthy non-mapping off_peak_pricing value (a bare string or a list in
YAML) must bill standard rates rather than raising, matching how every
other malformed piece of the block behaves.
"""
from datetime import datetime, timezone
from typing import cast
from litellm.types.utils import ModelInfo
usage = Usage(prompt_tokens=100, completion_tokens=50, total_tokens=150)
when = datetime(2026, 1, 1, 18, 0, tzinfo=timezone.utc)
for malformed_block in ("16:00-19:00", ["16:00-19:00"], 5e-7, True):
model_info = cast(
ModelInfo,
{
"input_cost_per_token": 1e-6,
"output_cost_per_token": 2e-6,
"off_peak_pricing": malformed_block,
},
)
result = _get_token_base_cost(model_info, usage, current_time=when)
assert result[0] == 1e-6
assert result[1] == 2e-6
def test_get_token_base_cost_off_peak_falls_back_to_standard_when_unset():
from datetime import datetime, timezone
from typing import cast