From 1f9fe73bd927ddf57aa76eb10596709f13753c68 Mon Sep 17 00:00:00 2001 From: its-amann Date: Thu, 11 Jun 2026 19:40:14 +0530 Subject: [PATCH 1/6] Fix tiered custom pricing for cache tokens --- litellm/litellm_core_utils/litellm_logging.py | 3 +- .../litellm_core_utils/llm_cost_calc/utils.py | 22 ++++- .../test_litellm_logging.py | 81 +++++++++++++++++++ tests/unit/test_cost_calculator.py | 52 ++++++++++++ 4 files changed, 155 insertions(+), 3 deletions(-) diff --git a/litellm/litellm_core_utils/litellm_logging.py b/litellm/litellm_core_utils/litellm_logging.py index 8d5be388eff..d0b83b43c7a 100644 --- a/litellm/litellm_core_utils/litellm_logging.py +++ b/litellm/litellm_core_utils/litellm_logging.py @@ -7219,7 +7219,8 @@ def get_standard_logging_object_payload( custom_llm_provider=kwargs.get("custom_llm_provider"), init_response_obj=init_response_obj, api_base=litellm_params.get("api_base"), - router_model_id=logging_obj.get_router_model_id(), + router_model_id=logging_obj.get_router_model_id() + or (_model_id if isinstance(_model_id, str) and _model_id else None), ) error_information, error_str = StandardLoggingPayloadSetup.get_error_information_for_logging_payload( diff --git a/litellm/litellm_core_utils/llm_cost_calc/utils.py b/litellm/litellm_core_utils/llm_cost_calc/utils.py index a874459798d..7e5019d359e 100644 --- a/litellm/litellm_core_utils/llm_cost_calc/utils.py +++ b/litellm/litellm_core_utils/llm_cost_calc/utils.py @@ -288,7 +288,7 @@ def _parse_above_token_threshold(key: str) -> float: return _parse_token_threshold(key.rsplit("_above_", 1)[1].split("_tokens")[0]) -def _prompt_exceeds_threshold(prompt_tokens: int, threshold: float, inclusive: bool) -> bool: +def _prompt_exceeds_threshold(prompt_tokens: float, threshold: float, inclusive: bool) -> bool: return prompt_tokens > threshold or (inclusive and prompt_tokens == threshold) @@ -689,6 +689,23 @@ def _apply_off_peak_to_base_costs( ) +def _loose_model_extra_token(usage: Usage, field_name: str) -> float: + extra: Final = usage.model_extra + if not isinstance(extra, dict) or field_name not in extra: + return 0.0 + token: Final = extra.get(field_name) + if isinstance(token, bool) or not isinstance(token, (int, float)): + return 0.0 + return float(token) + + +def _get_effective_prompt_tokens_for_tiered_pricing(usage: Usage) -> float: + prompt_tokens: Final = float(usage.prompt_tokens or 0) + cache_read_tokens: Final = _loose_model_extra_token(usage, "cache_read_input_tokens") + cache_creation_tokens: Final = _loose_model_extra_token(usage, "cache_creation_input_tokens") + return prompt_tokens + cache_read_tokens + cache_creation_tokens + + def _get_token_base_cost( model_info: ModelInfo, usage: Usage, @@ -761,6 +778,7 @@ def _get_token_base_cost( # Only sort the threshold keys (typically 1-2 keys instead of 66+) threshold: float | None = None + effective_prompt_tokens: Final = _get_effective_prompt_tokens_for_tiered_pricing(usage) for key in sorted(threshold_keys, key=_parse_above_token_threshold): value = model_info.get(key) if value is not None: @@ -768,7 +786,7 @@ def _get_token_base_cost( # Handle both formats: _above_128k_tokens and _above_128_tokens threshold_str = key.rsplit("_above_", 1)[1].split("_tokens")[0] threshold = _parse_above_token_threshold(key) - if _prompt_exceeds_threshold(usage.prompt_tokens, threshold, threshold_is_inclusive): + if _prompt_exceeds_threshold(effective_prompt_tokens, threshold, threshold_is_inclusive): # Prefer a service_tier-specific above-threshold key when available, # e.g. input_cost_per_token_priority_above_200k_tokens for Gemini # ON_DEMAND_PRIORITY. Falls back to the standard key automatically diff --git a/tests/unit/litellm_core_utils/test_litellm_logging.py b/tests/unit/litellm_core_utils/test_litellm_logging.py index 00c2b1ad8a8..dd1353826f9 100644 --- a/tests/unit/litellm_core_utils/test_litellm_logging.py +++ b/tests/unit/litellm_core_utils/test_litellm_logging.py @@ -5701,6 +5701,87 @@ def test_get_standard_logging_object_payload_includes_litellm_call_id(logging_ob assert payload["litellm_call_id"] == call_id +def test_standard_logging_model_map_uses_router_model_info_for_custom_pricing(): + """Spend-log model_map_value keeps the deployment above-200k rates from request metadata.""" + from litellm.litellm_core_utils.litellm_logging import ( + Logging, + get_standard_logging_object_payload, + ) + + model_id = "tiered-custom-pricing-logging-test" + model_info = { + "litellm_provider": "vertex_ai", + "mode": "chat", + "input_cost_per_token": 0.000005, + "output_cost_per_token": 0.000025, + "cache_creation_input_token_cost": 0.00000625, + "cache_read_input_token_cost": 0.0000005, + "input_cost_per_token_above_200k_tokens": 0.00001, + "output_cost_per_token_above_200k_tokens": 0.0000375, + "cache_creation_input_token_cost_above_200k_tokens": 0.0000125, + "cache_read_input_token_cost_above_200k_tokens": 0.000001, + } + litellm.register_model(model_cost={model_id: model_info}) + + try: + logging_obj = Logging( + model="vertex_ai/claude-opus-4-6", + messages=[{"role": "user", "content": "Hello"}], + stream=False, + call_type="completion", + start_time=datetime.datetime.now(), + litellm_call_id="test-tiered-pricing", + function_id="test-function", + ) + payload = get_standard_logging_object_payload( + kwargs={ + "model": "vertex_ai/claude-opus-4-6", + "messages": [{"role": "user", "content": "Hello"}], + "response_cost": 1.0, + "custom_llm_provider": "vertex_ai", + "litellm_params": { + "api_base": "", + "metadata": { + "model_info": { + "id": model_id, + **model_info, + }, + }, + }, + }, + init_response_obj={ + "id": "chatcmpl-test", + "model": "vertex_ai/claude-opus-4-6", + "usage": { + "prompt_tokens": 0, + "completion_tokens": 0, + "total_tokens": 585659, + "cache_creation_input_tokens": 585659, + }, + "choices": [], + }, + start_time=datetime.datetime.now(), + end_time=datetime.datetime.now(), + logging_obj=logging_obj, + status="success", + ) + + assert payload is not None + model_map_value = payload["model_map_information"]["model_map_value"] + assert model_map_value is not None + assert ( + model_map_value["cache_creation_input_token_cost_above_200k_tokens"] + == model_info["cache_creation_input_token_cost_above_200k_tokens"] + ) + assert ( + model_map_value["input_cost_per_token_above_200k_tokens"] + == model_info["input_cost_per_token_above_200k_tokens"] + ) + finally: + litellm.model_cost.pop(model_id, None) + litellm.model_cost.pop(f"vertex_ai/{model_id}", None) + + @pytest.mark.parametrize( "hidden_params, passed_cache_hit, expected_cache_hit, expected_cost, expected_unique_id", [ diff --git a/tests/unit/test_cost_calculator.py b/tests/unit/test_cost_calculator.py index 99119799ac0..f7185284fcb 100644 --- a/tests/unit/test_cost_calculator.py +++ b/tests/unit/test_cost_calculator.py @@ -3529,6 +3529,58 @@ def test_custom_pricing_applies_cache_creation_input_cost_via_cache_write_tokens assert completion_cost == pytest.approx(expected_completion) +def test_custom_pricing_above_200k_cache_creation_tokens_uses_tiered_rate(): + """Cache creation tokens reported outside prompt_tokens still cross the above-200k tier.""" + + model_id = "tiered-custom-pricing-deploy-test" + model_info = { + "litellm_provider": "vertex_ai", + "mode": "chat", + "input_cost_per_token": 0.000005, + "output_cost_per_token": 0.000025, + "cache_creation_input_token_cost": 0.00000625, + "cache_read_input_token_cost": 0.0000005, + "input_cost_per_token_above_200k_tokens": 0.00001, + "output_cost_per_token_above_200k_tokens": 0.0000375, + "cache_creation_input_token_cost_above_200k_tokens": 0.0000125, + "cache_read_input_token_cost_above_200k_tokens": 0.000001, + } + cache_creation_tokens = 585659 + + litellm.register_model(model_cost={model_id: model_info}) + try: + response = ModelResponse( + id="test-id", + created=1234567890, + model="vertex_ai/claude-opus-4-6", + object="chat.completion", + choices=[], + usage=Usage( + prompt_tokens=0, + completion_tokens=0, + total_tokens=cache_creation_tokens, + cache_creation_input_tokens=cache_creation_tokens, + cache_read_input_tokens=0, + ), + ) + + cost = completion_cost( + completion_response=response, + model="vertex_ai/claude-opus-4-6", + custom_llm_provider="vertex_ai", + custom_pricing=True, + router_model_id=model_id, + ) + + assert cost == pytest.approx( + cache_creation_tokens * model_info["cache_creation_input_token_cost_above_200k_tokens"] + ) + assert cost != pytest.approx(cache_creation_tokens * model_info["cache_creation_input_token_cost"]) + finally: + litellm.model_cost.pop(model_id, None) + litellm.model_cost.pop(f"vertex_ai/{model_id}", None) + + # --------------------------------------------------------------------------- # Bug 2 — db_spend_update_writer cache token extraction helpers. # --------------------------------------------------------------------------- From b2dc59fa46f817966d7b49067d7e4de142c2e7d4 Mon Sep 17 00:00:00 2001 From: its-amann Date: Thu, 11 Jun 2026 19:49:50 +0530 Subject: [PATCH 2/6] Update litellm/litellm_core_utils/llm_cost_calc/utils.py --- .../litellm_core_utils/llm_cost_calc/utils.py | 41 +++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/litellm/litellm_core_utils/llm_cost_calc/utils.py b/litellm/litellm_core_utils/llm_cost_calc/utils.py index 7e5019d359e..82780db57cc 100644 --- a/litellm/litellm_core_utils/llm_cost_calc/utils.py +++ b/litellm/litellm_core_utils/llm_cost_calc/utils.py @@ -689,6 +689,30 @@ def _apply_off_peak_to_base_costs( ) +def _loose_token_count(value: object) -> float: + if isinstance(value, bool) or not isinstance(value, (int, float)): + return 0.0 + return float(value) + + +def _detail_token(details: PromptTokensDetailsWrapper, field_name: str) -> int | None: + if field_name not in details.__dict__: + return None + if field_name == "text_tokens": + return details.text_tokens + if field_name == "cached_tokens": + return details.cached_tokens + if field_name == "cache_creation_tokens": + return details.cache_creation_tokens + if field_name == "audio_tokens": + return details.audio_tokens + if field_name == "image_tokens": + return details.image_tokens + if field_name == "video_tokens": + return details.video_tokens + return None + + def _loose_model_extra_token(usage: Usage, field_name: str) -> float: extra: Final = usage.model_extra if not isinstance(extra, dict) or field_name not in extra: @@ -700,6 +724,23 @@ def _loose_model_extra_token(usage: Usage, field_name: str) -> float: def _get_effective_prompt_tokens_for_tiered_pricing(usage: Usage) -> float: + """Return the total effective input tokens for tier-threshold comparisons. + + Some providers (Anthropic direct, Bedrock) roll cache_creation and cache_read + tokens into prompt_tokens before constructing the Usage object, while others + (Vertex AI) keep them separate. When prompt_tokens_details is present we can + derive the true total from its per-category fields and avoid double-counting. + """ + details: Final = usage.prompt_tokens_details + if details is not None: + raw_text_tokens: Final = _detail_token(details, "text_tokens") + text_tokens: Final = _loose_token_count(raw_text_tokens) + cached_tokens: Final = _loose_token_count(_detail_token(details, "cached_tokens")) + cache_creation: Final = _loose_token_count(_detail_token(details, "cache_creation_tokens")) + if text_tokens == 0: + return float(usage.prompt_tokens or 0) + cached_tokens + cache_creation + return text_tokens + cached_tokens + cache_creation + prompt_tokens: Final = float(usage.prompt_tokens or 0) cache_read_tokens: Final = _loose_model_extra_token(usage, "cache_read_input_tokens") cache_creation_tokens: Final = _loose_model_extra_token(usage, "cache_creation_input_tokens") From 637597cb2e953f19fb3028183000b5f815618cbd Mon Sep 17 00:00:00 2001 From: its-amann Date: Fri, 12 Jun 2026 10:08:45 +0530 Subject: [PATCH 3/6] Avoid double counting cache tokens in tier check --- .../litellm_core_utils/llm_cost_calc/utils.py | 34 +++++++---- tests/unit/test_cost_calculator.py | 59 +++++++++++++++++++ 2 files changed, 80 insertions(+), 13 deletions(-) diff --git a/litellm/litellm_core_utils/llm_cost_calc/utils.py b/litellm/litellm_core_utils/llm_cost_calc/utils.py index 82780db57cc..47a6fcb6f59 100644 --- a/litellm/litellm_core_utils/llm_cost_calc/utils.py +++ b/litellm/litellm_core_utils/llm_cost_calc/utils.py @@ -731,20 +731,28 @@ def _get_effective_prompt_tokens_for_tiered_pricing(usage: Usage) -> float: (Vertex AI) keep them separate. When prompt_tokens_details is present we can derive the true total from its per-category fields and avoid double-counting. """ - details: Final = usage.prompt_tokens_details - if details is not None: - raw_text_tokens: Final = _detail_token(details, "text_tokens") - text_tokens: Final = _loose_token_count(raw_text_tokens) - cached_tokens: Final = _loose_token_count(_detail_token(details, "cached_tokens")) - cache_creation: Final = _loose_token_count(_detail_token(details, "cache_creation_tokens")) - if text_tokens == 0: - return float(usage.prompt_tokens or 0) + cached_tokens + cache_creation - return text_tokens + cached_tokens + cache_creation - prompt_tokens: Final = float(usage.prompt_tokens or 0) - cache_read_tokens: Final = _loose_model_extra_token(usage, "cache_read_input_tokens") - cache_creation_tokens: Final = _loose_model_extra_token(usage, "cache_creation_input_tokens") - return prompt_tokens + cache_read_tokens + cache_creation_tokens + details: Final = usage.prompt_tokens_details + if details is None: + cache_read_tokens: Final = _loose_model_extra_token(usage, "cache_read_input_tokens") + cache_creation_tokens: Final = _loose_model_extra_token(usage, "cache_creation_input_tokens") + return prompt_tokens + cache_read_tokens + cache_creation_tokens + + raw_text_tokens: Final = _detail_token(details, "text_tokens") + cached_tokens: Final = _loose_token_count(_detail_token(details, "cached_tokens")) + cache_creation: Final = _loose_token_count(_detail_token(details, "cache_creation_tokens")) + if raw_text_tokens is None: + # Some providers populate prompt_tokens_details without text_tokens. + # If prompt_tokens already covers the cache detail total, use it as + # the rolled-up input total. Otherwise cache tokens were reported + # outside prompt_tokens and must be added for the tier threshold. + cache_detail_tokens: Final = cached_tokens + cache_creation + if cache_detail_tokens > 0 and prompt_tokens < cache_detail_tokens: + return prompt_tokens + cache_detail_tokens + return prompt_tokens + + text_tokens: Final = float(raw_text_tokens) + return text_tokens + cached_tokens + cache_creation def _get_token_base_cost( diff --git a/tests/unit/test_cost_calculator.py b/tests/unit/test_cost_calculator.py index f7185284fcb..07f2e106a1c 100644 --- a/tests/unit/test_cost_calculator.py +++ b/tests/unit/test_cost_calculator.py @@ -3581,6 +3581,65 @@ def test_custom_pricing_above_200k_cache_creation_tokens_uses_tiered_rate(): litellm.model_cost.pop(f"vertex_ai/{model_id}", None) +def test_custom_pricing_does_not_double_count_cached_tokens_when_text_tokens_missing(): + """Rolled-up prompt_tokens that already include cache tokens stay on the base tier below 200k.""" + + model_id = "tiered-custom-pricing-anthropic-details-test" + model_info = { + "litellm_provider": "anthropic", + "mode": "chat", + "input_cost_per_token": 0.000005, + "output_cost_per_token": 0.000025, + "cache_creation_input_token_cost": 0.00000625, + "cache_read_input_token_cost": 0.0000005, + "input_cost_per_token_above_200k_tokens": 0.00001, + "output_cost_per_token_above_200k_tokens": 0.0000375, + "cache_creation_input_token_cost_above_200k_tokens": 0.0000125, + "cache_read_input_token_cost_above_200k_tokens": 0.000001, + } + cache_creation_tokens = 120_000 + + litellm.register_model(model_cost={model_id: model_info}) + try: + response = ModelResponse( + id="test-id", + created=1234567890, + model="anthropic/claude-sonnet-4-5", + object="chat.completion", + choices=[], + usage=Usage( + prompt_tokens=130_000, + completion_tokens=0, + total_tokens=130_000, + prompt_tokens_details=PromptTokensDetailsWrapper(cache_creation_tokens=cache_creation_tokens), + ), + ) + + cost = completion_cost( + completion_response=response, + model="anthropic/claude-sonnet-4-5", + custom_llm_provider="anthropic", + custom_pricing=True, + router_model_id=model_id, + ) + + non_cache_prompt_tokens = 10_000 + expected_cost = ( + non_cache_prompt_tokens * model_info["input_cost_per_token"] + + cache_creation_tokens * model_info["cache_creation_input_token_cost"] + ) + unexpected_tiered_cost = ( + non_cache_prompt_tokens * model_info["input_cost_per_token_above_200k_tokens"] + + cache_creation_tokens * model_info["cache_creation_input_token_cost_above_200k_tokens"] + ) + + assert cost == pytest.approx(expected_cost) + assert cost != pytest.approx(unexpected_tiered_cost) + finally: + litellm.model_cost.pop(model_id, None) + litellm.model_cost.pop(f"anthropic/{model_id}", None) + + # --------------------------------------------------------------------------- # Bug 2 — db_spend_update_writer cache token extraction helpers. # --------------------------------------------------------------------------- From d79dcc7476203926339f7a1e1960187589343fec Mon Sep 17 00:00:00 2001 From: its-amann Date: Fri, 12 Jun 2026 17:34:25 +0530 Subject: [PATCH 4/6] Clarify tiered pricing helper comment --- litellm/litellm_core_utils/llm_cost_calc/utils.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/litellm/litellm_core_utils/llm_cost_calc/utils.py b/litellm/litellm_core_utils/llm_cost_calc/utils.py index 47a6fcb6f59..0f8edc2fc28 100644 --- a/litellm/litellm_core_utils/llm_cost_calc/utils.py +++ b/litellm/litellm_core_utils/llm_cost_calc/utils.py @@ -726,10 +726,9 @@ def _loose_model_extra_token(usage: Usage, field_name: str) -> float: def _get_effective_prompt_tokens_for_tiered_pricing(usage: Usage) -> float: """Return the total effective input tokens for tier-threshold comparisons. - Some providers (Anthropic direct, Bedrock) roll cache_creation and cache_read - tokens into prompt_tokens before constructing the Usage object, while others - (Vertex AI) keep them separate. When prompt_tokens_details is present we can - derive the true total from its per-category fields and avoid double-counting. + Some providers report cache tokens inside prompt_tokens, while others keep + them separate. When prompt_tokens_details is present, prefer the category + fields so the threshold check can avoid double-counting cache tokens. """ prompt_tokens: Final = float(usage.prompt_tokens or 0) details: Final = usage.prompt_tokens_details From ceb8dd8074acc05094edff68f26c757406edf400 Mon Sep 17 00:00:00 2001 From: its-amann Date: Fri, 12 Jun 2026 18:29:24 +0530 Subject: [PATCH 5/6] Include multimodal tokens in tiered pricing threshold --- .../litellm_core_utils/llm_cost_calc/utils.py | 16 +++++- tests/unit/test_cost_calculator.py | 49 +++++++++++++++++++ 2 files changed, 64 insertions(+), 1 deletion(-) diff --git a/litellm/litellm_core_utils/llm_cost_calc/utils.py b/litellm/litellm_core_utils/llm_cost_calc/utils.py index 0f8edc2fc28..f4995911e8d 100644 --- a/litellm/litellm_core_utils/llm_cost_calc/utils.py +++ b/litellm/litellm_core_utils/llm_cost_calc/utils.py @@ -751,7 +751,21 @@ def _get_effective_prompt_tokens_for_tiered_pricing(usage: Usage) -> float: return prompt_tokens text_tokens: Final = float(raw_text_tokens) - return text_tokens + cached_tokens + cache_creation + audio_tokens: Final = _loose_token_count(_detail_token(details, "audio_tokens")) + image_tokens: Final = _loose_token_count(_detail_token(details, "image_tokens")) + video_tokens: Final = _loose_token_count(_detail_token(details, "video_tokens")) + detail_total: Final = ( + text_tokens + + audio_tokens + + image_tokens + + video_tokens + + cached_tokens + + cache_creation + ) + modality_cache_tokens: Final = cached_tokens + cache_creation + if modality_cache_tokens > 0 and detail_total > prompt_tokens: + return prompt_tokens + return max(prompt_tokens, detail_total) def _get_token_base_cost( diff --git a/tests/unit/test_cost_calculator.py b/tests/unit/test_cost_calculator.py index 07f2e106a1c..3f009643a16 100644 --- a/tests/unit/test_cost_calculator.py +++ b/tests/unit/test_cost_calculator.py @@ -3640,6 +3640,55 @@ def test_custom_pricing_does_not_double_count_cached_tokens_when_text_tokens_mis litellm.model_cost.pop(f"anthropic/{model_id}", None) +def test_custom_pricing_uses_tiered_rate_when_multimodal_details_cross_threshold(): + """Image tokens counted beside text_tokens cross the above-200k tier.""" + + model_id = "tiered-custom-pricing-multimodal-details-test" + model_info = { + "litellm_provider": "openai", + "mode": "chat", + "input_cost_per_token": 0.000005, + "output_cost_per_token": 0.000025, + "input_cost_per_token_above_200k_tokens": 0.00001, + "output_cost_per_token_above_200k_tokens": 0.0000375, + } + text_tokens = 150_000 + image_tokens = 60_001 + + litellm.register_model(model_cost={model_id: model_info}) + try: + response = ModelResponse( + id="test-id", + created=1234567890, + model=f"openai/{model_id}", + object="chat.completion", + choices=[], + usage=Usage( + prompt_tokens=text_tokens + image_tokens, + completion_tokens=0, + total_tokens=text_tokens + image_tokens, + prompt_tokens_details=PromptTokensDetailsWrapper(text_tokens=text_tokens, image_tokens=image_tokens), + ), + ) + + cost = completion_cost( + completion_response=response, + model=f"openai/{model_id}", + custom_llm_provider="openai", + custom_pricing=True, + router_model_id=model_id, + ) + + expected_cost = (text_tokens + image_tokens) * model_info["input_cost_per_token_above_200k_tokens"] + base_cost = (text_tokens + image_tokens) * model_info["input_cost_per_token"] + + assert cost == pytest.approx(expected_cost) + assert cost != pytest.approx(base_cost) + finally: + litellm.model_cost.pop(model_id, None) + litellm.model_cost.pop(f"openai/{model_id}", None) + + # --------------------------------------------------------------------------- # Bug 2 — db_spend_update_writer cache token extraction helpers. # --------------------------------------------------------------------------- From 50ff0c1346f948be9aa09f352bbf6869ce26ade0 Mon Sep 17 00:00:00 2001 From: its-amann Date: Sun, 28 Jun 2026 10:32:28 +0530 Subject: [PATCH 6/6] Format cost utility after merge --- litellm/litellm_core_utils/llm_cost_calc/utils.py | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/litellm/litellm_core_utils/llm_cost_calc/utils.py b/litellm/litellm_core_utils/llm_cost_calc/utils.py index f4995911e8d..7f71fce635e 100644 --- a/litellm/litellm_core_utils/llm_cost_calc/utils.py +++ b/litellm/litellm_core_utils/llm_cost_calc/utils.py @@ -754,14 +754,7 @@ def _get_effective_prompt_tokens_for_tiered_pricing(usage: Usage) -> float: audio_tokens: Final = _loose_token_count(_detail_token(details, "audio_tokens")) image_tokens: Final = _loose_token_count(_detail_token(details, "image_tokens")) video_tokens: Final = _loose_token_count(_detail_token(details, "video_tokens")) - detail_total: Final = ( - text_tokens - + audio_tokens - + image_tokens - + video_tokens - + cached_tokens - + cache_creation - ) + detail_total: Final = text_tokens + audio_tokens + image_tokens + video_tokens + cached_tokens + cache_creation modality_cache_tokens: Final = cached_tokens + cache_creation if modality_cache_tokens > 0 and detail_total > prompt_tokens: return prompt_tokens