This commit is contained in:
Anuj ojha 2026-08-29 07:16:28 -04:00 committed by GitHub
commit 135740967d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 55 additions and 9 deletions

View file

@ -2233,9 +2233,7 @@ def batch_cost_calculator(
output_cost_per_token: Final = model_info.get("output_cost_per_token")
total_prompt_cost = 0.0
total_completion_cost = 0.0
if input_cost_per_token_batches is not None:
total_prompt_cost = usage.prompt_tokens * input_cost_per_token_batches
elif input_cost_per_token:
if input_cost_per_token_batches is not None or input_cost_per_token:
details: Final = parse_prompt_tokens_details(usage)
cache_read_tokens: Final = details["cache_hit_tokens"]
cache_creation_tokens: Final = details["cache_creation_tokens"]
@ -2243,15 +2241,20 @@ def batch_cost_calculator(
# Subtract cached tokens from prompt_tokens before calculating cost
# Fixes issue where cached tokens are being charged again
base_input_tokens: Final = get_billable_input_tokens(usage) - cache_creation_tokens
total_prompt_cost = (
base_input_tokens * (input_cost_per_token) / 2
) # batch cost is usually half of the regular token cost
batch_input_cost_per_token: Final = (
input_cost_per_token_batches
if input_cost_per_token_batches is not None
else (input_cost_per_token or 0.0) / 2
)
total_prompt_cost = base_input_tokens * batch_input_cost_per_token
# 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_creation_cost: Final = model_info.get("cache_creation_input_token_cost") or input_cost_per_token
non_batch_input_cost: Final = (
input_cost_per_token if input_cost_per_token else (input_cost_per_token_batches or 0.0) * 2
)
cache_creation_cost: Final = model_info.get("cache_creation_input_token_cost") or non_batch_input_cost
total_prompt_cost += cache_creation_tokens * cache_creation_cost / 2
if output_cost_per_token_batches is not None:
total_completion_cost = usage.completion_tokens * output_cost_per_token_batches

View file

@ -431,7 +431,11 @@ def test_total_usage_and_cost_normalize_mixed_responses_and_chat():
assert usage.completion_tokens == 12
assert usage.total_tokens == 42
assert usage.cache_read_input_tokens == 3
assert cost == pytest.approx((30 * 0.00125) + (12 * 0.005))
# model_info declares no cache_read_input_token_cost, so the 3 cached tokens
# are excluded from the base input_cost_per_token_batches charge (consistent
# with how generic_cost_per_token prices an undeclared cache rate at $0)
# rather than billed at the full non-cached batches rate.
assert cost == pytest.approx((27 * 0.00125) + (12 * 0.005))
def test_total_usage_empty_is_zero():

View file

@ -3778,6 +3778,45 @@ 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_discounts_cache_tokens_with_explicit_batches_rate():
"""
Regression test: when a model declares an explicit input_cost_per_token_batches
(e.g. gpt-4.1, gpt-4o, gemini-3-pro-preview, Claude on Bedrock/Vertex all do),
cached and cache-creation tokens must still get their cache discount instead of
being billed at the full non-cached batches rate.
Before the fix, batch_cost_calculator billed every prompt token (including the
8000 cached and 2000 cache-creation tokens below) at the flat batches rate,
overcharging by more than 2x on cache-heavy batch requests.
"""
from litellm.cost_calculator import batch_cost_calculator
model_info: ModelInfo = {
"supported_openai_params": [],
"input_cost_per_token": 3e-6,
"input_cost_per_token_batches": 1.5e-6, # exactly half of input_cost_per_token
"output_cost_per_token": 15e-6,
"output_cost_per_token_batches": 7.5e-6,
"cache_read_input_token_cost": 3e-7,
"cache_creation_input_token_cost": 3.75e-6,
}
prompt_cost, completion_cost_value = batch_cost_calculator(
usage=_batch_cache_usage(),
model="claude-sonnet-4-5-20250929",
custom_llm_provider="anthropic",
model_info=model_info,
)
expected_prompt_cost = (1000 * 3e-6 + 8000 * 3e-7 + 2000 * 3.75e-6) / 2
assert prompt_cost == pytest.approx(expected_prompt_cost)
assert completion_cost_value == pytest.approx(200 * 7.5e-6)
# The bug billed every prompt token (cached ones included) at the flat batches
# rate, so guard against regressing back to that overcharge.
buggy_prompt_cost = 11000 * 1.5e-6
assert prompt_cost < buggy_prompt_cost
def test_completion_cost_bills_interactions_api_response():
from litellm.types.interactions import InteractionsAPIResponse