mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-13 23:11:40 +00:00
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:
commit
e907e5ee9b
7 changed files with 138 additions and 13 deletions
|
|
@ -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":
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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.",
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue