diff --git a/litellm/litellm_core_utils/llm_cost_calc/utils.py b/litellm/litellm_core_utils/llm_cost_calc/utils.py index cf1b1962df8..e24fa004448 100644 --- a/litellm/litellm_core_utils/llm_cost_calc/utils.py +++ b/litellm/litellm_core_utils/llm_cost_calc/utils.py @@ -74,12 +74,9 @@ def _uses_inclusive_token_thresholds(custom_llm_provider: str | None) -> bool: def apply_provider_cache_read_default(model_info: ModelInfo, custom_llm_provider: str | None) -> ModelInfo: - """Dispatch to the provider's cache-read pricing default; providers without one keep their entry as is.""" - match custom_llm_provider: - case "fireworks_ai": - return with_default_cache_read_rate(model_info) - case _: - return model_info + if custom_llm_provider == "fireworks_ai": + return with_default_cache_read_rate(model_info) + return model_info def _get_token_detail_value(details: object, key: str) -> int | None: diff --git a/litellm/llms/fireworks_ai/cache_pricing.py b/litellm/llms/fireworks_ai/cache_pricing.py index c28a8684c66..f5e49cad01a 100644 --- a/litellm/llms/fireworks_ai/cache_pricing.py +++ b/litellm/llms/fireworks_ai/cache_pricing.py @@ -1,7 +1,3 @@ -""" -Fireworks AI serverless cache-read pricing defaults. -""" - from typing import ( Final, cast, # noqa: TID251 # the derived entry is a dict copy of a ReadOnly TypedDict; no cast-free way to retype it @@ -11,16 +7,24 @@ from litellm.constants import FIREWORKS_AI_DEFAULT_CACHE_READ_RATE_RATIO from litellm.types.utils import ModelInfo +def _as_rate(value: object) -> float | None: + if isinstance(value, bool) or not isinstance(value, (int, float, str)): + return None + try: + return float(value) + except ValueError: + return None + + def with_default_cache_read_rate(model_info: ModelInfo) -> ModelInfo: - """Entries without a cache-read rate get the documented discount off the input rate; the shared map is - never mutated, so a copy carries it.""" - input_rate: Final = model_info.get("input_cost_per_token") + input_rate: Final = _as_rate(model_info.get("input_cost_per_token")) if model_info.get("cache_read_input_token_cost") is not None or input_rate is None: return model_info cache_read_rate: Final = input_rate * FIREWORKS_AI_DEFAULT_CACHE_READ_RATE_RATIO off_peak: Final = model_info.get("off_peak_pricing") if off_peak is None or "cache_read_input_token_cost" in off_peak: return cast(ModelInfo, {**model_info, "cache_read_input_token_cost": cache_read_rate}) + off_peak_input_rate: Final = _as_rate(off_peak.get("input_cost_per_token")) return cast( ModelInfo, { @@ -29,8 +33,8 @@ def with_default_cache_read_rate(model_info: ModelInfo) -> ModelInfo: "off_peak_pricing": { **off_peak, "cache_read_input_token_cost": ( - off_peak["input_cost_per_token"] * FIREWORKS_AI_DEFAULT_CACHE_READ_RATE_RATIO - if "input_cost_per_token" in off_peak + off_peak_input_rate * FIREWORKS_AI_DEFAULT_CACHE_READ_RATE_RATIO + if off_peak_input_rate is not None else cache_read_rate ), }, diff --git a/tests/test_litellm/llms/fireworks_ai/test_fireworks_ai_cache_pricing.py b/tests/test_litellm/llms/fireworks_ai/test_fireworks_ai_cache_pricing.py index 2ab273b27c6..c21943cfe75 100644 --- a/tests/test_litellm/llms/fireworks_ai/test_fireworks_ai_cache_pricing.py +++ b/tests/test_litellm/llms/fireworks_ai/test_fireworks_ai_cache_pricing.py @@ -48,3 +48,18 @@ def test_off_peak_window_without_its_own_input_rate_reuses_the_standard_derived_ derived = with_default_cache_read_rate(model_info) assert derived["off_peak_pricing"]["cache_read_input_token_cost"] == derived["cache_read_input_token_cost"] + + +def test_string_rates_from_config_are_coerced_before_the_discount_is_applied() -> None: + model_info: ModelInfo = { + "input_cost_per_token": "2e-6", + "off_peak_pricing": { + "hours_utc": "14:00-00:00", + "input_cost_per_token": "1e-6", + }, + } + + derived = with_default_cache_read_rate(model_info) + + assert derived["cache_read_input_token_cost"] == pytest.approx(1e-6) + assert derived["off_peak_pricing"]["cache_read_input_token_cost"] == pytest.approx(5e-7)