mirror of
https://github.com/BerriAI/litellm.git
synced 2026-11-05 07:37:50 +00:00
fix(dashscope): keep configured image and rerank bases, normalize blank keys, derive brand chat bases
- image calls fall back to the provider's own chat base env (DASHSCOPE_API_BASE, QWENCLOUD_API_BASE, QWEN_AI_PLATFORM_API_BASE) before the image default - an explicit /compatible-api/v1/reranks URL wins over DASHSCOPE_API_BASE_RERANK again - a blank DashScope key counts as unset on /v1/messages, so it never falls back to ANTHROPIC_API_KEY - QwenCloud and Qianwen AI Platform chat and embeddings derive their compatible-mode base from a domain root - tests annotate new variables with Final and drop the config-class assertion
This commit is contained in:
parent
22a8358a43
commit
be9f9afecb
13 changed files with 127 additions and 56 deletions
|
|
@ -146,7 +146,13 @@ def _rerank_origin(api_base: str | None) -> str | None:
|
|||
return endpoints.origin if endpoints is not None else _chat_shaped_aliyun_origin(api_base)
|
||||
|
||||
|
||||
def _is_rerank_url(api_base: str) -> bool:
|
||||
return urlparse(api_base).path.rstrip("/") == DASHSCOPE_RERANK_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_rerank_url(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}"
|
||||
|
|
|
|||
|
|
@ -109,6 +109,9 @@ class DashScopeImageGenerationConfig(BaseImageGenerationConfig):
|
|||
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 _configured_chat_api_base(self) -> str | None:
|
||||
return get_secret_str("DASHSCOPE_API_BASE")
|
||||
|
||||
def get_complete_url(
|
||||
self,
|
||||
api_base: str | None,
|
||||
|
|
@ -118,12 +121,13 @@ class DashScopeImageGenerationConfig(BaseImageGenerationConfig):
|
|||
litellm_params: dict,
|
||||
stream: bool | None = None,
|
||||
) -> str:
|
||||
if api_base and api_base.rstrip("/").endswith(DASHSCOPE_CHAT_COMPATIBLE_PATH):
|
||||
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)
|
||||
endpoints: Final = resolve_dashscope_endpoints(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(api_base)
|
||||
return self._resolve_image_api_base(configured_api_base)
|
||||
|
||||
def validate_environment(
|
||||
self,
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ from litellm.litellm_core_utils.url_utils import ensure_api_route
|
|||
from litellm.llms.anthropic.messages.native.transformation import (
|
||||
AnthropicMessagesConfig,
|
||||
)
|
||||
from litellm.secret_managers.main import get_secret_str
|
||||
from litellm.secret_managers.main import get_secret_str, normalize_nonempty_secret_str
|
||||
|
||||
from ..common_utils import (
|
||||
dashscope_base_selects_anthropic_messages,
|
||||
|
|
@ -42,7 +42,11 @@ class DashScopeAnthropicMessagesConfig(AnthropicMessagesConfig):
|
|||
|
||||
@staticmethod
|
||||
def get_api_key(api_key: str | None = None) -> str | None:
|
||||
return api_key or get_secret_str("DASHSCOPE_API_KEY") or litellm.api_key
|
||||
return (
|
||||
normalize_nonempty_secret_str(api_key)
|
||||
or normalize_nonempty_secret_str(get_secret_str("DASHSCOPE_API_KEY"))
|
||||
or normalize_nonempty_secret_str(litellm.api_key)
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def get_api_base(api_base: str | None = None) -> str:
|
||||
|
|
|
|||
|
|
@ -3,7 +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 .common_utils import resolve_dashscope_family_rerank_api_base, resolve_dashscope_openai_api_base
|
||||
from .embed.transformation import DashScopeEmbeddingConfig
|
||||
from .image_generation.transformation import DashScopeImageGenerationConfig
|
||||
from .rerank.transformation import DashScopeRerankConfig
|
||||
|
|
@ -38,7 +38,7 @@ class QwenAIPlatformChatConfig(DashScopeChatConfig):
|
|||
return self._resolve_chat_api_base(api_base), _resolve_qwen_ai_platform_api_key(api_key)
|
||||
|
||||
def _resolve_chat_api_base(self, api_base: str | None) -> str:
|
||||
return api_base or get_secret_str("QWEN_AI_PLATFORM_API_BASE") or QWEN_AI_PLATFORM_API_BASE
|
||||
return resolve_dashscope_openai_api_base(api_base, "QWEN_AI_PLATFORM_API_BASE", QWEN_AI_PLATFORM_API_BASE)
|
||||
|
||||
|
||||
class QwenAIPlatformEmbeddingConfig(DashScopeEmbeddingConfig):
|
||||
|
|
@ -46,7 +46,7 @@ class QwenAIPlatformEmbeddingConfig(DashScopeEmbeddingConfig):
|
|||
return _require_qwen_ai_platform_api_key(api_key)
|
||||
|
||||
def _resolve_embedding_api_base(self, api_base: str | None) -> str:
|
||||
return api_base or get_secret_str("QWEN_AI_PLATFORM_API_BASE") or QWEN_AI_PLATFORM_API_BASE
|
||||
return resolve_dashscope_openai_api_base(api_base, "QWEN_AI_PLATFORM_API_BASE", QWEN_AI_PLATFORM_API_BASE)
|
||||
|
||||
|
||||
class QwenAIPlatformRerankConfig(DashScopeRerankConfig):
|
||||
|
|
@ -66,6 +66,9 @@ class QwenAIPlatformImageGenerationConfig(DashScopeImageGenerationConfig):
|
|||
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 _configured_chat_api_base(self) -> str | None:
|
||||
return get_secret_str("QWEN_AI_PLATFORM_API_BASE")
|
||||
|
||||
|
||||
class QwenAIPlatformVideoConfig(DashScopeVideoConfig):
|
||||
def _resolve_api_key(self, api_key: str | None) -> str:
|
||||
|
|
|
|||
|
|
@ -3,7 +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 .common_utils import resolve_dashscope_family_rerank_api_base, resolve_dashscope_openai_api_base
|
||||
from .embed.transformation import DashScopeEmbeddingConfig
|
||||
from .image_generation.transformation import DashScopeImageGenerationConfig
|
||||
from .rerank.transformation import DashScopeRerankConfig
|
||||
|
|
@ -38,7 +38,7 @@ class QwenCloudChatConfig(DashScopeChatConfig):
|
|||
return self._resolve_chat_api_base(api_base), _resolve_qwencloud_api_key(api_key)
|
||||
|
||||
def _resolve_chat_api_base(self, api_base: str | None) -> str:
|
||||
return api_base or get_secret_str("QWENCLOUD_API_BASE") or QWENCLOUD_API_BASE
|
||||
return resolve_dashscope_openai_api_base(api_base, "QWENCLOUD_API_BASE", QWENCLOUD_API_BASE)
|
||||
|
||||
|
||||
class QwenCloudEmbeddingConfig(DashScopeEmbeddingConfig):
|
||||
|
|
@ -46,7 +46,7 @@ class QwenCloudEmbeddingConfig(DashScopeEmbeddingConfig):
|
|||
return _require_qwencloud_api_key(api_key)
|
||||
|
||||
def _resolve_embedding_api_base(self, api_base: str | None) -> str:
|
||||
return api_base or get_secret_str("QWENCLOUD_API_BASE") or QWENCLOUD_API_BASE
|
||||
return resolve_dashscope_openai_api_base(api_base, "QWENCLOUD_API_BASE", QWENCLOUD_API_BASE)
|
||||
|
||||
|
||||
class QwenCloudRerankConfig(DashScopeRerankConfig):
|
||||
|
|
@ -66,6 +66,9 @@ class QwenCloudImageGenerationConfig(DashScopeImageGenerationConfig):
|
|||
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 _configured_chat_api_base(self) -> str | None:
|
||||
return get_secret_str("QWENCLOUD_API_BASE")
|
||||
|
||||
|
||||
class QwenCloudVideoConfig(DashScopeVideoConfig):
|
||||
def _resolve_api_key(self, api_key: str | None) -> str:
|
||||
|
|
|
|||
|
|
@ -5,20 +5,21 @@ These tests validate the DashScopeConfig class which extends OpenAIGPTConfig.
|
|||
DashScope is an OpenAI-compatible provider with minor customizations.
|
||||
"""
|
||||
|
||||
from typing import Final
|
||||
|
||||
import pytest
|
||||
|
||||
from litellm.types.llms.openai import AllMessageValues
|
||||
import pytest
|
||||
|
||||
import litellm
|
||||
from litellm import completion
|
||||
from litellm.llms.dashscope.chat.transformation import DashScopeChatConfig
|
||||
|
||||
WORKSPACE = "https://llm-abc123.cn-beijing.maas.aliyuncs.com"
|
||||
WORKSPACE: Final = "https://llm-abc123.cn-beijing.maas.aliyuncs.com"
|
||||
|
||||
|
||||
def test_get_complete_url_derives_the_compatible_base_from_any_standard_base():
|
||||
config = DashScopeChatConfig()
|
||||
config: Final = DashScopeChatConfig()
|
||||
|
||||
for api_base in (
|
||||
WORKSPACE,
|
||||
|
|
@ -26,7 +27,7 @@ def test_get_complete_url_derives_the_compatible_base_from_any_standard_base():
|
|||
f"{WORKSPACE}/apps/anthropic",
|
||||
f"{WORKSPACE}/api/v1",
|
||||
):
|
||||
url = config.get_complete_url(
|
||||
url: Final = config.get_complete_url(
|
||||
api_base=api_base,
|
||||
api_key="sk-test",
|
||||
model="qwen3.7-plus",
|
||||
|
|
@ -38,7 +39,7 @@ def test_get_complete_url_derives_the_compatible_base_from_any_standard_base():
|
|||
|
||||
|
||||
def test_get_complete_url_keeps_unrecognized_hosts():
|
||||
url = DashScopeChatConfig().get_complete_url(
|
||||
url: Final = DashScopeChatConfig().get_complete_url(
|
||||
api_base="https://gateway.example.com/v1",
|
||||
api_key="sk-test",
|
||||
model="qwen3.7-plus",
|
||||
|
|
|
|||
|
|
@ -2,6 +2,8 @@
|
|||
Unit tests for DashScope's protocol-specific base URL derivation.
|
||||
"""
|
||||
|
||||
from typing import Final
|
||||
|
||||
import pytest
|
||||
|
||||
from litellm.llms.dashscope.common_utils import (
|
||||
|
|
@ -12,7 +14,7 @@ from litellm.llms.dashscope.common_utils import (
|
|||
resolve_dashscope_openai_api_base,
|
||||
)
|
||||
|
||||
WORKSPACE = "https://llm-abc123.cn-beijing.maas.aliyuncs.com"
|
||||
WORKSPACE: Final = "https://llm-abc123.cn-beijing.maas.aliyuncs.com"
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
|
|
@ -29,7 +31,7 @@ WORKSPACE = "https://llm-abc123.cn-beijing.maas.aliyuncs.com"
|
|||
],
|
||||
)
|
||||
def test_workspace_base_derives_every_protocol(api_base: str):
|
||||
endpoints = resolve_dashscope_endpoints(api_base)
|
||||
endpoints: Final = resolve_dashscope_endpoints(api_base)
|
||||
|
||||
assert endpoints is not None
|
||||
assert endpoints.origin == WORKSPACE
|
||||
|
|
@ -48,7 +50,7 @@ def test_workspace_base_derives_every_protocol(api_base: str):
|
|||
)
|
||||
@pytest.mark.parametrize("path", ["", "/", "/compatible-mode/v1"])
|
||||
def test_standard_hosts_derive_every_protocol(origin: str, path: str):
|
||||
endpoints = resolve_dashscope_endpoints(f"{origin}{path}")
|
||||
endpoints: Final = resolve_dashscope_endpoints(f"{origin}{path}")
|
||||
|
||||
assert endpoints is not None
|
||||
assert endpoints.openai == f"{origin}/compatible-mode/v1"
|
||||
|
|
@ -119,7 +121,7 @@ def test_rerank_base_derives_from_the_domain(monkeypatch: pytest.MonkeyPatch):
|
|||
|
||||
|
||||
def test_rerank_base_keeps_unrecognized_hosts():
|
||||
gateway = "https://gateway.example.com/v1"
|
||||
gateway: Final = "https://gateway.example.com/v1"
|
||||
|
||||
assert (
|
||||
resolve_dashscope_family_rerank_api_base(gateway, "DASHSCOPE_API_BASE_RERANK", "https://default/reranks")
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ Unit tests for DashScope embedding transformation.
|
|||
"""
|
||||
|
||||
import json
|
||||
from typing import Final
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
import httpx
|
||||
|
|
@ -16,11 +17,11 @@ from litellm.llms.dashscope.embed.transformation import (
|
|||
)
|
||||
from litellm.types.utils import EmbeddingResponse
|
||||
|
||||
WORKSPACE = "https://llm-abc123.cn-beijing.maas.aliyuncs.com"
|
||||
WORKSPACE: Final = "https://llm-abc123.cn-beijing.maas.aliyuncs.com"
|
||||
|
||||
|
||||
def test_get_complete_url_derives_the_compatible_base_from_any_standard_base():
|
||||
config = DashScopeEmbeddingConfig()
|
||||
config: Final = DashScopeEmbeddingConfig()
|
||||
|
||||
for api_base in (
|
||||
WORKSPACE,
|
||||
|
|
@ -28,7 +29,7 @@ def test_get_complete_url_derives_the_compatible_base_from_any_standard_base():
|
|||
f"{WORKSPACE}/apps/anthropic",
|
||||
f"{WORKSPACE}/api/v1",
|
||||
):
|
||||
url = config.get_complete_url(
|
||||
url: Final = config.get_complete_url(
|
||||
api_base=api_base,
|
||||
api_key="sk-test",
|
||||
model="text-embedding-v4",
|
||||
|
|
@ -40,7 +41,7 @@ def test_get_complete_url_derives_the_compatible_base_from_any_standard_base():
|
|||
|
||||
|
||||
def test_get_complete_url_keeps_unrecognized_hosts():
|
||||
url = DashScopeEmbeddingConfig().get_complete_url(
|
||||
url: Final = DashScopeEmbeddingConfig().get_complete_url(
|
||||
api_base="https://gateway.example.com/v1",
|
||||
api_key="sk-test",
|
||||
model="text-embedding-v4",
|
||||
|
|
|
|||
|
|
@ -12,10 +12,8 @@ from litellm.exceptions import AuthenticationError
|
|||
from litellm.llms.dashscope.messages.transformation import (
|
||||
DashScopeAnthropicMessagesConfig,
|
||||
)
|
||||
from litellm.types.utils import LlmProviders
|
||||
from litellm.utils import ProviderConfigManager
|
||||
|
||||
WORKSPACE = "https://llm-abc123.cn-beijing.maas.aliyuncs.com"
|
||||
WORKSPACE: Final = "https://llm-abc123.cn-beijing.maas.aliyuncs.com"
|
||||
INTL: Final = "https://dashscope-intl.aliyuncs.com"
|
||||
CODING_PLAN: Final = "https://coding-intl.dashscope.aliyuncs.com"
|
||||
CHAT_COMPLETION: Final = {
|
||||
|
|
@ -76,9 +74,9 @@ MESSAGE: Final = {
|
|||
],
|
||||
)
|
||||
def test_get_complete_url(api_base: str | None, expected: str):
|
||||
config = DashScopeAnthropicMessagesConfig()
|
||||
config: Final = DashScopeAnthropicMessagesConfig()
|
||||
|
||||
url = config.get_complete_url(
|
||||
url: Final = config.get_complete_url(
|
||||
api_base=api_base,
|
||||
api_key="sk-test",
|
||||
model="qwen3.7-plus",
|
||||
|
|
@ -89,8 +87,13 @@ def test_get_complete_url(api_base: str | None, expected: str):
|
|||
assert url == expected
|
||||
|
||||
|
||||
def test_missing_dashscope_key_does_not_fall_back_to_anthropic(monkeypatch: pytest.MonkeyPatch):
|
||||
@pytest.mark.parametrize("dashscope_api_key", [None, " "])
|
||||
def test_missing_dashscope_key_does_not_fall_back_to_anthropic(
|
||||
monkeypatch: pytest.MonkeyPatch, dashscope_api_key: str | None
|
||||
):
|
||||
monkeypatch.delenv("DASHSCOPE_API_KEY", raising=False)
|
||||
if dashscope_api_key is not None:
|
||||
monkeypatch.setenv("DASHSCOPE_API_KEY", dashscope_api_key)
|
||||
monkeypatch.delenv("ANTHROPIC_AUTH_TOKEN", raising=False)
|
||||
monkeypatch.setenv("ANTHROPIC_API_KEY", "sk-ant-secret")
|
||||
monkeypatch.setattr(litellm, "api_key", None)
|
||||
|
|
@ -115,7 +118,7 @@ def test_missing_dashscope_key_does_not_fall_back_to_anthropic(monkeypatch: pyte
|
|||
def test_caller_auth_header_is_honored_without_a_dashscope_key(monkeypatch: pytest.MonkeyPatch):
|
||||
monkeypatch.delenv("DASHSCOPE_API_KEY", raising=False)
|
||||
monkeypatch.setattr(litellm, "api_key", None)
|
||||
headers = {"x-api-key": "caller-key"}
|
||||
headers: Final = {"x-api-key": "caller-key"}
|
||||
|
||||
resolved_headers, _ = DashScopeAnthropicMessagesConfig().validate_anthropic_messages_environment(
|
||||
headers=headers,
|
||||
|
|
@ -133,7 +136,7 @@ def test_caller_auth_header_is_honored_without_a_dashscope_key(monkeypatch: pyte
|
|||
def test_caller_authorization_header_is_honored_without_a_dashscope_key(monkeypatch: pytest.MonkeyPatch):
|
||||
monkeypatch.delenv("DASHSCOPE_API_KEY", raising=False)
|
||||
monkeypatch.setattr(litellm, "api_key", None)
|
||||
headers = {"Authorization": "Bearer caller-token"}
|
||||
headers: Final = {"Authorization": "Bearer caller-token"}
|
||||
|
||||
resolved_headers, _ = DashScopeAnthropicMessagesConfig().validate_anthropic_messages_environment(
|
||||
headers=headers,
|
||||
|
|
@ -162,21 +165,11 @@ def test_dashscope_key_wins_over_an_anthropic_environment_key(monkeypatch: pytes
|
|||
api_base=WORKSPACE,
|
||||
)
|
||||
|
||||
header_values = " ".join(resolved_headers.values())
|
||||
header_values: Final = " ".join(resolved_headers.values())
|
||||
assert "sk-dashscope" in header_values
|
||||
assert "sk-ant-secret" not in header_values
|
||||
|
||||
|
||||
def test_dashscope_resolves_to_the_native_messages_config():
|
||||
config = ProviderConfigManager.get_provider_anthropic_messages_config(
|
||||
model="qwen3.7-plus",
|
||||
provider=LlmProviders.DASHSCOPE,
|
||||
)
|
||||
|
||||
assert isinstance(config, DashScopeAnthropicMessagesConfig)
|
||||
assert config.custom_llm_provider == "dashscope"
|
||||
|
||||
|
||||
def test_dashscope_prefix_reaches_the_provider():
|
||||
model, provider, _, _ = litellm.get_llm_provider(
|
||||
"dashscope/qwen3.7-plus",
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ Unit tests for DashScope rerank transformation.
|
|||
"""
|
||||
|
||||
import json
|
||||
from typing import Final
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
import httpx
|
||||
|
|
@ -27,12 +28,20 @@ class TestDashScopeRerankURL:
|
|||
assert url == DEFAULT_RERANK_URL
|
||||
|
||||
def test_domain_root_remaps_to_rerank_route(self):
|
||||
url = self.config.get_complete_url(
|
||||
url: Final = self.config.get_complete_url(
|
||||
api_base="https://llm-abc123.cn-beijing.maas.aliyuncs.com",
|
||||
model="qwen3-rerank",
|
||||
)
|
||||
assert url == "https://llm-abc123.cn-beijing.maas.aliyuncs.com/compatible-api/v1/reranks"
|
||||
|
||||
def test_explicit_rerank_url_wins_over_the_rerank_env(self, monkeypatch: pytest.MonkeyPatch):
|
||||
monkeypatch.setenv("DASHSCOPE_API_BASE_RERANK", DEFAULT_RERANK_URL)
|
||||
explicit_url: Final = "https://dashscope-intl.aliyuncs.com/compatible-api/v1/reranks"
|
||||
|
||||
url: Final = self.config.get_complete_url(api_base=explicit_url, model="qwen3-rerank")
|
||||
|
||||
assert url == explicit_url
|
||||
|
||||
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",
|
||||
|
|
|
|||
|
|
@ -1120,14 +1120,14 @@ class TestDashScopeVideoEnvironment:
|
|||
assert url == "https://ws-123.cn-beijing.maas.aliyuncs.com"
|
||||
|
||||
def test_every_protocol_base_normalizes_to_the_host(self):
|
||||
workspace = "https://ws-123.cn-beijing.maas.aliyuncs.com"
|
||||
workspace: Final = "https://ws-123.cn-beijing.maas.aliyuncs.com"
|
||||
|
||||
for api_base in (
|
||||
f"{workspace}/compatible-mode/v1",
|
||||
f"{workspace}/apps/anthropic",
|
||||
f"{workspace}/api/v1",
|
||||
):
|
||||
url = DashScopeVideoConfig().get_complete_url(
|
||||
url: Final = DashScopeVideoConfig().get_complete_url(
|
||||
model="wan3.0-video", api_base=api_base, litellm_params={}
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import json
|
|||
import math
|
||||
import re
|
||||
from pathlib import Path
|
||||
from typing import Final
|
||||
|
||||
import pytest
|
||||
|
||||
|
|
@ -211,6 +212,18 @@ class TestQwenBrandDefaultUrls:
|
|||
)
|
||||
assert url == f"{brand['default_base']}/embeddings"
|
||||
|
||||
@pytest.mark.parametrize("brand", BRAND_CASES)
|
||||
@pytest.mark.parametrize(("config_key", "route"), [("chat_config", "chat/completions"), ("embedding_config", "embeddings")])
|
||||
def test_openai_routes_derive_from_a_domain_root(self, brand, config_key, route):
|
||||
url: Final = brand[config_key]().get_complete_url(
|
||||
api_base="https://dashscope-intl.aliyuncs.com",
|
||||
api_key="sk-test",
|
||||
model="qwen-max",
|
||||
optional_params={},
|
||||
litellm_params={},
|
||||
)
|
||||
assert url == f"https://dashscope-intl.aliyuncs.com/compatible-mode/v1/{route}"
|
||||
|
||||
@pytest.mark.parametrize("brand", BRAND_CASES)
|
||||
def test_rerank_complete_url(self, brand):
|
||||
url = brand["rerank_config"]().get_complete_url(api_base=None, model="gte-rerank-v2")
|
||||
|
|
@ -249,6 +262,19 @@ class TestQwenBrandDefaultUrls:
|
|||
)
|
||||
assert url == brand["default_image_base"]
|
||||
|
||||
@pytest.mark.parametrize("brand", BRAND_CASES)
|
||||
def test_image_generation_uses_its_own_configured_base(self, monkeypatch, brand):
|
||||
monkeypatch.setenv("DASHSCOPE_API_BASE", "https://legacy.example.com/images")
|
||||
monkeypatch.setenv(brand["base_env"], "https://gateway.example.com/qwen/images")
|
||||
url: Final = brand["image_config"]().get_complete_url(
|
||||
api_base=None,
|
||||
api_key="sk-test",
|
||||
model="qwen-image",
|
||||
optional_params={},
|
||||
litellm_params={},
|
||||
)
|
||||
assert url == "https://gateway.example.com/qwen/images"
|
||||
|
||||
@pytest.mark.parametrize("brand", BRAND_CASES)
|
||||
def test_validate_environment_requires_key(self, brand):
|
||||
with pytest.raises(ValueError, match="DASHSCOPE_API_KEY"):
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ qwen-image-3.0, qwen-image-3.0-pro).
|
|||
Run in docker: pytest tests/unit/test_dashscope_image_generation.py -v
|
||||
"""
|
||||
|
||||
from typing import Final
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
import httpx
|
||||
|
|
@ -101,7 +102,7 @@ class TestDashScopeImageGenerationConfig:
|
|||
def test_get_complete_url_derives_from_the_domain(
|
||||
self, api_base: str, expected: str
|
||||
):
|
||||
url = self.cfg.get_complete_url(api_base, None, "qwen-image-3.0", {}, {})
|
||||
url: Final = self.cfg.get_complete_url(api_base, None, "qwen-image-3.0", {}, {})
|
||||
assert url == expected
|
||||
|
||||
def test_validate_environment_sets_auth_header(self):
|
||||
|
|
@ -487,30 +488,48 @@ def test_litellm_image_generation_dashscope_end_to_end(model: str):
|
|||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("api_base", "image_base_env", "expected_url"),
|
||||
("api_base", "chat_base_env", "image_base_env", "expected_url"),
|
||||
[
|
||||
(None, "", DEFAULT_API_BASE),
|
||||
("https://dashscope.aliyuncs.com", "", f"{DASHSCOPE_DEFAULT_ORIGIN}{DASHSCOPE_IMAGE_PATH}"),
|
||||
(None, "https://images.example.com/generate", "https://images.example.com/generate"),
|
||||
(None, "", "", DEFAULT_API_BASE),
|
||||
("https://dashscope.aliyuncs.com", "", "", f"{DASHSCOPE_DEFAULT_ORIGIN}{DASHSCOPE_IMAGE_PATH}"),
|
||||
(None, "", "https://images.example.com/generate", "https://images.example.com/generate"),
|
||||
(
|
||||
"https://dashscope.aliyuncs.com/compatible-mode/v1",
|
||||
"",
|
||||
"https://images.example.com/generate",
|
||||
"https://images.example.com/generate",
|
||||
),
|
||||
(None, "https://gateway.example.com/dashscope/images", "", "https://gateway.example.com/dashscope/images"),
|
||||
(
|
||||
None,
|
||||
"https://dashscope-us.aliyuncs.com",
|
||||
"",
|
||||
f"https://dashscope-us.aliyuncs.com{DASHSCOPE_IMAGE_PATH}",
|
||||
),
|
||||
(
|
||||
"https://dashscope-intl.aliyuncs.com",
|
||||
"https://gateway.example.com/dashscope/images",
|
||||
"",
|
||||
f"https://dashscope-intl.aliyuncs.com{DASHSCOPE_IMAGE_PATH}",
|
||||
),
|
||||
],
|
||||
)
|
||||
def test_image_generation_resolves_from_the_callers_base_not_the_chat_default(
|
||||
monkeypatch: pytest.MonkeyPatch, api_base: str | None, image_base_env: str, expected_url: str
|
||||
def test_image_generation_resolves_from_the_configured_base_not_the_chat_default(
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
api_base: str | None,
|
||||
chat_base_env: str,
|
||||
image_base_env: str,
|
||||
expected_url: str,
|
||||
):
|
||||
monkeypatch.delenv("DASHSCOPE_API_BASE", raising=False)
|
||||
monkeypatch.setenv("DASHSCOPE_API_BASE", chat_base_env)
|
||||
monkeypatch.setenv("DASHSCOPE_API_BASE_IMAGE", image_base_env)
|
||||
image_url = "https://dashscope-result.oss.aliyuncs.com/t.png"
|
||||
image_url: Final = "https://dashscope-result.oss.aliyuncs.com/t.png"
|
||||
|
||||
with respx.mock(assert_all_called=True) as upstream:
|
||||
upstream.post(expected_url).respond(
|
||||
200, json={"output": {"choices": [{"message": {"content": [{"image": image_url}]}}]}}
|
||||
)
|
||||
response = litellm.image_generation(
|
||||
response: Final = litellm.image_generation(
|
||||
model="dashscope/qwen-image-3.0", prompt="a puppy", api_key="sk-test-key", api_base=api_base
|
||||
)
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue