From cbcb55af89a35aab85c4030116bd1badc7783600 Mon Sep 17 00:00:00 2001 From: kerry Date: Fri, 18 Sep 2026 18:11:17 +0000 Subject: [PATCH 1/3] fix(schema): classify off_peak_pricing as a structured object in the model prices schema generator Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- ci_cd/generate_model_prices_schema.py | 38 ++++++++++++++++ .../test_litellm/test_model_prices_schema.py | 44 +++++++++++++++++++ 2 files changed, 82 insertions(+) diff --git a/ci_cd/generate_model_prices_schema.py b/ci_cd/generate_model_prices_schema.py index ab29b70bdd4..bf669ccce29 100644 --- a/ci_cd/generate_model_prices_schema.py +++ b/ci_cd/generate_model_prices_schema.py @@ -31,7 +31,45 @@ EXTRA_BOOLEAN_KEYS = frozenset( } ) +HOURS_UTC: JsonSchema = { + "description": 'UTC "HH:MM-HH:MM" window, or a list of them; a window may wrap past midnight.', + "oneOf": [STRING, {"type": "array", "items": STRING, "minItems": 1}], +} + +OFF_PEAK_WINDOW: JsonSchema = { + "type": "object", + "properties": { + "hours_utc": HOURS_UTC, + "weekdays": { + "type": "array", + "description": "ISO-8601 weekday numbers (1 = Monday .. 7 = Sunday) or English day names the window applies on.", + "items": {"oneOf": [{"type": "integer", "minimum": 1, "maximum": 7}, STRING]}, + "minItems": 1, + }, + }, + "required": ["hours_utc"], + "additionalProperties": False, +} + OBJECT_KEYS: dict[str, JsonSchema] = { + "off_peak_pricing": { + "type": "object", + "description": "Rates that replace the same-named base fields while the request falls inside the stated UTC windows.", + "properties": { + "hours_utc": HOURS_UTC, + "windows": {"type": "array", "items": OFF_PEAK_WINDOW, "minItems": 1}, + "weekday_timezone": { + "type": "string", + "description": "IANA zone the weekdays of each window are read on; defaults to UTC.", + }, + "input_cost_per_token": NONNEG_NUMBER, + "output_cost_per_token": NONNEG_NUMBER, + "output_cost_per_reasoning_token": NONNEG_NUMBER, + "cache_read_input_token_cost": NONNEG_NUMBER, + "cache_creation_input_token_cost": NONNEG_NUMBER, + }, + "additionalProperties": False, + }, "search_context_cost_per_query": { "type": "object", "description": "USD cost per web search query, keyed by search context size.", diff --git a/tests/test_litellm/test_model_prices_schema.py b/tests/test_litellm/test_model_prices_schema.py index 2f9b11a16b7..65aaa2870ef 100644 --- a/tests/test_litellm/test_model_prices_schema.py +++ b/tests/test_litellm/test_model_prices_schema.py @@ -125,6 +125,50 @@ def test_schema_accepts_cache_creation_cost_inside_a_pricing_tier(committed_sche assert validator.is_valid({"some-model": entry}) +OFF_PEAK_ENTRY: Final = MappingProxyType( + { + "litellm_provider": "openrouter", + "mode": "chat", + "input_cost_per_token": 2e-6, + "output_cost_per_token": 8e-6, + "off_peak_pricing": { + "hours_utc": "16:30-00:30", + "windows": [{"hours_utc": ["00:30-02:00"], "weekdays": [6, "Sunday"]}], + "weekday_timezone": "Asia/Shanghai", + "input_cost_per_token": 1e-6, + "output_cost_per_token": 4e-6, + "cache_read_input_token_cost": 1e-7, + }, + } +) + + +def test_generator_classifies_off_peak_pricing_as_a_windowed_rate_block(): + generator = load_generator() + schema = json.loads(generator.render(generator.build_schema({"some-model": dict(OFF_PEAK_ENTRY)}))) + validator = build_validator(schema) + assert validator.is_valid({"some-model": dict(OFF_PEAK_ENTRY)}) + + +@pytest.mark.parametrize( + "block", + [ + {"hours_utc": "16:30-00:30", "input_cost_per_token": "1e-6"}, + {"hours_utc": "16:30-00:30", "input_cost_per_token": -1e-6}, + {"hours_utc": 1630, "input_cost_per_token": 1e-6}, + {"hours_utc": "16:30-00:30", "discount": 0.5}, + {"windows": [{"weekdays": [6]}], "input_cost_per_token": 1e-6}, + {"windows": [{"hours_utc": "00:30-02:00", "weekdays": [0]}], "input_cost_per_token": 1e-6}, + {"windows": [], "input_cost_per_token": 1e-6}, + ], +) +def test_generated_off_peak_schema_rejects_malformed_blocks(block: dict): + generator = load_generator() + schema = json.loads(generator.render(generator.build_schema({"some-model": dict(OFF_PEAK_ENTRY)}))) + validator = build_validator(schema) + assert not validator.is_valid({"some-model": {**OFF_PEAK_ENTRY, "off_peak_pricing": block}}) + + def find_duplicate_keys(path: Path) -> list[str]: duplicates: list[str] = [] From 4b215e2a60ff21079863e0a7151532a947a52a40 Mon Sep 17 00:00:00 2001 From: kerry Date: Fri, 18 Sep 2026 18:17:36 +0000 Subject: [PATCH 2/3] fix(schema): require a schedule and well-formed windows in off_peak_pricing Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- ci_cd/generate_model_prices_schema.py | 18 +++++++++++++----- tests/test_litellm/test_model_prices_schema.py | 7 ++++++- 2 files changed, 19 insertions(+), 6 deletions(-) diff --git a/ci_cd/generate_model_prices_schema.py b/ci_cd/generate_model_prices_schema.py index bf669ccce29..4ba4368e33c 100644 --- a/ci_cd/generate_model_prices_schema.py +++ b/ci_cd/generate_model_prices_schema.py @@ -19,6 +19,10 @@ NONNEG_NUMBER: JsonSchema = {"type": "number", "minimum": 0} NONNEG_INTEGER: JsonSchema = {"type": "integer", "minimum": 0} BOOLEAN: JsonSchema = {"type": "boolean"} STRING: JsonSchema = {"type": "string"} +TIME_WINDOW: JsonSchema = {"type": "string", "pattern": r"^([01]\d|2[0-3]):[0-5]\d-([01]\d|2[0-3]):[0-5]\d$"} +WEEKDAY_PATTERN = ( + r"(?i)^(mon|monday|tue|tues|tuesday|wed|wednesday|thu|thur|thurs|thursday|fri|friday|sat|saturday|sun|sunday)$" +) EXTRA_BOOLEAN_KEYS = frozenset( { @@ -33,7 +37,7 @@ EXTRA_BOOLEAN_KEYS = frozenset( HOURS_UTC: JsonSchema = { "description": 'UTC "HH:MM-HH:MM" window, or a list of them; a window may wrap past midnight.', - "oneOf": [STRING, {"type": "array", "items": STRING, "minItems": 1}], + "oneOf": [TIME_WINDOW, {"type": "array", "items": TIME_WINDOW, "minItems": 1}], } OFF_PEAK_WINDOW: JsonSchema = { @@ -43,7 +47,12 @@ OFF_PEAK_WINDOW: JsonSchema = { "weekdays": { "type": "array", "description": "ISO-8601 weekday numbers (1 = Monday .. 7 = Sunday) or English day names the window applies on.", - "items": {"oneOf": [{"type": "integer", "minimum": 1, "maximum": 7}, STRING]}, + "items": { + "oneOf": [ + {"type": "integer", "minimum": 1, "maximum": 7}, + {"type": "string", "pattern": WEEKDAY_PATTERN}, + ] + }, "minItems": 1, }, }, @@ -68,6 +77,7 @@ OBJECT_KEYS: dict[str, JsonSchema] = { "cache_read_input_token_cost": NONNEG_NUMBER, "cache_creation_input_token_cost": NONNEG_NUMBER, }, + "anyOf": [{"required": ["hours_utc"]}, {"required": ["windows"]}], "additionalProperties": False, }, "search_context_cost_per_query": { @@ -365,9 +375,7 @@ def render(schema: JsonSchema) -> str: def validation_errors(prices: dict, schema: JsonSchema) -> tuple: - validator = jsonschema.Draft202012Validator( - schema, format_checker=jsonschema.Draft202012Validator.FORMAT_CHECKER - ) + validator = jsonschema.Draft202012Validator(schema, format_checker=jsonschema.Draft202012Validator.FORMAT_CHECKER) return tuple( f"{'.'.join(str(part) for part in error.absolute_path)}: {error.message}" for error in validator.iter_errors(prices) diff --git a/tests/test_litellm/test_model_prices_schema.py b/tests/test_litellm/test_model_prices_schema.py index 65aaa2870ef..a9015397b32 100644 --- a/tests/test_litellm/test_model_prices_schema.py +++ b/tests/test_litellm/test_model_prices_schema.py @@ -133,7 +133,7 @@ OFF_PEAK_ENTRY: Final = MappingProxyType( "output_cost_per_token": 8e-6, "off_peak_pricing": { "hours_utc": "16:30-00:30", - "windows": [{"hours_utc": ["00:30-02:00"], "weekdays": [6, "Sunday"]}], + "windows": [{"hours_utc": ["00:30-02:00"], "weekdays": [6, "Sunday", "mon", "THURS"]}], "weekday_timezone": "Asia/Shanghai", "input_cost_per_token": 1e-6, "output_cost_per_token": 4e-6, @@ -160,6 +160,11 @@ def test_generator_classifies_off_peak_pricing_as_a_windowed_rate_block(): {"windows": [{"weekdays": [6]}], "input_cost_per_token": 1e-6}, {"windows": [{"hours_utc": "00:30-02:00", "weekdays": [0]}], "input_cost_per_token": 1e-6}, {"windows": [], "input_cost_per_token": 1e-6}, + {"input_cost_per_token": 1e-6}, + {"hours_utc": "16:30", "input_cost_per_token": 1e-6}, + {"hours_utc": "25:00-01:00", "input_cost_per_token": 1e-6}, + {"hours_utc": ["16:30-00:30", "4pm-midnight"], "input_cost_per_token": 1e-6}, + {"windows": [{"hours_utc": "00:30-02:00", "weekdays": ["Funday"]}], "input_cost_per_token": 1e-6}, ], ) def test_generated_off_peak_schema_rejects_malformed_blocks(block: dict): From 7208e310f038b6559f8ed99f4ddd295ebbc9987c Mon Sep 17 00:00:00 2001 From: kerry Date: Sat, 19 Sep 2026 00:39:57 +0000 Subject: [PATCH 3/3] fix(schema): annotate new off_peak_pricing constants with Final Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- ci_cd/generate_model_prices_schema.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/ci_cd/generate_model_prices_schema.py b/ci_cd/generate_model_prices_schema.py index 4ba4368e33c..8eec07dadda 100644 --- a/ci_cd/generate_model_prices_schema.py +++ b/ci_cd/generate_model_prices_schema.py @@ -3,7 +3,7 @@ from __future__ import annotations import json import sys from pathlib import Path -from typing import Optional +from typing import Final, Optional import jsonschema @@ -19,8 +19,8 @@ NONNEG_NUMBER: JsonSchema = {"type": "number", "minimum": 0} NONNEG_INTEGER: JsonSchema = {"type": "integer", "minimum": 0} BOOLEAN: JsonSchema = {"type": "boolean"} STRING: JsonSchema = {"type": "string"} -TIME_WINDOW: JsonSchema = {"type": "string", "pattern": r"^([01]\d|2[0-3]):[0-5]\d-([01]\d|2[0-3]):[0-5]\d$"} -WEEKDAY_PATTERN = ( +TIME_WINDOW: Final[JsonSchema] = {"type": "string", "pattern": r"^([01]\d|2[0-3]):[0-5]\d-([01]\d|2[0-3]):[0-5]\d$"} +WEEKDAY_PATTERN: Final = ( r"(?i)^(mon|monday|tue|tues|tuesday|wed|wednesday|thu|thur|thurs|thursday|fri|friday|sat|saturday|sun|sunday)$" ) @@ -35,12 +35,12 @@ EXTRA_BOOLEAN_KEYS = frozenset( } ) -HOURS_UTC: JsonSchema = { +HOURS_UTC: Final[JsonSchema] = { "description": 'UTC "HH:MM-HH:MM" window, or a list of them; a window may wrap past midnight.', "oneOf": [TIME_WINDOW, {"type": "array", "items": TIME_WINDOW, "minItems": 1}], } -OFF_PEAK_WINDOW: JsonSchema = { +OFF_PEAK_WINDOW: Final[JsonSchema] = { "type": "object", "properties": { "hours_utc": HOURS_UTC,