diff --git a/litellm/llms/dashscope/cost_calculator.py b/litellm/llms/dashscope/cost_calculator.py index 771ce140f66..8261e47f422 100644 --- a/litellm/llms/dashscope/cost_calculator.py +++ b/litellm/llms/dashscope/cost_calculator.py @@ -8,7 +8,8 @@ See https://help.aliyun.com/zh/model-studio/billing-for-model-studio """ from dataclasses import dataclass -from typing import Final +from types import MappingProxyType +from typing import Final, cast from litellm.litellm_core_utils.llm_cost_calc.tiered_pricing import select_tier_for_input, tier_rate from litellm.litellm_core_utils.llm_cost_calc.utils import ( @@ -50,6 +51,30 @@ def _extract_token_breakdown(usage: Usage) -> TokenBreakdown: ) +# Models like deepseek-v4-pro-0813 price thinking-mode requests at their own input, +# cache-hit, and output rates; reasoning tokens in the usage mark the request as thinking-mode +_THINKING_RATE_KEYS: Final = MappingProxyType( + { + "input_cost_per_token": "input_cost_per_token_thinking", + "cache_read_input_token_cost": "cache_read_input_token_cost_thinking", + "output_cost_per_token": "output_cost_per_token_thinking", + } +) + + +def _with_thinking_rates(model_info: ModelInfo, breakdown: TokenBreakdown) -> ModelInfo: + if breakdown.reasoning_tokens == 0: + return model_info + overrides: Final = { + base_key: model_info.get(thinking_key) + for base_key, thinking_key in _THINKING_RATE_KEYS.items() + if model_info.get(thinking_key) is not None + } + if not overrides: + return model_info + return cast("ModelInfo", {**model_info, **overrides}) + + def _flat_rate(model_info: ModelInfo, cost_key: str, fallback_cost_key: str) -> float: value: Final = model_info.get(cost_key) if value is None: @@ -123,8 +148,9 @@ def cost_per_token(model: str, usage: Usage) -> tuple[float, float]: Returns: Tuple[float, float] - (prompt_cost_in_usd, completion_cost_in_usd) """ - model_info: Final = get_model_info(model=model, custom_llm_provider="dashscope") + base_model_info: Final = get_model_info(model=model, custom_llm_provider="dashscope") breakdown: Final = _extract_token_breakdown(usage) + model_info: Final = _with_thinking_rates(model_info=base_model_info, breakdown=breakdown) raw_tiers: Final = model_info.get("tiered_pricing") tiered_pricing: Final = raw_tiers if isinstance(raw_tiers, list) else None tier: Final = ( diff --git a/litellm/model_prices_and_context_window_backup.json b/litellm/model_prices_and_context_window_backup.json index 718e6c489fd..11038fb04ac 100644 --- a/litellm/model_prices_and_context_window_backup.json +++ b/litellm/model_prices_and_context_window_backup.json @@ -13766,6 +13766,23 @@ "supports_system_messages": true, "supports_tool_choice": false }, + "dashscope/MiniMax/MiniMax-M3": { + "cache_read_input_token_cost": 1.19e-07, + "input_cost_per_token": 5.94e-07, + "litellm_provider": "dashscope", + "max_input_tokens": 1048576, + "max_output_tokens": 128000, + "max_tokens": 128000, + "mode": "chat", + "output_cost_per_token": 2.374e-06, + "source": "https://help.aliyun.com/en/model-studio/minimax-m3", + "supports_function_calling": true, + "supports_prompt_caching": true, + "supports_reasoning": true, + "supports_tool_choice": true, + "supports_video_input": true, + "supports_vision": true + }, "dashscope/deepseek-v4-flash": { "cache_read_input_token_cost": 4e-08, "input_cost_per_token": 2e-07, @@ -13814,6 +13831,26 @@ "supports_response_schema": true, "supports_tool_choice": true }, + "dashscope/deepseek-v4-pro-0813": { + "cache_read_input_token_cost": 6.4e-08, + "cache_read_input_token_cost_thinking": 1.27e-07, + "input_cost_per_token": 6.36e-07, + "input_cost_per_token_thinking": 1.272e-06, + "litellm_provider": "dashscope", + "max_input_tokens": 1000000, + "max_output_tokens": 393216, + "max_tokens": 393216, + "mode": "chat", + "output_cost_per_reasoning_token": 3.816e-06, + "output_cost_per_token": 1.908e-06, + "output_cost_per_token_thinking": 3.816e-06, + "source": "https://help.aliyun.com/en/model-studio/deepseek-v4-pro", + "supports_function_calling": true, + "supports_prompt_caching": true, + "supports_reasoning": true, + "supports_response_schema": true, + "supports_tool_choice": true + }, "dashscope/glm-5.1": { "cache_read_input_token_cost": 2.6e-07, "input_cost_per_token": 1.4e-06, @@ -13863,6 +13900,40 @@ "supports_tool_choice": true, "supports_vision": true }, + "dashscope/kimi-k3": { + "cache_read_input_token_cost": 2.83e-07, + "input_cost_per_token": 2.827e-06, + "litellm_provider": "dashscope", + "max_input_tokens": 1048576, + "max_output_tokens": 1048576, + "max_tokens": 1048576, + "mode": "chat", + "output_cost_per_token": 1.4133e-05, + "source": "https://www.alibabacloud.com/help/en/model-studio/kimi-k3", + "supports_function_calling": true, + "supports_prompt_caching": true, + "supports_reasoning": true, + "supports_response_schema": true, + "supports_tool_choice": true, + "supports_vision": true + }, + "dashscope/kimi/kimi-k3": { + "cache_read_input_token_cost": 2.83e-07, + "input_cost_per_token": 2.827e-06, + "litellm_provider": "dashscope", + "max_input_tokens": 1048576, + "max_output_tokens": 1048576, + "max_tokens": 1048576, + "mode": "chat", + "output_cost_per_token": 1.4133e-05, + "source": "https://www.alibabacloud.com/help/en/model-studio/kimi-k3", + "supports_function_calling": true, + "supports_prompt_caching": true, + "supports_reasoning": true, + "supports_response_schema": true, + "supports_tool_choice": true, + "supports_vision": true + }, "dashscope/qwen-coder": { "input_cost_per_token": 3e-07, "litellm_provider": "dashscope", diff --git a/litellm/types/utils.py b/litellm/types/utils.py index 55a32989b1c..3383f917cd3 100644 --- a/litellm/types/utils.py +++ b/litellm/types/utils.py @@ -222,6 +222,7 @@ class ModelInfoBase(ProviderSpecificModelInfo, total=False): cache_read_input_token_cost_above_272k_tokens_priority: float | None cache_read_input_token_cost_above_272k_tokens_flex: float | None cache_read_input_token_cost_above_512k_tokens: float | None + cache_read_input_token_cost_thinking: ReadOnly[float | None] # DashScope dual-mode models: cache-hit rate for thinking-mode requests # Smallest prefix this model will actually cache, whatever caching mechanism its provider uses. # Absent means the provider-agnostic default applies; see MINIMUM_PROMPT_CACHE_TOKEN_COUNT. prompt_cache_min_tokens: int | None @@ -234,6 +235,7 @@ class ModelInfoBase(ProviderSpecificModelInfo, total=False): input_cost_per_token_above_272k_tokens_priority: float | None input_cost_per_token_above_272k_tokens_flex: float | None input_cost_per_token_above_512k_tokens: float | None # MiniMax-M3: prompts >512K priced at 2x input + input_cost_per_token_thinking: ReadOnly[float | None] # DashScope dual-mode models: input rate for thinking-mode requests input_cost_per_character_above_128k_tokens: float | None # only for vertex ai models input_cost_per_query: float | None # only for rerank models input_cost_per_image: float | None # only for vertex ai models @@ -274,6 +276,7 @@ class ModelInfoBase(ProviderSpecificModelInfo, total=False): output_cost_per_reasoning_token: float | None output_cost_per_reasoning_token_flex: float | None output_cost_per_reasoning_token_priority: float | None + output_cost_per_token_thinking: ReadOnly[float | None] # DashScope dual-mode models: output rate for thinking-mode requests output_cost_per_video_per_second: float | None # only for vertex ai models output_cost_per_audio_per_second: float | None # only for vertex ai models output_cost_per_second: float | None # for OpenAI Speech models @@ -3453,6 +3456,9 @@ class CustomPricingLiteLLMParams(MirroredPricingParams): output_cost_per_reasoning_token: float | None = None output_cost_per_reasoning_token_flex: float | None = None output_cost_per_reasoning_token_priority: float | None = None + output_cost_per_token_thinking: float | None = None + input_cost_per_token_thinking: float | None = None + cache_read_input_token_cost_thinking: float | None = None output_cost_per_video_per_second: float | None = None output_cost_per_audio_per_second: float | None = None search_context_cost_per_query: dict[str, Any] | None = None diff --git a/litellm/utils.py b/litellm/utils.py index ab011f4123d..4f54de10262 100644 --- a/litellm/utils.py +++ b/litellm/utils.py @@ -5854,6 +5854,7 @@ def _get_model_info_helper( cache_read_input_token_cost_above_512k_tokens=_model_info.get( "cache_read_input_token_cost_above_512k_tokens", None ), + cache_read_input_token_cost_thinking=_model_info.get("cache_read_input_token_cost_thinking", None), cache_read_input_token_cost_flex=_model_info.get("cache_read_input_token_cost_flex", None), cache_read_input_token_cost_priority=_model_info.get("cache_read_input_token_cost_priority", None), cache_read_input_token_cost_ultrafast=_model_info.get("cache_read_input_token_cost_ultrafast", None), @@ -5874,6 +5875,7 @@ def _get_model_info_helper( "input_cost_per_token_above_272k_tokens_flex", None ), input_cost_per_token_above_512k_tokens=_model_info.get("input_cost_per_token_above_512k_tokens", None), + input_cost_per_token_thinking=_model_info.get("input_cost_per_token_thinking", None), input_cost_per_query=_model_info.get("input_cost_per_query", None), input_cost_per_second=_model_info.get("input_cost_per_second", None), input_cost_per_audio_token=_model_info.get("input_cost_per_audio_token", None), @@ -5899,6 +5901,7 @@ def _get_model_info_helper( output_cost_per_character=_model_info.get("output_cost_per_character", None), output_cost_per_reasoning_token=_model_info.get("output_cost_per_reasoning_token", None), output_cost_per_reasoning_token_flex=_model_info.get("output_cost_per_reasoning_token_flex", None), + output_cost_per_token_thinking=_model_info.get("output_cost_per_token_thinking", None), output_cost_per_reasoning_token_priority=_model_info.get( "output_cost_per_reasoning_token_priority", None ), diff --git a/model_prices_and_context_window.json b/model_prices_and_context_window.json index 718e6c489fd..11038fb04ac 100644 --- a/model_prices_and_context_window.json +++ b/model_prices_and_context_window.json @@ -13766,6 +13766,23 @@ "supports_system_messages": true, "supports_tool_choice": false }, + "dashscope/MiniMax/MiniMax-M3": { + "cache_read_input_token_cost": 1.19e-07, + "input_cost_per_token": 5.94e-07, + "litellm_provider": "dashscope", + "max_input_tokens": 1048576, + "max_output_tokens": 128000, + "max_tokens": 128000, + "mode": "chat", + "output_cost_per_token": 2.374e-06, + "source": "https://help.aliyun.com/en/model-studio/minimax-m3", + "supports_function_calling": true, + "supports_prompt_caching": true, + "supports_reasoning": true, + "supports_tool_choice": true, + "supports_video_input": true, + "supports_vision": true + }, "dashscope/deepseek-v4-flash": { "cache_read_input_token_cost": 4e-08, "input_cost_per_token": 2e-07, @@ -13814,6 +13831,26 @@ "supports_response_schema": true, "supports_tool_choice": true }, + "dashscope/deepseek-v4-pro-0813": { + "cache_read_input_token_cost": 6.4e-08, + "cache_read_input_token_cost_thinking": 1.27e-07, + "input_cost_per_token": 6.36e-07, + "input_cost_per_token_thinking": 1.272e-06, + "litellm_provider": "dashscope", + "max_input_tokens": 1000000, + "max_output_tokens": 393216, + "max_tokens": 393216, + "mode": "chat", + "output_cost_per_reasoning_token": 3.816e-06, + "output_cost_per_token": 1.908e-06, + "output_cost_per_token_thinking": 3.816e-06, + "source": "https://help.aliyun.com/en/model-studio/deepseek-v4-pro", + "supports_function_calling": true, + "supports_prompt_caching": true, + "supports_reasoning": true, + "supports_response_schema": true, + "supports_tool_choice": true + }, "dashscope/glm-5.1": { "cache_read_input_token_cost": 2.6e-07, "input_cost_per_token": 1.4e-06, @@ -13863,6 +13900,40 @@ "supports_tool_choice": true, "supports_vision": true }, + "dashscope/kimi-k3": { + "cache_read_input_token_cost": 2.83e-07, + "input_cost_per_token": 2.827e-06, + "litellm_provider": "dashscope", + "max_input_tokens": 1048576, + "max_output_tokens": 1048576, + "max_tokens": 1048576, + "mode": "chat", + "output_cost_per_token": 1.4133e-05, + "source": "https://www.alibabacloud.com/help/en/model-studio/kimi-k3", + "supports_function_calling": true, + "supports_prompt_caching": true, + "supports_reasoning": true, + "supports_response_schema": true, + "supports_tool_choice": true, + "supports_vision": true + }, + "dashscope/kimi/kimi-k3": { + "cache_read_input_token_cost": 2.83e-07, + "input_cost_per_token": 2.827e-06, + "litellm_provider": "dashscope", + "max_input_tokens": 1048576, + "max_output_tokens": 1048576, + "max_tokens": 1048576, + "mode": "chat", + "output_cost_per_token": 1.4133e-05, + "source": "https://www.alibabacloud.com/help/en/model-studio/kimi-k3", + "supports_function_calling": true, + "supports_prompt_caching": true, + "supports_reasoning": true, + "supports_response_schema": true, + "supports_tool_choice": true, + "supports_vision": true + }, "dashscope/qwen-coder": { "input_cost_per_token": 3e-07, "litellm_provider": "dashscope", diff --git a/tests/test_litellm/llms/dashscope/test_dashscope_cost_calculator.py b/tests/test_litellm/llms/dashscope/test_dashscope_cost_calculator.py index 8dc4620dd1b..347e633a9e4 100644 --- a/tests/test_litellm/llms/dashscope/test_dashscope_cost_calculator.py +++ b/tests/test_litellm/llms/dashscope/test_dashscope_cost_calculator.py @@ -14,7 +14,6 @@ import os import pytest # Add the project root to Python path - import litellm from litellm.llms.dashscope.cost_calculator import ( cost_per_token as dashscope_cost_per_token, @@ -526,3 +525,81 @@ class TestDashscopeCostCalculator: assert prompt_cost == 0.0 assert math.isclose(completion_cost, 500 * 1.6e-06, rel_tol=1e-10) + + def test_dashscope_thinking_mode_bills_thinking_rates(self): + """ + deepseek-v4-pro-0813 prices thinking-mode requests at their own input, cache-hit, + and output rates. A request whose usage carries reasoning tokens must bill every + token at those rates, not the cheaper non-thinking ones. + """ + usage = Usage( + prompt_tokens=1000, + completion_tokens=500, + prompt_tokens_details=PromptTokensDetailsWrapper(cached_tokens=200), + completion_tokens_details=CompletionTokensDetailsWrapper(reasoning_tokens=300), + ) + + prompt_cost, completion_cost = dashscope_cost_per_token( + model="deepseek-v4-pro-0813", usage=usage + ) + + model_info = litellm.get_model_info("dashscope/deepseek-v4-pro-0813") + expected_prompt_cost = ( + 800 * model_info["input_cost_per_token_thinking"] + + 200 * model_info["cache_read_input_token_cost_thinking"] + ) + expected_completion_cost = 500 * model_info["output_cost_per_token_thinking"] + + assert model_info["input_cost_per_token_thinking"] > model_info["input_cost_per_token"] + assert math.isclose(prompt_cost, expected_prompt_cost, rel_tol=1e-10) + assert math.isclose(completion_cost, expected_completion_cost, rel_tol=1e-10) + + def test_dashscope_non_thinking_request_bills_base_rates(self): + """ + The same dual-mode model without reasoning tokens in the usage stays on the + non-thinking rates. + """ + usage = Usage( + prompt_tokens=1000, + completion_tokens=500, + prompt_tokens_details=PromptTokensDetailsWrapper(cached_tokens=200), + ) + + prompt_cost, completion_cost = dashscope_cost_per_token( + model="deepseek-v4-pro-0813", usage=usage + ) + + model_info = litellm.get_model_info("dashscope/deepseek-v4-pro-0813") + expected_prompt_cost = ( + 800 * model_info["input_cost_per_token"] + + 200 * model_info["cache_read_input_token_cost"] + ) + expected_completion_cost = 500 * model_info["output_cost_per_token"] + + assert math.isclose(prompt_cost, expected_prompt_cost, rel_tol=1e-10) + assert math.isclose(completion_cost, expected_completion_cost, rel_tol=1e-10) + + def test_dashscope_zero_thinking_rate_bills_thinking_input_free(self): + """ + An explicit zero thinking rate is a price, not a missing value: it must override + the base rate rather than fall through to it. + """ + litellm.model_cost["dashscope/qwen-zero-thinking-test"] = { + "litellm_provider": "dashscope", + "mode": "chat", + "input_cost_per_token": 4e-07, + "input_cost_per_token_thinking": 0, + "output_cost_per_token": 1.6e-06, + } + + usage = Usage( + prompt_tokens=1000, + completion_tokens=500, + completion_tokens_details=CompletionTokensDetailsWrapper(reasoning_tokens=150), + ) + prompt_cost, completion_cost = dashscope_cost_per_token( + model="qwen-zero-thinking-test", usage=usage + ) + + assert prompt_cost == 0.0 + assert math.isclose(completion_cost, 500 * 1.6e-06, rel_tol=1e-10)