From 250bc0393635953e5d850fd2f9902d07dd91d5e3 Mon Sep 17 00:00:00 2001 From: John Karam <168653085+johnkaram02@users.noreply.github.com> Date: Tue, 27 Oct 2026 23:49:49 +0000 Subject: [PATCH] fix(cost): bill batch cached tokens at the input rate without a cache read price When a model has no batch input price, batch_cost_calculator falls back to half the regular input price. That path subtracts cached tokens from the billed input and then prices them with cache_read_input_token_cost, which returns 0 when the model has no cache read price, so cached tokens in a batch cost nothing Bill cached tokens at the input price when the model has no cache read price, the same way cache writes and the batch price path already fall back --- litellm/cost_calculator.py | 7 +++++-- tests/unit/test_cost_calculator.py | 18 ++++++++++++++++++ 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/litellm/cost_calculator.py b/litellm/cost_calculator.py index 86b22069a39..7d179931f86 100644 --- a/litellm/cost_calculator.py +++ b/litellm/cost_calculator.py @@ -38,11 +38,11 @@ from litellm.litellm_core_utils.llm_cost_calc.utils import ( BilledTokenRates, CostCalculatorUtils, calculate_cache_writing_cost, - calculate_cost_component, generic_cost_per_character, generic_cost_per_token, get_batch_cost_rates, get_billable_input_tokens, + get_cost_per_unit, get_regional_uplift_multiplier, get_service_tier_cost_key, get_token_type_cost_breakdown, @@ -2993,7 +2993,10 @@ def batch_cost_calculator( # Add cache read cost if applicable cache_read_cost_key: Final = get_service_tier_cost_key("cache_read_input_token_cost", None) - total_prompt_cost += calculate_cost_component(model_info, cache_read_cost_key, cache_read_tokens) / 2 + cache_read_cost: Final = get_cost_per_unit(model_info, cache_read_cost_key, None) + total_prompt_cost += ( + cache_read_tokens * (input_cost_per_token if cache_read_cost is None else cache_read_cost) / 2 + ) cache_creation_cost: Final = model_info.get("cache_creation_input_token_cost") or input_cost_per_token total_prompt_cost += cache_creation_tokens * cache_creation_cost / 2 diff --git a/tests/unit/test_cost_calculator.py b/tests/unit/test_cost_calculator.py index 84f6dab9eff..72b38efee01 100644 --- a/tests/unit/test_cost_calculator.py +++ b/tests/unit/test_cost_calculator.py @@ -4310,6 +4310,24 @@ def test_batch_cost_calculator_cache_creation_falls_back_to_input_rate(): assert prompt_cost == pytest.approx((1000 * 3e-6 + 8000 * 3e-7 + 2000 * 3e-6) / 2) +def test_batch_cost_calculator_cache_read_falls_back_to_input_rate(): + from litellm.cost_calculator import batch_cost_calculator + + model_info: ModelInfo = { + "supported_openai_params": [], + "input_cost_per_token": 3e-6, + "output_cost_per_token": 15e-6, + } + prompt_cost, _ = batch_cost_calculator( + usage=_batch_cache_usage(), + model="my-fine-tuned-model", + custom_llm_provider="openai", + model_info=model_info, + ) + + assert prompt_cost == pytest.approx(11000 * 3e-6 / 2) + + def test_completion_cost_bills_interactions_api_response(): from litellm.types.interactions import InteractionsAPIResponse