From abb6b5f8b3191db47171614dff2ef7a1f32bc7ea Mon Sep 17 00:00:00 2001 From: mateo-berri <277851410+mateo-berri@users.noreply.github.com> Date: Wed, 28 Oct 2026 15:21:01 -0700 Subject: [PATCH] fix(dashscope): derive chat from compatible-api bases, keep the caller's rerank base, and let explicit bases beat per-endpoint env vars --- litellm/llms/dashscope/common_utils.py | 28 ++++++++----------- .../image_generation/transformation.py | 14 ++++++---- litellm/llms/dashscope/qwen_ai_platform.py | 7 +++-- litellm/llms/dashscope/qwencloud.py | 7 +++-- litellm/rerank_api/main.py | 12 +++++++- .../dashscope/test_dashscope_common_utils.py | 3 ++ .../test_dashscope_rerank_transformation.py | 4 ++- tests/unit/test_dashscope_image_generation.py | 7 +++++ 8 files changed, 54 insertions(+), 28 deletions(-) diff --git a/litellm/llms/dashscope/common_utils.py b/litellm/llms/dashscope/common_utils.py index c0865dfe4c6..675d565df64 100644 --- a/litellm/llms/dashscope/common_utils.py +++ b/litellm/llms/dashscope/common_utils.py @@ -96,19 +96,12 @@ def resolve_dashscope_endpoints(api_base: str | None) -> DashScopeEndpoints | No ) -def _is_compatible_api_base(api_base: str) -> bool: - path: Final = urlparse(api_base).path.rstrip("/") - return path == DASHSCOPE_COMPATIBLE_API_PATH or path.startswith(f"{DASHSCOPE_COMPATIBLE_API_PATH}/") - - def resolve_dashscope_openai_api_base( api_base: str | None, env_var: str = "DASHSCOPE_API_BASE", default: str = f"{DASHSCOPE_DEFAULT_ORIGIN}{DASHSCOPE_CHAT_COMPATIBLE_PATH}", ) -> str: candidate: Final = api_base or get_secret_str(env_var) - if candidate and _is_compatible_api_base(candidate): - return candidate endpoints: Final = resolve_dashscope_endpoints(candidate) if endpoints is not None: return endpoints.openai @@ -148,20 +141,21 @@ def _chat_shaped_aliyun_origin(api_base: str | None) -> str | None: return f"{parsed.scheme}://{parsed.netloc}" -def _rerank_origin(api_base: str | None) -> str | None: - endpoints: Final = resolve_dashscope_endpoints(api_base) - return endpoints.origin if endpoints is not None else _chat_shaped_aliyun_origin(api_base) +def _is_compatible_api_base(api_base: str) -> bool: + path: Final = urlparse(api_base).path.rstrip("/") + return path == DASHSCOPE_COMPATIBLE_API_PATH or path.startswith(f"{DASHSCOPE_COMPATIBLE_API_PATH}/") def resolve_dashscope_family_rerank_api_base(api_base: str | None, env_var: str, default_rerank_base: str) -> str: - if api_base is not None and _is_compatible_api_base(api_base): + if api_base is None: + return get_secret_str(env_var) or default_rerank_base + if _is_compatible_api_base(api_base): return api_base - origin: Final = _rerank_origin(api_base) - if origin is not None: - return get_secret_str(env_var) or f"{origin}{DASHSCOPE_RERANK_PATH}" - if api_base is not None: - return api_base - return get_secret_str(env_var) or default_rerank_base + chat_origin: Final = _chat_shaped_aliyun_origin(api_base) + if chat_origin is not None: + return get_secret_str(env_var) or f"{chat_origin}{DASHSCOPE_RERANK_PATH}" + endpoints: Final = resolve_dashscope_endpoints(api_base) + return f"{endpoints.origin}{DASHSCOPE_RERANK_PATH}" if endpoints is not None else api_base def get_dashscope_family_embedding_config(custom_llm_provider: str) -> "BaseEmbeddingConfig": diff --git a/litellm/llms/dashscope/image_generation/transformation.py b/litellm/llms/dashscope/image_generation/transformation.py index 8aff266367a..f5d4da5996e 100644 --- a/litellm/llms/dashscope/image_generation/transformation.py +++ b/litellm/llms/dashscope/image_generation/transformation.py @@ -106,8 +106,11 @@ class DashScopeImageGenerationConfig(BaseImageGenerationConfig): raise ValueError("DASHSCOPE_API_KEY is not set") return resolved_api_key - def _resolve_image_api_base(self, image_api_base: str | None) -> str: - return image_api_base or get_secret_str("DASHSCOPE_API_BASE_IMAGE") or DEFAULT_API_BASE + def _image_api_base_env(self) -> str | None: + return get_secret_str("DASHSCOPE_API_BASE_IMAGE") + + def _default_image_api_base(self) -> str: + return DEFAULT_API_BASE def _configured_chat_api_base(self) -> str | None: return get_secret_str("DASHSCOPE_API_BASE") @@ -121,13 +124,14 @@ class DashScopeImageGenerationConfig(BaseImageGenerationConfig): litellm_params: dict, stream: bool | None = None, ) -> str: + image_env_api_base: Final = self._image_api_base_env() configured_api_base: Final = api_base or self._configured_chat_api_base() if configured_api_base and configured_api_base.rstrip("/").endswith(DASHSCOPE_CHAT_COMPATIBLE_PATH): - return self._resolve_image_api_base(None) + return image_env_api_base or self._default_image_api_base() endpoints: Final = resolve_dashscope_endpoints(configured_api_base) if endpoints is not None: - return f"{endpoints.origin}{DASHSCOPE_IMAGE_PATH}" - return self._resolve_image_api_base(configured_api_base) + return (None if api_base else image_env_api_base) or f"{endpoints.origin}{DASHSCOPE_IMAGE_PATH}" + return configured_api_base or image_env_api_base or self._default_image_api_base() def validate_environment( self, diff --git a/litellm/llms/dashscope/qwen_ai_platform.py b/litellm/llms/dashscope/qwen_ai_platform.py index 256e01941c0..db01c8a0a15 100644 --- a/litellm/llms/dashscope/qwen_ai_platform.py +++ b/litellm/llms/dashscope/qwen_ai_platform.py @@ -63,8 +63,11 @@ class QwenAIPlatformImageGenerationConfig(DashScopeImageGenerationConfig): def _resolve_api_key(self, api_key: str | None) -> str: return _require_qwen_ai_platform_api_key(api_key) - def _resolve_image_api_base(self, image_api_base: str | None) -> str: - return image_api_base or get_secret_str("QWEN_AI_PLATFORM_API_BASE_IMAGE") or QWEN_AI_PLATFORM_IMAGE_API_BASE + def _image_api_base_env(self) -> str | None: + return get_secret_str("QWEN_AI_PLATFORM_API_BASE_IMAGE") + + def _default_image_api_base(self) -> str: + return QWEN_AI_PLATFORM_IMAGE_API_BASE def _configured_chat_api_base(self) -> str | None: return get_secret_str("QWEN_AI_PLATFORM_API_BASE") diff --git a/litellm/llms/dashscope/qwencloud.py b/litellm/llms/dashscope/qwencloud.py index b21a2af057a..6ae42c62e42 100644 --- a/litellm/llms/dashscope/qwencloud.py +++ b/litellm/llms/dashscope/qwencloud.py @@ -63,8 +63,11 @@ class QwenCloudImageGenerationConfig(DashScopeImageGenerationConfig): def _resolve_api_key(self, api_key: str | None) -> str: return _require_qwencloud_api_key(api_key) - def _resolve_image_api_base(self, image_api_base: str | None) -> str: - return image_api_base or get_secret_str("QWENCLOUD_API_BASE_IMAGE") or QWENCLOUD_IMAGE_API_BASE + def _image_api_base_env(self) -> str | None: + return get_secret_str("QWENCLOUD_API_BASE_IMAGE") + + def _default_image_api_base(self) -> str: + return QWENCLOUD_IMAGE_API_BASE def _configured_chat_api_base(self) -> str | None: return get_secret_str("QWENCLOUD_API_BASE") diff --git a/litellm/rerank_api/main.py b/litellm/rerank_api/main.py index 65d9b89cf79..e344ce46be7 100644 --- a/litellm/rerank_api/main.py +++ b/litellm/rerank_api/main.py @@ -19,6 +19,12 @@ from litellm.types.rerank import RerankResponse from litellm.types.router import * from litellm.utils import ProviderConfigManager, client, exception_type +_PROVIDERS_RESOLVING_THEIR_OWN_RERANK_BASE: Final = ( + litellm.LlmProviders.DASHSCOPE, + litellm.LlmProviders.QWENCLOUD, + litellm.LlmProviders.QWEN_AI_PLATFORM, +) + ####### ENVIRONMENT VARIABLES ################### # Initialize any necessary instances or variables here together_rerank: Final = TogetherAIRerank() @@ -543,7 +549,11 @@ def rerank( logging_obj=litellm_logging_obj, timeout=optional_params.timeout, api_key=dynamic_api_key or optional_params.api_key, - api_base=dynamic_api_base or optional_params.api_base, + api_base=( + optional_params.api_base or dynamic_api_base + if _custom_llm_provider in _PROVIDERS_RESOLVING_THEIR_OWN_RERANK_BASE + else dynamic_api_base or optional_params.api_base + ), _is_async=_is_async, headers=headers or litellm.headers or {}, client=client, diff --git a/tests/unit/llms/dashscope/test_dashscope_common_utils.py b/tests/unit/llms/dashscope/test_dashscope_common_utils.py index ff46c970cc4..abb9b1c9329 100644 --- a/tests/unit/llms/dashscope/test_dashscope_common_utils.py +++ b/tests/unit/llms/dashscope/test_dashscope_common_utils.py @@ -81,6 +81,9 @@ def test_openai_base_derives_from_the_domain(monkeypatch: pytest.MonkeyPatch): assert resolve_dashscope_openai_api_base(WORKSPACE) == f"{WORKSPACE}/compatible-mode/v1" assert resolve_dashscope_openai_api_base(f"{WORKSPACE}/apps/anthropic") == f"{WORKSPACE}/compatible-mode/v1" + assert ( + resolve_dashscope_openai_api_base(f"{WORKSPACE}/compatible-api/v1/reranks") == f"{WORKSPACE}/compatible-mode/v1" + ) def test_openai_base_keeps_unrecognized_hosts(monkeypatch: pytest.MonkeyPatch): diff --git a/tests/unit/llms/dashscope/test_dashscope_rerank_transformation.py b/tests/unit/llms/dashscope/test_dashscope_rerank_transformation.py index f7d4b700242..f0dbdb74b68 100644 --- a/tests/unit/llms/dashscope/test_dashscope_rerank_transformation.py +++ b/tests/unit/llms/dashscope/test_dashscope_rerank_transformation.py @@ -467,9 +467,11 @@ def test_transform_rerank_response_shape_errors_do_not_echo_the_payload(): [ "https://dashscope-intl.aliyuncs.com/compatible-api/v1/reranks", "https://dashscope-intl.aliyuncs.com/compatible-api/v1", + "https://dashscope-intl.aliyuncs.com", + "https://dashscope-intl.aliyuncs.com/api/v1", ], ) -def test_rerank_sends_an_explicit_compatible_api_base_over_the_rerank_env( +def test_rerank_sends_an_explicit_base_over_the_rerank_env( monkeypatch: pytest.MonkeyPatch, provider: str, rerank_env: str, api_base: str ): monkeypatch.setenv("DASHSCOPE_API_BASE", "https://dashscope-intl.aliyuncs.com") diff --git a/tests/unit/test_dashscope_image_generation.py b/tests/unit/test_dashscope_image_generation.py index 5f99a11519f..6d3905c6574 100644 --- a/tests/unit/test_dashscope_image_generation.py +++ b/tests/unit/test_dashscope_image_generation.py @@ -500,6 +500,13 @@ def test_litellm_image_generation_dashscope_end_to_end(model: str): "https://images.example.com/generate", ), (None, "https://gateway.example.com/dashscope/images", "", "https://gateway.example.com/dashscope/images"), + (None, "https://dashscope-us.aliyuncs.com", "https://images.example.com/generate", "https://images.example.com/generate"), + ( + "https://dashscope-us.aliyuncs.com", + "", + "https://images.example.com/generate", + f"https://dashscope-us.aliyuncs.com{DASHSCOPE_IMAGE_PATH}", + ), ( None, "https://dashscope-us.aliyuncs.com",