From e6ec88b1b7503ab45ee654697ffae9b3de57c3e1 Mon Sep 17 00:00:00 2001 From: shivam Date: Thu, 16 Jul 2026 01:03:52 +0000 Subject: [PATCH] fix(cost): tier-aware fallback for Gemini image tokens; avoid double-billing per-second audio/video Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- .../litellm_core_utils/llm_cost_calc/utils.py | 23 ++++++++++--------- .../llm_cost_calc/test_llm_cost_calc_utils.py | 17 ++++++++------ 2 files changed, 22 insertions(+), 18 deletions(-) diff --git a/litellm/litellm_core_utils/llm_cost_calc/utils.py b/litellm/litellm_core_utils/llm_cost_calc/utils.py index 2a5322416de..8913425c03f 100644 --- a/litellm/litellm_core_utils/llm_cost_calc/utils.py +++ b/litellm/litellm_core_utils/llm_cost_calc/utils.py @@ -581,29 +581,30 @@ def _calculate_input_cost( ### AUDIO COST if prompt_tokens_details["audio_tokens"]: audio_cost_key = _get_service_tier_cost_key("input_cost_per_audio_token", service_tier) - if model_info.get(audio_cost_key) is None: - prompt_cost += float(prompt_tokens_details["audio_tokens"]) * prompt_base_cost - else: + if model_info.get(audio_cost_key) is not None: prompt_cost += calculate_cost_component(model_info, audio_cost_key, prompt_tokens_details["audio_tokens"]) + elif model_info.get("input_cost_per_audio_per_second") is None: + prompt_cost += float(prompt_tokens_details["audio_tokens"]) * prompt_base_cost ### IMAGE TOKEN COST if prompt_tokens_details["image_tokens"]: - # For image token costs: - # First check if input_cost_per_image_token is available. If not, default to generic input_cost_per_token. image_token_cost_key = "input_cost_per_image_token" - if model_info.get(image_token_cost_key) is None: - image_token_cost_key = "input_cost_per_token" - prompt_cost += calculate_cost_component(model_info, image_token_cost_key, prompt_tokens_details["image_tokens"]) + if model_info.get(image_token_cost_key) is not None: + prompt_cost += calculate_cost_component( + model_info, image_token_cost_key, prompt_tokens_details["image_tokens"] + ) + else: + prompt_cost += float(prompt_tokens_details["image_tokens"]) * prompt_base_cost ### VIDEO TOKEN COST if prompt_tokens_details["video_tokens"]: video_token_cost_key = "input_cost_per_video_token" - if model_info.get(video_token_cost_key) is None: - prompt_cost += float(prompt_tokens_details["video_tokens"]) * prompt_base_cost - else: + if model_info.get(video_token_cost_key) is not None: prompt_cost += calculate_cost_component( model_info, video_token_cost_key, prompt_tokens_details["video_tokens"] ) + elif model_info.get("input_cost_per_video_per_second") is None: + prompt_cost += float(prompt_tokens_details["video_tokens"]) * prompt_base_cost ### CACHE WRITING COST - Now uses tiered pricing if ( diff --git a/tests/test_litellm/litellm_core_utils/llm_cost_calc/test_llm_cost_calc_utils.py b/tests/test_litellm/litellm_core_utils/llm_cost_calc/test_llm_cost_calc_utils.py index b84cda71ea2..10ae51e3328 100644 --- a/tests/test_litellm/litellm_core_utils/llm_cost_calc/test_llm_cost_calc_utils.py +++ b/tests/test_litellm/litellm_core_utils/llm_cost_calc/test_llm_cost_calc_utils.py @@ -379,12 +379,12 @@ def test_audio_input_tokens_gemini_priced_at_text_rate(): ) -def test_audio_video_input_tokens_gemini_use_above_200k_tier(): - """Regression for LIT-4474: audio/video fallbacks must honor the >200k long-context tier. +def test_audio_video_image_input_tokens_gemini_use_above_200k_tier(): + """Regression for LIT-4474: audio/video/image fallbacks must honor the >200k long-context tier. Falling back to the raw un-tiered input_cost_per_token undercounts when the request crosses - the 200k boundary; audio and video should be priced at input_cost_per_token_above_200k_tokens - like text. + the 200k boundary; audio, video and image tokens should be priced at + input_cost_per_token_above_200k_tokens like text. """ model = "gemini-3-pro-preview" os.environ["LITELLM_LOCAL_MODEL_COST_MAP"] = "True" @@ -393,11 +393,13 @@ def test_audio_video_input_tokens_gemini_use_above_200k_tier(): model_cost_map = litellm.model_cost[f"gemini/{model}"] hi_rate = model_cost_map["input_cost_per_token_above_200k_tokens"] assert hi_rate != model_cost_map["input_cost_per_token"] + assert model_cost_map.get("input_cost_per_image_token") is None text_tokens = 100000 - audio_tokens = 60000 - video_tokens = 80000 - prompt_tokens = text_tokens + audio_tokens + video_tokens + audio_tokens = 40000 + video_tokens = 60000 + image_tokens = 40000 + prompt_tokens = text_tokens + audio_tokens + video_tokens + image_tokens usage = Usage( completion_tokens=0, prompt_tokens=prompt_tokens, @@ -406,6 +408,7 @@ def test_audio_video_input_tokens_gemini_use_above_200k_tier(): text_tokens=text_tokens, audio_tokens=audio_tokens, video_tokens=video_tokens, + image_tokens=image_tokens, ), )