Merge pull request #39237 from BerriAI/litellm_fix_dashscope_rerank_endpoint

fix(dashscope): remap chat-shaped api_base to the live rerank route
This commit is contained in:
Mateo Wang 2026-09-10 10:31:48 -07:00 committed by GitHub
commit e907e5ee9b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 138 additions and 13 deletions

View file

@ -2,7 +2,8 @@
Common utilities for the DashScope LLM provider.
"""
from typing import TYPE_CHECKING
from typing import TYPE_CHECKING, Final
from urllib.parse import urlparse
import httpx
@ -16,6 +17,27 @@ if TYPE_CHECKING:
)
from litellm.llms.base_llm.rerank.transformation import BaseRerankConfig
DASHSCOPE_CHAT_COMPATIBLE_PATH: Final = "/compatible-mode/v1"
DASHSCOPE_RERANK_PATH: Final = "/compatible-api/v1/reranks"
def _rerank_base_for_chat_shaped_base(api_base: str | None) -> str | None:
if api_base is None:
return None
parsed: Final = urlparse(api_base)
host: Final = parsed.hostname or ""
on_aliyun_host: Final = host == "aliyuncs.com" or host.endswith(".aliyuncs.com")
if not on_aliyun_host or parsed.path.rstrip("/") != DASHSCOPE_CHAT_COMPATIBLE_PATH:
return None
return f"{parsed.scheme}://{parsed.netloc}{DASHSCOPE_RERANK_PATH}"
def resolve_dashscope_family_rerank_api_base(api_base: str | None, env_var: str, default_rerank_base: str) -> str:
remapped: Final = _rerank_base_for_chat_shaped_base(api_base)
if api_base is not None and remapped is None:
return api_base
return get_secret_str(env_var) or remapped or default_rerank_base
def get_dashscope_family_embedding_config(custom_llm_provider: str) -> "BaseEmbeddingConfig":
if custom_llm_provider == "qwencloud":

View file

@ -3,6 +3,7 @@ from typing import Final
from litellm.secret_managers.main import get_secret_str
from .chat.transformation import DashScopeChatConfig
from .common_utils import resolve_dashscope_family_rerank_api_base
from .embed.transformation import DashScopeEmbeddingConfig
from .image_generation.transformation import DashScopeImageGenerationConfig
from .rerank.transformation import DashScopeRerankConfig
@ -51,7 +52,9 @@ class QwenAIPlatformRerankConfig(DashScopeRerankConfig):
return _require_qwen_ai_platform_api_key(api_key)
def _resolve_rerank_api_base(self, api_base: str | None) -> str:
return api_base or get_secret_str("QWEN_AI_PLATFORM_API_BASE_RERANK") or QWEN_AI_PLATFORM_RERANK_API_BASE
return resolve_dashscope_family_rerank_api_base(
api_base, "QWEN_AI_PLATFORM_API_BASE_RERANK", QWEN_AI_PLATFORM_RERANK_API_BASE
)
class QwenAIPlatformImageGenerationConfig(DashScopeImageGenerationConfig):

View file

@ -3,6 +3,7 @@ from typing import Final
from litellm.secret_managers.main import get_secret_str
from .chat.transformation import DashScopeChatConfig
from .common_utils import resolve_dashscope_family_rerank_api_base
from .embed.transformation import DashScopeEmbeddingConfig
from .image_generation.transformation import DashScopeImageGenerationConfig
from .rerank.transformation import DashScopeRerankConfig
@ -51,7 +52,9 @@ class QwenCloudRerankConfig(DashScopeRerankConfig):
return _require_qwencloud_api_key(api_key)
def _resolve_rerank_api_base(self, api_base: str | None) -> str:
return api_base or get_secret_str("QWENCLOUD_API_BASE_RERANK") or QWENCLOUD_RERANK_API_BASE
return resolve_dashscope_family_rerank_api_base(
api_base, "QWENCLOUD_API_BASE_RERANK", QWENCLOUD_RERANK_API_BASE
)
class QwenCloudImageGenerationConfig(DashScopeImageGenerationConfig):

View file

@ -12,8 +12,12 @@ Endpoint
- https://dashscope.aliyuncs.com/compatible-api/v1/reranks
Note: chat/embed live under `/compatible-mode/v1/`, but DashScope's rerank
route is exposed under `/compatible-api/v1/reranks` per the docs. Override
with `DASHSCOPE_API_BASE_RERANK` to point at a different host or path.
route is exposed under `/compatible-api/v1/reranks` per the docs. A chat-shaped
`.aliyuncs.com/compatible-mode/v1` base reaching this config (the chat default
from `get_llm_provider`, or a `DASHSCOPE_API_BASE` env var) is redirected to
the same host's rerank route, since `/compatible-mode/v1/reranks` is a dead
route on every DashScope host. Override with `DASHSCOPE_API_BASE_RERANK` to
point at a different host or path.
Empirically, qwen3-rerank accepts `return_documents=true` and echoes
`results[].document.text` back, even though the public docs list the flag
@ -40,7 +44,7 @@ from litellm.types.rerank import (
RerankTokens,
)
from ..common_utils import DashScopeError
from ..common_utils import DashScopeError, resolve_dashscope_family_rerank_api_base
DEFAULT_RERANK_URL: Final = "https://dashscope.aliyuncs.com/compatible-api/v1/reranks"
@ -67,9 +71,7 @@ class DashScopeRerankConfig(BaseRerankConfig):
return resolved_api_key
def _resolve_rerank_api_base(self, api_base: str | None) -> str:
if api_base is not None:
return api_base
return get_secret_str("DASHSCOPE_API_BASE_RERANK") or DEFAULT_RERANK_URL
return resolve_dashscope_family_rerank_api_base(api_base, "DASHSCOPE_API_BASE_RERANK", DEFAULT_RERANK_URL)
def get_complete_url(
self,

View file

@ -25,19 +25,43 @@ class TestDashScopeRerankURL:
url = self.config.get_complete_url(api_base=None, model="qwen3-rerank")
assert url == DEFAULT_RERANK_URL
def test_explicit_v1_base_appends_reranks(self):
def test_chat_shaped_base_remaps_to_rerank_route(self):
url = self.config.get_complete_url(
api_base="https://dashscope.aliyuncs.com/compatible-mode/v1",
model="qwen3-rerank",
)
assert url == "https://dashscope.aliyuncs.com/compatible-mode/v1/reranks"
assert url == "https://dashscope.aliyuncs.com/compatible-api/v1/reranks"
def test_intl_v1_base_appends_reranks(self):
def test_intl_chat_shaped_base_remaps_to_intl_rerank_route(self):
url = self.config.get_complete_url(
api_base="https://dashscope-intl.aliyuncs.com/compatible-mode/v1",
model="qwen3-rerank",
)
assert url == "https://dashscope-intl.aliyuncs.com/compatible-mode/v1/reranks"
assert url == "https://dashscope-intl.aliyuncs.com/compatible-api/v1/reranks"
def test_chat_shaped_base_with_trailing_slash_remaps(self):
url = self.config.get_complete_url(
api_base="https://dashscope-intl.aliyuncs.com/compatible-mode/v1/",
model="qwen3-rerank",
)
assert url == "https://dashscope-intl.aliyuncs.com/compatible-api/v1/reranks"
def test_rerank_env_var_wins_over_chat_shaped_base(self, monkeypatch):
monkeypatch.setenv(
"DASHSCOPE_API_BASE_RERANK", "https://rerank.example.com/v1/reranks"
)
url = self.config.get_complete_url(
api_base="https://dashscope-intl.aliyuncs.com/compatible-mode/v1",
model="qwen3-rerank",
)
assert url == "https://rerank.example.com/v1/reranks"
def test_non_aliyun_chat_path_base_not_remapped(self):
url = self.config.get_complete_url(
api_base="https://gateway.example.com/compatible-mode/v1",
model="qwen3-rerank",
)
assert url == "https://gateway.example.com/compatible-mode/v1/reranks"
def test_already_complete_url_passthrough(self):
full = "https://dashscope.aliyuncs.com/compatible-api/v1/reranks"

View file

@ -209,6 +209,11 @@ class TestQwenBrandDefaultUrls:
url = brand["rerank_config"]().get_complete_url(api_base=None, model="gte-rerank-v2")
assert url == "https://rerank.example.com/v1/reranks"
@pytest.mark.parametrize("brand", BRAND_CASES)
def test_rerank_remaps_chat_shaped_default_base(self, brand):
url = brand["rerank_config"]().get_complete_url(api_base=brand["default_base"], model="gte-rerank-v2")
assert url == brand["default_rerank_base"]
@pytest.mark.parametrize("brand", BRAND_CASES)
def test_image_generation_complete_url(self, brand):
url = brand["image_config"]().get_complete_url(

View file

@ -111,6 +111,72 @@ def test_together_rerank_honors_api_base(respx_mock: respx.MockRouter):
assert mock_route.calls[0].request.headers["authorization"] == "Bearer fake-together-key"
DASHSCOPE_RERANK_BODY = {
"object": "list",
"results": [{"index": 0, "relevance_score": 0.95}],
"model": "qwen3-rerank",
"id": "rerank-mock-id",
"usage": {"total_tokens": 10},
}
def test_dashscope_rerank_defaults_to_live_rerank_route(respx_mock: respx.MockRouter, monkeypatch):
"""Regression for the dead default endpoint: get_llm_provider always returns the
chat base for dashscope, which used to hijack rerank onto the dead
/compatible-mode/v1/reranks route."""
monkeypatch.delenv("DASHSCOPE_API_BASE", raising=False)
monkeypatch.delenv("DASHSCOPE_API_BASE_RERANK", raising=False)
mock_route = respx_mock.post("https://dashscope.aliyuncs.com/compatible-api/v1/reranks")
mock_route.return_value = httpx.Response(200, json=DASHSCOPE_RERANK_BODY)
response = litellm.rerank(
model="dashscope/qwen3-rerank",
query=MARKER_QUERY,
documents=[MARKER_DOC],
api_key="fake-dashscope-key",
)
assert mock_route.called
assert response.results[0]["relevance_score"] == 0.95
def test_dashscope_rerank_chat_env_base_keeps_host_and_rerank_route(respx_mock: respx.MockRouter, monkeypatch):
"""Regression: a chat-style DASHSCOPE_API_BASE must not hijack rerank onto the
chat path, while its host (the region) is preserved."""
monkeypatch.setenv("DASHSCOPE_API_BASE", "https://dashscope-intl.aliyuncs.com/compatible-mode/v1")
monkeypatch.delenv("DASHSCOPE_API_BASE_RERANK", raising=False)
mock_route = respx_mock.post("https://dashscope-intl.aliyuncs.com/compatible-api/v1/reranks")
mock_route.return_value = httpx.Response(200, json=DASHSCOPE_RERANK_BODY)
litellm.rerank(
model="dashscope/qwen3-rerank",
query=MARKER_QUERY,
documents=[MARKER_DOC],
api_key="fake-dashscope-key",
)
assert mock_route.called
def test_dashscope_rerank_explicit_api_base_wins(respx_mock: respx.MockRouter, monkeypatch):
monkeypatch.setenv("DASHSCOPE_API_BASE", "https://dashscope-intl.aliyuncs.com/compatible-mode/v1")
mock_route = respx_mock.post("https://custom-rerank.example/v1/reranks")
mock_route.return_value = httpx.Response(200, json=DASHSCOPE_RERANK_BODY)
litellm.rerank(
model="dashscope/qwen3-rerank",
query=MARKER_QUERY,
documents=[MARKER_DOC],
api_key="fake-dashscope-key",
api_base="https://custom-rerank.example/v1",
)
assert mock_route.called
DASHSCOPE_404_BODY = {
"error": {
"message": "The model `does-not-exist` does not exist or you do not have access to it.",