feat(dashscope): bill qwen3-rerank by token usage

The base BaseRerankConfig prices rerank per query (input_cost_per_query *
search_units), but DashScope reports usage as total_tokens and never sets
search_units, so cost always resolved to $0.

Add a token-based calculate_rerank_cost override (input_cost_per_token *
total_tokens, mirroring jina_ai/voyage) and register a built-in price for
dashscope/qwen3-rerank ($0.1 / 1M tokens), so usage is billed out of the
box without any per-model pricing config.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Feng Jiajie 2026-05-28 20:59:18 +08:00
parent 5699a06413
commit 8acc9b9f3b
4 changed files with 123 additions and 2 deletions

View file

@ -22,7 +22,7 @@ as supported only for gte-rerank-v2 / qwen3-vl-rerank.
Docs - https://help.aliyun.com/zh/model-studio/text-rerank-api
"""
from typing import Any, Dict, List, Optional, Union
from typing import Any, Dict, List, Optional, Tuple, Union
import httpx
@ -38,6 +38,7 @@ from litellm.types.rerank import (
RerankResponseMeta,
RerankTokens,
)
from litellm.types.utils import ModelInfo
from ..common_utils import DashScopeError
@ -239,3 +240,33 @@ class DashScopeRerankConfig(BaseRerankConfig):
message=error_message,
headers=headers,
)
def calculate_rerank_cost(
self,
model: str,
custom_llm_provider: Optional[str] = None,
billed_units: Optional[RerankBilledUnits] = None,
model_info: Optional[ModelInfo] = None,
) -> Tuple[float, float]:
"""
qwen3-rerank is billed per token. DashScope reports the count in
usage.total_tokens, surfaced here as billed_units["total_tokens"];
set the per-token price via `input_cost_per_token` on the model.
The base BaseRerankConfig prices per query (input_cost_per_query *
search_units), which DashScope does not report — hence this override.
"""
if (
model_info is None
or "input_cost_per_token" not in model_info
or model_info["input_cost_per_token"] is None
or billed_units is None
):
return 0.0, 0.0
total_tokens = billed_units.get("total_tokens")
if total_tokens is None:
return 0.0, 0.0
input_cost = model_info["input_cost_per_token"] * total_tokens
return input_cost, 0.0

View file

@ -10992,6 +10992,13 @@
"supports_reasoning": true,
"supports_tool_choice": true
},
"dashscope/qwen3-rerank": {
"input_cost_per_token": 1e-07,
"litellm_provider": "dashscope",
"mode": "rerank",
"output_cost_per_token": 0.0,
"source": "https://www.alibabacloud.com/help/en/model-studio/model-pricing"
},
"dashscope/qwen3-vl-235b-a22b-instruct": {
"input_cost_per_token": 4e-07,
"litellm_provider": "dashscope",

View file

@ -10992,6 +10992,13 @@
"supports_reasoning": true,
"supports_tool_choice": true
},
"dashscope/qwen3-rerank": {
"input_cost_per_token": 1e-07,
"litellm_provider": "dashscope",
"mode": "rerank",
"output_cost_per_token": 0.0,
"source": "https://www.alibabacloud.com/help/en/model-studio/model-pricing"
},
"dashscope/qwen3-vl-235b-a22b-instruct": {
"input_cost_per_token": 4e-07,
"litellm_provider": "dashscope",

View file

@ -12,12 +12,14 @@ import pytest
sys.path.insert(0, os.path.abspath("../../../../.."))
import litellm
from litellm.cost_calculator import rerank_cost
from litellm.llms.dashscope.common_utils import DashScopeError
from litellm.llms.dashscope.rerank.transformation import (
DEFAULT_RERANK_URL,
DashScopeRerankConfig,
)
from litellm.types.rerank import RerankResponse
from litellm.types.rerank import RerankBilledUnits, RerankResponse
class TestDashScopeRerankURL:
@ -314,6 +316,80 @@ class TestDashScopeRerankResponse:
assert err.status_code == 500
class TestDashScopeRerankCost:
def setup_method(self):
self.config = DashScopeRerankConfig()
def test_cost_is_token_based(self):
# qwen3-rerank bills per token: total_tokens * input_cost_per_token.
prompt_cost, completion_cost = self.config.calculate_rerank_cost(
model="qwen3-rerank",
billed_units=RerankBilledUnits(total_tokens=1000),
model_info={"input_cost_per_token": 0.00000005}, # $0.05 / 1M tokens
)
assert abs(prompt_cost - 0.00005) < 1e-10 # 1000 * 0.00000005
assert completion_cost == 0.0
def test_cost_zero_when_total_tokens_missing(self):
prompt_cost, completion_cost = self.config.calculate_rerank_cost(
model="qwen3-rerank",
billed_units=RerankBilledUnits(),
model_info={"input_cost_per_token": 0.00000005},
)
assert (prompt_cost, completion_cost) == (0.0, 0.0)
def test_cost_zero_when_model_info_missing(self):
prompt_cost, completion_cost = self.config.calculate_rerank_cost(
model="qwen3-rerank",
billed_units=RerankBilledUnits(total_tokens=1000),
model_info=None,
)
assert (prompt_cost, completion_cost) == (0.0, 0.0)
def test_cost_zero_when_price_unset(self):
# Model registered without input_cost_per_token -> no billing.
prompt_cost, completion_cost = self.config.calculate_rerank_cost(
model="qwen3-rerank",
billed_units=RerankBilledUnits(total_tokens=1000),
model_info={},
)
assert (prompt_cost, completion_cost) == (0.0, 0.0)
class TestDashScopeRerankPriceMap:
def _load_local_cost_map(self, monkeypatch):
# setattr auto-restores litellm.model_cost after the test, so mutating
# the global map here can't leak into other tests.
monkeypatch.setenv("LITELLM_LOCAL_MODEL_COST_MAP", "True")
monkeypatch.setattr(litellm, "model_cost", litellm.get_model_cost_map(url=""))
def test_qwen3_rerank_price_registered(self, monkeypatch):
# Pin the built-in price entry so cost tracking works without per-model
# config: $0.1 / 1M tokens == 1e-07 per token.
self._load_local_cost_map(monkeypatch)
info = litellm.get_model_info(
model="qwen3-rerank", custom_llm_provider="dashscope"
)
assert info["input_cost_per_token"] == 1e-07
assert info["mode"] == "rerank"
assert info["litellm_provider"] == "dashscope"
def test_qwen3_rerank_billed_end_to_end(self, monkeypatch):
# Full production cost path: rerank_cost -> get_model_info ->
# DashScopeRerankConfig.calculate_rerank_cost. Guards the seam the
# direct unit tests skip.
self._load_local_cost_map(monkeypatch)
prompt_cost, completion_cost = rerank_cost(
model="qwen3-rerank",
custom_llm_provider="dashscope",
billed_units=RerankBilledUnits(total_tokens=1000),
)
assert abs(prompt_cost - 1e-04) < 1e-12 # 1000 * 1e-07
assert completion_cost == 0.0
class TestProviderConfigManagerDispatch:
def test_dashscope_returns_rerank_config(self):
import litellm