From 8d3c273d55932e70bd9bd5d11721443b117b13eb Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Wed, 1 Jul 2026 04:37:26 +0000 Subject: [PATCH] fix(cost): add missing cache_read_input_token_cost for DeepSeek models Several DeepSeek and OpenRouter/DeepSeek models had cache pricing data under input_cost_per_token_cache_hit but not under the key the cost calculator actually uses (cache_read_input_token_cost). This caused cost_breakdown to omit cache_read_cost for these models. Also adds cache_creation_input_token_cost=0.0 for all DeepSeek provider models since DeepSeek auto-caches with no creation cost. Closes #31594 Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- ...odel_prices_and_context_window_backup.json | 14 ++++ model_prices_and_context_window.json | 14 ++++ .../llm_cost_calc/test_llm_cost_calc_utils.py | 40 ++++++++++ tests/test_litellm/test_cost_calculator.py | 76 +++++++++++++++++++ 4 files changed, 144 insertions(+) diff --git a/litellm/model_prices_and_context_window_backup.json b/litellm/model_prices_and_context_window_backup.json index 21132db93cb..2f4417dd339 100644 --- a/litellm/model_prices_and_context_window_backup.json +++ b/litellm/model_prices_and_context_window_backup.json @@ -11255,6 +11255,7 @@ ] }, "deepseek-chat": { + "cache_creation_input_token_cost": 0.0, "cache_read_input_token_cost": 2.8e-08, "input_cost_per_token": 2.8e-07, "litellm_provider": "deepseek", @@ -11276,6 +11277,7 @@ "supports_tool_choice": true }, "deepseek-reasoner": { + "cache_creation_input_token_cost": 0.0, "cache_read_input_token_cost": 2.8e-08, "input_cost_per_token": 2.8e-07, "litellm_provider": "deepseek", @@ -13806,6 +13808,8 @@ "supports_tool_choice": true }, "deepseek/deepseek-coder": { + "cache_creation_input_token_cost": 0.0, + "cache_read_input_token_cost": 1.4e-08, "input_cost_per_token": 1.4e-07, "input_cost_per_token_cache_hit": 1.4e-08, "litellm_provider": "deepseek", @@ -13820,6 +13824,8 @@ "supports_tool_choice": true }, "deepseek/deepseek-r1": { + "cache_creation_input_token_cost": 0.0, + "cache_read_input_token_cost": 1.4e-07, "input_cost_per_token": 5.5e-07, "input_cost_per_token_cache_hit": 1.4e-07, "litellm_provider": "deepseek", @@ -13835,6 +13841,7 @@ "supports_tool_choice": true }, "deepseek/deepseek-reasoner": { + "cache_creation_input_token_cost": 0.0, "cache_read_input_token_cost": 2.8e-08, "input_cost_per_token": 2.8e-07, "input_cost_per_token_cache_hit": 2.8e-08, @@ -13875,6 +13882,8 @@ "supports_tool_choice": true }, "deepseek/deepseek-v3.2": { + "cache_creation_input_token_cost": 0.0, + "cache_read_input_token_cost": 2.8e-08, "input_cost_per_token": 2.8e-07, "input_cost_per_token_cache_hit": 2.8e-08, "litellm_provider": "deepseek", @@ -28324,6 +28333,7 @@ "supports_tool_choice": true }, "openrouter/deepseek/deepseek-chat-v3.1": { + "cache_read_input_token_cost": 2e-08, "input_cost_per_token": 2e-07, "input_cost_per_token_cache_hit": 2e-08, "litellm_provider": "openrouter", @@ -28339,6 +28349,7 @@ "supports_tool_choice": true }, "openrouter/deepseek/deepseek-v3.2": { + "cache_read_input_token_cost": 2.8e-08, "input_cost_per_token": 2.8e-07, "input_cost_per_token_cache_hit": 2.8e-08, "litellm_provider": "openrouter", @@ -28354,6 +28365,7 @@ "supports_tool_choice": true }, "openrouter/deepseek/deepseek-v3.2-exp": { + "cache_read_input_token_cost": 2e-08, "input_cost_per_token": 2e-07, "input_cost_per_token_cache_hit": 2e-08, "litellm_provider": "openrouter", @@ -28369,6 +28381,7 @@ "supports_tool_choice": true }, "openrouter/deepseek/deepseek-r1": { + "cache_read_input_token_cost": 1.4e-07, "input_cost_per_token": 5.5e-07, "input_cost_per_token_cache_hit": 1.4e-07, "litellm_provider": "openrouter", @@ -28384,6 +28397,7 @@ "supports_tool_choice": true }, "openrouter/deepseek/deepseek-r1-0528": { + "cache_read_input_token_cost": 1.4e-07, "input_cost_per_token": 5e-07, "input_cost_per_token_cache_hit": 1.4e-07, "litellm_provider": "openrouter", diff --git a/model_prices_and_context_window.json b/model_prices_and_context_window.json index 73cefeb7c77..bd5dd0eaca7 100644 --- a/model_prices_and_context_window.json +++ b/model_prices_and_context_window.json @@ -11255,6 +11255,7 @@ ] }, "deepseek-chat": { + "cache_creation_input_token_cost": 0.0, "cache_read_input_token_cost": 2.8e-08, "input_cost_per_token": 2.8e-07, "litellm_provider": "deepseek", @@ -11276,6 +11277,7 @@ "supports_tool_choice": true }, "deepseek-reasoner": { + "cache_creation_input_token_cost": 0.0, "cache_read_input_token_cost": 2.8e-08, "input_cost_per_token": 2.8e-07, "litellm_provider": "deepseek", @@ -13806,6 +13808,8 @@ "supports_tool_choice": true }, "deepseek/deepseek-coder": { + "cache_creation_input_token_cost": 0.0, + "cache_read_input_token_cost": 1.4e-08, "input_cost_per_token": 1.4e-07, "input_cost_per_token_cache_hit": 1.4e-08, "litellm_provider": "deepseek", @@ -13820,6 +13824,8 @@ "supports_tool_choice": true }, "deepseek/deepseek-r1": { + "cache_creation_input_token_cost": 0.0, + "cache_read_input_token_cost": 1.4e-07, "input_cost_per_token": 5.5e-07, "input_cost_per_token_cache_hit": 1.4e-07, "litellm_provider": "deepseek", @@ -13835,6 +13841,7 @@ "supports_tool_choice": true }, "deepseek/deepseek-reasoner": { + "cache_creation_input_token_cost": 0.0, "cache_read_input_token_cost": 2.8e-08, "input_cost_per_token": 2.8e-07, "input_cost_per_token_cache_hit": 2.8e-08, @@ -13875,6 +13882,8 @@ "supports_tool_choice": true }, "deepseek/deepseek-v3.2": { + "cache_creation_input_token_cost": 0.0, + "cache_read_input_token_cost": 2.8e-08, "input_cost_per_token": 2.8e-07, "input_cost_per_token_cache_hit": 2.8e-08, "litellm_provider": "deepseek", @@ -28485,6 +28494,7 @@ "supports_tool_choice": true }, "openrouter/deepseek/deepseek-chat-v3.1": { + "cache_read_input_token_cost": 2e-08, "input_cost_per_token": 2e-07, "input_cost_per_token_cache_hit": 2e-08, "litellm_provider": "openrouter", @@ -28500,6 +28510,7 @@ "supports_tool_choice": true }, "openrouter/deepseek/deepseek-v3.2": { + "cache_read_input_token_cost": 2.8e-08, "input_cost_per_token": 2.8e-07, "input_cost_per_token_cache_hit": 2.8e-08, "litellm_provider": "openrouter", @@ -28515,6 +28526,7 @@ "supports_tool_choice": true }, "openrouter/deepseek/deepseek-v3.2-exp": { + "cache_read_input_token_cost": 2e-08, "input_cost_per_token": 2e-07, "input_cost_per_token_cache_hit": 2e-08, "litellm_provider": "openrouter", @@ -28530,6 +28542,7 @@ "supports_tool_choice": true }, "openrouter/deepseek/deepseek-r1": { + "cache_read_input_token_cost": 1.4e-07, "input_cost_per_token": 5.5e-07, "input_cost_per_token_cache_hit": 1.4e-07, "litellm_provider": "openrouter", @@ -28545,6 +28558,7 @@ "supports_tool_choice": true }, "openrouter/deepseek/deepseek-r1-0528": { + "cache_read_input_token_cost": 1.4e-07, "input_cost_per_token": 5e-07, "input_cost_per_token_cache_hit": 1.4e-07, "litellm_provider": "openrouter", 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 e9977efe47d..bce193e80d2 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 @@ -2038,3 +2038,43 @@ def test_token_type_cost_breakdown_applies_regional_uplift(): text_input_cost = 600 * model_info["input_cost_per_token"] * uplift assert text_output_cost + eu.reasoning_cost == pytest.approx(completion_cost) assert text_input_cost + eu.cache_read_cost == pytest.approx(prompt_cost) + + +@pytest.mark.parametrize( + "model,custom_llm_provider,expected_cache_read_rate", + [ + ("deepseek-chat", "deepseek", 2.8e-08), + ("deepseek/deepseek-r1", "deepseek", 1.4e-07), + ("deepseek/deepseek-v3.2", "deepseek", 2.8e-08), + ("deepseek/deepseek-coder", "deepseek", 1.4e-08), + ], +) +def test_deepseek_cache_read_cost_in_breakdown( + model, custom_llm_provider, expected_cache_read_rate +): + """ + DeepSeek models report cached tokens via prompt_cache_hit_tokens. The + cost breakdown must produce a non-zero cache_read_cost matching the + model's cache_read_input_token_cost rate. + + Regression for https://github.com/BerriAI/litellm/issues/31594 + """ + os.environ["LITELLM_LOCAL_MODEL_COST_MAP"] = "True" + litellm.model_cost = litellm.get_model_cost_map(url="") + + cache_hit_tokens = 64 + usage = Usage( + prompt_tokens=100, + completion_tokens=50, + total_tokens=150, + prompt_cache_hit_tokens=cache_hit_tokens, + prompt_cache_miss_tokens=36, + ) + + breakdown = get_token_type_cost_breakdown( + model=model, custom_llm_provider=custom_llm_provider, usage=usage + ) + + assert breakdown.cache_read_cost == pytest.approx( + cache_hit_tokens * expected_cache_read_rate + ) diff --git a/tests/test_litellm/test_cost_calculator.py b/tests/test_litellm/test_cost_calculator.py index 81627cd3393..55fe4ce17ce 100644 --- a/tests/test_litellm/test_cost_calculator.py +++ b/tests/test_litellm/test_cost_calculator.py @@ -3176,3 +3176,79 @@ def test_completion_cost_logs_reasoning_and_cache_breakdown(): assert logging_obj.cost_breakdown is not None assert logging_obj.cost_breakdown["reasoning_cost"] == pytest.approx(3114 * 2.5e-06) assert logging_obj.cost_breakdown["cache_read_cost"] == pytest.approx(100 * 3e-08) + + +@pytest.mark.parametrize( + "model,custom_llm_provider,cache_read_rate", + [ + ("deepseek-chat", "deepseek", 2.8e-08), + ("deepseek/deepseek-r1", "deepseek", 1.4e-07), + ("deepseek/deepseek-v3.2", "deepseek", 2.8e-08), + ("deepseek/deepseek-coder", "deepseek", 1.4e-08), + ], +) +def test_deepseek_cost_breakdown_includes_cache_read_cost( + model, custom_llm_provider, cache_read_rate +): + """ + DeepSeek reports cached tokens via prompt_cache_hit_tokens. The cost + breakdown must surface cache_read_cost so spend logs show where cache + savings come from, not just a lower total. + + Regression for https://github.com/BerriAI/litellm/issues/31594 + """ + from datetime import datetime + + from litellm.litellm_core_utils.litellm_logging import Logging + from litellm.types.utils import Choices, Message + + os.environ["LITELLM_LOCAL_MODEL_COST_MAP"] = "True" + litellm.model_cost = litellm.get_model_cost_map(url="") + + cache_hit_tokens = 64 + + logging_obj = Logging( + model=model, + messages=[{"role": "user", "content": "Hello"}], + stream=False, + call_type="completion", + start_time=datetime.now(), + litellm_call_id="deepseek-cache-breakdown", + function_id="f", + ) + + response = ModelResponse( + id="x", + created=1, + model=model, + object="chat.completion", + choices=[ + Choices( + index=0, + message=Message(role="assistant", content="hi"), + finish_reason="stop", + ) + ], + usage=Usage( + prompt_tokens=100, + completion_tokens=50, + total_tokens=150, + prompt_cache_hit_tokens=cache_hit_tokens, + prompt_cache_miss_tokens=36, + ), + ) + + litellm.completion_cost( + completion_response=response, + model=model, + custom_llm_provider=custom_llm_provider, + litellm_logging_obj=logging_obj, + ) + + assert logging_obj.cost_breakdown is not None + assert "cache_read_cost" in logging_obj.cost_breakdown, ( + f"cache_read_cost missing from cost_breakdown for {model}" + ) + assert logging_obj.cost_breakdown["cache_read_cost"] == pytest.approx( + cache_hit_tokens * cache_read_rate + )