fix(dashscope): derive chat from compatible-api bases, keep the caller's rerank base, and let explicit bases beat per-endpoint env vars

This commit is contained in:
mateo-berri 2026-10-28 15:21:01 -07:00
parent 5e83b0a363
commit abb6b5f8b3
8 changed files with 54 additions and 28 deletions

View file

@ -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":

View file

@ -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,

View file

@ -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")

View file

@ -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")

View file

@ -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,

View file

@ -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):

View file

@ -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")

View file

@ -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",