mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-01 02:02:20 +00:00
fix(cost): disable override rules whose hours_utc never parses and pin the shipped dates
This commit is contained in:
parent
0ea1f99c09
commit
2c246239fc
4 changed files with 98 additions and 14 deletions
|
|
@ -4,7 +4,7 @@
|
|||
import re
|
||||
from collections.abc import Mapping, Sequence
|
||||
from dataclasses import dataclass
|
||||
from datetime import date, datetime, timezone, tzinfo
|
||||
from datetime import date, datetime, time, timezone, tzinfo
|
||||
from types import MappingProxyType
|
||||
from typing import Final, Literal, TypedDict, cast
|
||||
from zoneinfo import ZoneInfo, ZoneInfoNotFoundError
|
||||
|
|
@ -408,14 +408,7 @@ def _is_within_off_peak_window(off_peak_hours_utc: str | Sequence[str], current_
|
|||
"""
|
||||
reference: Final = current_time if current_time is not None else current_billing_time()
|
||||
now: Final = (reference.astimezone(timezone.utc) if reference.tzinfo is not None else reference).time()
|
||||
windows: Final = (off_peak_hours_utc,) if isinstance(off_peak_hours_utc, str) else off_peak_hours_utc
|
||||
for window in windows:
|
||||
try:
|
||||
start_str, end_str = window.split("-")
|
||||
start = datetime.strptime(start_str.strip(), "%H:%M").replace(tzinfo=timezone.utc).time()
|
||||
end = datetime.strptime(end_str.strip(), "%H:%M").replace(tzinfo=timezone.utc).time()
|
||||
except (ValueError, AttributeError):
|
||||
continue
|
||||
for start, end in _parse_windows(off_peak_hours_utc):
|
||||
if start < end:
|
||||
if start <= now < end:
|
||||
return True
|
||||
|
|
@ -447,6 +440,21 @@ _WEEKDAY_NUMBERS: Final = MappingProxyType(
|
|||
)
|
||||
|
||||
|
||||
def _parse_window(window: object) -> tuple[time, time] | None:
|
||||
try:
|
||||
start_str, end_str = window.split("-")
|
||||
start: Final = datetime.strptime(start_str.strip(), "%H:%M").replace(tzinfo=timezone.utc).time()
|
||||
end: Final = datetime.strptime(end_str.strip(), "%H:%M").replace(tzinfo=timezone.utc).time()
|
||||
except (ValueError, AttributeError):
|
||||
return None
|
||||
return (start, end)
|
||||
|
||||
|
||||
def _parse_windows(off_peak_hours_utc: str | Sequence[str]) -> tuple[tuple[time, time], ...]:
|
||||
windows: Final = (off_peak_hours_utc,) if isinstance(off_peak_hours_utc, str) else off_peak_hours_utc
|
||||
return tuple(parsed for window in windows if (parsed := _parse_window(window)) is not None)
|
||||
|
||||
|
||||
def _normalize_weekday(value: object) -> int | None:
|
||||
if isinstance(value, bool):
|
||||
return None
|
||||
|
|
@ -522,8 +530,8 @@ def _is_off_peak(off_peak: Mapping[str, object], current_time: datetime | None =
|
|||
hours apply only on its weekdays. A rule carrying override_dates applies only on those
|
||||
dates, read on the weekday_timezone calendar: on a listed date the matching rules alone
|
||||
decide (their weekdays are ignored, and the flat hours_utc and every other rule are
|
||||
skipped), and on any other date the rule never applies (a malformed override_dates or
|
||||
hours_utc disables the rule entirely).
|
||||
skipped), and on any other date the rule never applies (a malformed override_dates, or an
|
||||
hours_utc none of whose windows parse, disables the rule entirely).
|
||||
"""
|
||||
reference: Final = current_time if current_time is not None else current_billing_time()
|
||||
reference_utc: Final = (
|
||||
|
|
@ -540,7 +548,7 @@ def _is_off_peak(off_peak: Mapping[str, object], current_time: datetime | None =
|
|||
override_rules: Final = tuple(
|
||||
rule
|
||||
for rule in rules
|
||||
if _as_window_strings(rule.get("hours_utc")) and today in _as_dates(rule.get("override_dates"))
|
||||
if _parse_windows(_as_window_strings(rule.get("hours_utc"))) and today in _as_dates(rule.get("override_dates"))
|
||||
)
|
||||
if override_rules:
|
||||
for rule in override_rules:
|
||||
|
|
|
|||
|
|
@ -723,6 +723,9 @@ def test_is_off_peak_ignores_malformed_override_dates():
|
|||
["2026-1-1"],
|
||||
["2026-W01-4"],
|
||||
["2026-01-1 "],
|
||||
["2026-02-30"],
|
||||
["2026-13-01"],
|
||||
["2026-01-011"],
|
||||
):
|
||||
block = {"windows": peak_windows + [{"hours_utc": "00:00-00:00", "override_dates": bad}]}
|
||||
assert _is_off_peak(block, peak_instant) is False, (
|
||||
|
|
@ -740,11 +743,47 @@ def test_is_off_peak_override_rule_without_valid_hours_is_disabled():
|
|||
"windows": [
|
||||
{"override_dates": ["2026-03-03"], "hours_utc": 5},
|
||||
{"override_dates": ["2026-03-03"]},
|
||||
{"override_dates": ["2026-03-03"], "hours_utc": "garbage"},
|
||||
{"override_dates": ["2026-03-03"], "hours_utc": "25:00-26:00"},
|
||||
{"override_dates": ["2026-03-03"], "hours_utc": ["4pm-midnight"]},
|
||||
],
|
||||
}
|
||||
assert _is_off_peak(block, datetime(2026, 3, 3, 12, 0, tzinfo=timezone.utc)) is True
|
||||
|
||||
|
||||
def test_is_off_peak_override_rule_ignores_its_weekdays():
|
||||
"""On a date an override rule lists, its own weekdays field is ignored: a weekend-only
|
||||
holiday rule still applies on the Thursday holiday."""
|
||||
from datetime import datetime, timezone
|
||||
|
||||
block = {
|
||||
"windows": [
|
||||
{"hours_utc": ["00:00-01:00", "04:00-06:00", "10:00-00:00"], "weekdays": [1, 2, 3, 4, 5]},
|
||||
{"hours_utc": "00:00-00:00", "weekdays": [6, 7], "override_dates": ["2026-01-01"]},
|
||||
],
|
||||
}
|
||||
holiday_thursday = datetime(2026, 1, 1, 2, 0, tzinfo=timezone.utc)
|
||||
assert _is_off_peak(block, holiday_thursday) is True, (
|
||||
f"{holiday_thursday.isoformat()} is a Thursday holiday billed off-peak all day"
|
||||
)
|
||||
|
||||
|
||||
def test_is_off_peak_matching_override_rules_are_a_union():
|
||||
"""Every rule listing today's date applies: the date is off-peak inside any of their
|
||||
windows, peak outside all of them."""
|
||||
from datetime import datetime, timezone
|
||||
|
||||
block = {
|
||||
"windows": [
|
||||
{"override_dates": ["2026-03-03"], "hours_utc": "01:00-02:00"},
|
||||
{"override_dates": ["2026-03-03"], "hours_utc": "12:00-13:00"},
|
||||
],
|
||||
}
|
||||
assert _is_off_peak(block, datetime(2026, 3, 3, 1, 30, tzinfo=timezone.utc)) is True
|
||||
assert _is_off_peak(block, datetime(2026, 3, 3, 12, 30, tzinfo=timezone.utc)) is True
|
||||
assert _is_off_peak(block, datetime(2026, 3, 3, 5, 0, tzinfo=timezone.utc)) is False
|
||||
|
||||
|
||||
def test_is_off_peak_disables_rules_with_partially_malformed_override_dates():
|
||||
"""One bad entry disables the whole override_dates list: a rule whose dates do not all
|
||||
parse never applies on any date, so the flat hours decide alone."""
|
||||
|
|
@ -803,6 +842,35 @@ def test_override_dates_use_weekday_timezone_calendar_on_shipped_deepseek_rows()
|
|||
]
|
||||
assert not drifted, f"off_peak schedule drift on {drifted}"
|
||||
|
||||
from datetime import date, timedelta
|
||||
|
||||
windows: Final = block["windows"]
|
||||
assert windows[:2] == [
|
||||
{"hours_utc": ["00:00-01:00", "04:00-06:00", "10:00-00:00"], "weekdays": [1, 2, 3, 4, 5]},
|
||||
{"hours_utc": "00:00-00:00", "weekdays": [6, 7]},
|
||||
]
|
||||
holidays: Final = {
|
||||
(start + timedelta(days=offset)).isoformat()
|
||||
for start, days in (
|
||||
(date(2026, 1, 1), 3),
|
||||
(date(2026, 2, 15), 9),
|
||||
(date(2026, 4, 4), 3),
|
||||
(date(2026, 5, 1), 5),
|
||||
(date(2026, 6, 19), 3),
|
||||
(date(2026, 9, 25), 3),
|
||||
(date(2026, 10, 1), 7),
|
||||
)
|
||||
for offset in range(days)
|
||||
}
|
||||
workdays: Final = {"2026-01-04", "2026-02-14", "2026-02-28", "2026-05-09", "2026-09-20", "2026-10-10"}
|
||||
holiday_rule, workday_rule = windows[2], windows[3]
|
||||
assert holiday_rule["hours_utc"] == "00:00-00:00"
|
||||
assert set(holiday_rule["override_dates"]) == holidays
|
||||
assert workday_rule["hours_utc"] == windows[0]["hours_utc"]
|
||||
assert set(workday_rule["override_dates"]) == workdays
|
||||
assert holidays.isdisjoint(workdays)
|
||||
assert all(date.fromisoformat(day).isoweekday() in (6, 7) for day in workdays)
|
||||
|
||||
shanghai_make_up_sunday = datetime(2026, 1, 3, 17, 0, tzinfo=timezone.utc)
|
||||
make_up_sunday_peak = datetime(2026, 1, 4, 2, 0, tzinfo=timezone.utc)
|
||||
plain_sunday = datetime(2026, 1, 11, 2, 0, tzinfo=timezone.utc)
|
||||
|
|
|
|||
|
|
@ -392,7 +392,11 @@ def deepseek_off_peak_drift(entry: Mapping[str, object]) -> str | None:
|
|||
windows: Final = tuple(block.get("windows", ()))
|
||||
if windows[: len(DEEPSEEK_OFF_PEAK_WINDOWS)] != DEEPSEEK_OFF_PEAK_WINDOWS:
|
||||
return f"windows={block.get('windows')}"
|
||||
if not all("override_dates" in rule for rule in windows[len(DEEPSEEK_OFF_PEAK_WINDOWS) :]):
|
||||
if not all(
|
||||
"override_dates" in rule
|
||||
and rule.get("hours_utc") in ("00:00-00:00", DEEPSEEK_OFF_PEAK_WINDOWS[0]["hours_utc"])
|
||||
for rule in windows[len(DEEPSEEK_OFF_PEAK_WINDOWS) :]
|
||||
):
|
||||
return f"windows={block.get('windows')}"
|
||||
halved: Final = {rate: block.get(rate) for rate in DEEPSEEK_HALVED_RATES}
|
||||
expected: Final = {rate: float(str(entry[rate])) / 2 for rate in DEEPSEEK_HALVED_RATES}
|
||||
|
|
|
|||
|
|
@ -1057,7 +1057,11 @@ def test_aaamodel_prices_and_context_window_json_is_valid():
|
|||
"type": "array",
|
||||
"items": {"oneOf": [{"type": "integer"}, {"type": "string"}]},
|
||||
},
|
||||
"override_dates": {"type": "array", "items": {"type": "string"}},
|
||||
"override_dates": {
|
||||
"type": "array",
|
||||
"minItems": 1,
|
||||
"items": {"type": "string", "pattern": "^\\d{4}-\\d{2}-\\d{2}$"},
|
||||
},
|
||||
},
|
||||
"required": ["hours_utc"],
|
||||
"additionalProperties": False,
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue