mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-06 08:16:43 +00:00
Merge a481ccfd11 into ec3f8183c3
This commit is contained in:
commit
d46a1ffeb8
6 changed files with 257 additions and 3 deletions
|
|
@ -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 = (
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
),
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue