mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-05 08:07:05 +00:00
Merge pull request #39149 from BerriAI/litellm_qwencloud_provider_aliases
feat(dashscope): add QwenCloud and Qwen AI Platform provider aliases
This commit is contained in:
commit
435433fa07
25 changed files with 4644 additions and 66 deletions
|
|
@ -354,6 +354,8 @@ curl -X POST 'http://0.0.0.0:4000/v1/chat/completions' \
|
|||
| [Petals (`petals`)](https://docs.litellm.ai/docs/providers/petals) | ✅ | ✅ | ✅ | | | | | | | |
|
||||
| [Pinstripes (`pinstripes`)](https://docs.litellm.ai/docs/providers/pinstripes) | ✅ | ✅ | ✅ | | | | | | | |
|
||||
| [Predibase (`predibase`)](https://docs.litellm.ai/docs/providers/predibase) | ✅ | ✅ | ✅ | | | | | | | |
|
||||
| [Qwen AI Platform (`qwen_ai_platform`)](https://docs.litellm.ai/docs/providers/qwencloud) | ✅ | ✅ | ✅ | ✅ | ✅ | | | | | ✅ |
|
||||
| [QwenCloud (`qwencloud`)](https://docs.litellm.ai/docs/providers/qwencloud) | ✅ | ✅ | ✅ | ✅ | ✅ | | | | | ✅ |
|
||||
| [Recraft (`recraft`)](https://docs.litellm.ai/docs/providers/recraft) | | | | | ✅ | | | | | |
|
||||
| [Replicate (`replicate`)](https://docs.litellm.ai/docs/providers/replicate) | ✅ | ✅ | ✅ | | | | | | | |
|
||||
| [Sagemaker Chat (`sagemaker_chat`)](https://docs.litellm.ai/docs/providers/aws_sagemaker) | ✅ | ✅ | ✅ | | | | | | | |
|
||||
|
|
|
|||
|
|
@ -659,6 +659,8 @@ aiml_models: Set = set()
|
|||
deepgram_models: Set = set()
|
||||
elevenlabs_models: Set = set()
|
||||
dashscope_models: Set = set()
|
||||
qwencloud_models: Set = set()
|
||||
qwen_ai_platform_models: Set = set()
|
||||
moonshot_models: Set = set()
|
||||
publicai_models: Set = set()
|
||||
darkbloom_models: Set = set()
|
||||
|
|
@ -909,6 +911,10 @@ def _populate_provider_model_sets(model_cost_map: Dict) -> None:
|
|||
heroku_models.add(key)
|
||||
elif value.get("litellm_provider") == "dashscope":
|
||||
dashscope_models.add(key)
|
||||
elif value.get("litellm_provider") == "qwencloud":
|
||||
qwencloud_models.add(key)
|
||||
elif value.get("litellm_provider") == "qwen_ai_platform":
|
||||
qwen_ai_platform_models.add(key)
|
||||
elif value.get("litellm_provider") == "modelscope":
|
||||
modelscope_models.add(key)
|
||||
elif value.get("litellm_provider") == "moonshot":
|
||||
|
|
@ -1072,6 +1078,8 @@ model_list = list(
|
|||
| deepgram_models
|
||||
| elevenlabs_models
|
||||
| dashscope_models
|
||||
| qwencloud_models
|
||||
| qwen_ai_platform_models
|
||||
| moonshot_models
|
||||
| publicai_models
|
||||
| darkbloom_models
|
||||
|
|
@ -1178,6 +1186,8 @@ def _build_models_by_provider() -> dict:
|
|||
"elevenlabs": elevenlabs_models,
|
||||
"heroku": heroku_models,
|
||||
"dashscope": dashscope_models,
|
||||
"qwencloud": qwencloud_models,
|
||||
"qwen_ai_platform": qwen_ai_platform_models,
|
||||
"modelscope": modelscope_models,
|
||||
"moonshot": moonshot_models,
|
||||
"publicai": publicai_models,
|
||||
|
|
@ -2014,6 +2024,24 @@ if TYPE_CHECKING:
|
|||
from .llms.dashscope.rerank.transformation import (
|
||||
DashScopeRerankConfig as DashScopeRerankConfig,
|
||||
)
|
||||
from .llms.dashscope.qwencloud import (
|
||||
QwenCloudChatConfig as QwenCloudChatConfig,
|
||||
)
|
||||
from .llms.dashscope.qwencloud import (
|
||||
QwenCloudEmbeddingConfig as QwenCloudEmbeddingConfig,
|
||||
)
|
||||
from .llms.dashscope.qwencloud import (
|
||||
QwenCloudRerankConfig as QwenCloudRerankConfig,
|
||||
)
|
||||
from .llms.dashscope.qwen_ai_platform import (
|
||||
QwenAIPlatformChatConfig as QwenAIPlatformChatConfig,
|
||||
)
|
||||
from .llms.dashscope.qwen_ai_platform import (
|
||||
QwenAIPlatformEmbeddingConfig as QwenAIPlatformEmbeddingConfig,
|
||||
)
|
||||
from .llms.dashscope.qwen_ai_platform import (
|
||||
QwenAIPlatformRerankConfig as QwenAIPlatformRerankConfig,
|
||||
)
|
||||
from .llms.modelscope.chat.transformation import (
|
||||
ModelScopeChatConfig as ModelScopeChatConfig,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -310,6 +310,8 @@ LLM_CONFIG_NAMES: Final = (
|
|||
"GigaChatConfig",
|
||||
"GigaChatEmbeddingConfig",
|
||||
"DashScopeChatConfig",
|
||||
"QwenCloudChatConfig",
|
||||
"QwenAIPlatformChatConfig",
|
||||
"ModelScopeChatConfig",
|
||||
"MoonshotChatConfig",
|
||||
"DockerModelRunnerChatConfig",
|
||||
|
|
@ -1172,6 +1174,14 @@ _LLM_CONFIGS_IMPORT_MAP: Final = {
|
|||
".llms.dashscope.chat.transformation",
|
||||
"DashScopeChatConfig",
|
||||
),
|
||||
"QwenCloudChatConfig": (
|
||||
".llms.dashscope.qwencloud",
|
||||
"QwenCloudChatConfig",
|
||||
),
|
||||
"QwenAIPlatformChatConfig": (
|
||||
".llms.dashscope.qwen_ai_platform",
|
||||
"QwenAIPlatformChatConfig",
|
||||
),
|
||||
"GDCGeminiConfig": (
|
||||
".llms.gdc.chat.transformation",
|
||||
"GDCGeminiConfig",
|
||||
|
|
|
|||
|
|
@ -630,6 +630,8 @@ LITELLM_CHAT_PROVIDERS: Final = [
|
|||
"nscale",
|
||||
"nebius",
|
||||
"dashscope",
|
||||
"qwencloud",
|
||||
"qwen_ai_platform",
|
||||
"modelscope",
|
||||
"moonshot",
|
||||
"publicai",
|
||||
|
|
@ -799,6 +801,7 @@ openai_compatible_endpoints: Final[list] = [
|
|||
"inference.api.nscale.com/v1",
|
||||
"api.studio.nebius.ai/v1",
|
||||
"https://dashscope-intl.aliyuncs.com/compatible-mode/v1",
|
||||
"https://dashscope.aliyuncs.com/compatible-mode/v1",
|
||||
"https://api-inference.modelscope.cn/v1",
|
||||
"https://api.moonshot.ai/v1",
|
||||
"https://api.publicai.co/v1",
|
||||
|
|
@ -872,6 +875,8 @@ openai_compatible_providers: Final[list] = [
|
|||
"nscale",
|
||||
"nebius",
|
||||
"dashscope",
|
||||
"qwencloud",
|
||||
"qwen_ai_platform",
|
||||
"modelscope",
|
||||
"moonshot",
|
||||
"v0",
|
||||
|
|
@ -902,6 +907,8 @@ openai_text_completion_compatible_providers: Final[list] = [ # providers that s
|
|||
"featherless_ai",
|
||||
"nebius",
|
||||
"dashscope",
|
||||
"qwencloud",
|
||||
"qwen_ai_platform",
|
||||
"modelscope",
|
||||
"moonshot",
|
||||
"publicai",
|
||||
|
|
@ -1109,7 +1116,7 @@ nebius_models: Final[set] = set(
|
|||
]
|
||||
)
|
||||
|
||||
dashscope_models: Final[set] = set(
|
||||
dashscope_models: Final[frozenset] = frozenset(
|
||||
[
|
||||
"qwen-turbo",
|
||||
"qwen-plus",
|
||||
|
|
@ -1124,6 +1131,10 @@ dashscope_models: Final[set] = set(
|
|||
]
|
||||
)
|
||||
|
||||
qwencloud_models: Final[frozenset] = frozenset(dashscope_models)
|
||||
|
||||
qwen_ai_platform_models: Final[frozenset] = frozenset(dashscope_models)
|
||||
|
||||
nebius_embedding_models: Final[set] = set(
|
||||
[
|
||||
"BAAI/bge-en-icl",
|
||||
|
|
|
|||
|
|
@ -641,12 +641,12 @@ def cost_per_token(
|
|||
return xai_cost_per_token(model=model, usage=usage_block)
|
||||
elif custom_llm_provider == "lemonade":
|
||||
return lemonade_cost_per_token(model=model, usage=usage_block)
|
||||
elif custom_llm_provider == "dashscope":
|
||||
elif custom_llm_provider in ("dashscope", "qwencloud", "qwen_ai_platform"):
|
||||
from litellm.llms.dashscope.cost_calculator import (
|
||||
cost_per_token as dashscope_cost_per_token,
|
||||
)
|
||||
|
||||
return dashscope_cost_per_token(model=model, usage=usage_block)
|
||||
return dashscope_cost_per_token(model=model, usage=usage_block, custom_llm_provider=custom_llm_provider)
|
||||
elif custom_llm_provider == "azure_ai":
|
||||
return azure_ai_cost_per_token(
|
||||
model=model,
|
||||
|
|
|
|||
|
|
@ -386,6 +386,8 @@ def image_generation(
|
|||
litellm.LlmProviders.VERTEX_AI,
|
||||
litellm.LlmProviders.OPENROUTER,
|
||||
litellm.LlmProviders.DASHSCOPE,
|
||||
litellm.LlmProviders.QWENCLOUD,
|
||||
litellm.LlmProviders.QWEN_AI_PLATFORM,
|
||||
):
|
||||
if image_generation_config is None:
|
||||
raise ValueError(f"image generation config is not supported for {custom_llm_provider}")
|
||||
|
|
|
|||
|
|
@ -536,6 +536,14 @@ def get_llm_provider(
|
|||
)
|
||||
|
||||
|
||||
def _dashscope_family_chat_config(custom_llm_provider: str) -> "litellm.DashScopeChatConfig":
|
||||
if custom_llm_provider == "qwencloud":
|
||||
return litellm.QwenCloudChatConfig()
|
||||
if custom_llm_provider == "qwen_ai_platform":
|
||||
return litellm.QwenAIPlatformChatConfig()
|
||||
return litellm.DashScopeChatConfig()
|
||||
|
||||
|
||||
def _get_openai_compatible_provider_info(
|
||||
model: str,
|
||||
api_base: str | None,
|
||||
|
|
@ -785,11 +793,11 @@ def _get_openai_compatible_provider_info(
|
|||
api_base,
|
||||
dynamic_api_key,
|
||||
) = litellm.HerokuChatConfig()._get_openai_compatible_provider_info(api_base, api_key)
|
||||
elif custom_llm_provider == "dashscope":
|
||||
elif custom_llm_provider in ("dashscope", "qwencloud", "qwen_ai_platform"):
|
||||
(
|
||||
api_base,
|
||||
dynamic_api_key,
|
||||
) = litellm.DashScopeChatConfig()._get_openai_compatible_provider_info(api_base, api_key)
|
||||
) = _dashscope_family_chat_config(custom_llm_provider)._get_openai_compatible_provider_info(api_base, api_key)
|
||||
elif custom_llm_provider == "modelscope":
|
||||
(
|
||||
api_base,
|
||||
|
|
|
|||
|
|
@ -54,6 +54,9 @@ class DashScopeChatConfig(OpenAIGPTConfig):
|
|||
dynamic_api_key: Final = api_key or get_secret_str("DASHSCOPE_API_KEY")
|
||||
return api_base, dynamic_api_key
|
||||
|
||||
def _resolve_chat_api_base(self, api_base: str | None) -> str:
|
||||
return api_base or "https://dashscope.aliyuncs.com/compatible-mode/v1"
|
||||
|
||||
def get_complete_url(
|
||||
self,
|
||||
api_base: str | None,
|
||||
|
|
@ -66,10 +69,7 @@ class DashScopeChatConfig(OpenAIGPTConfig):
|
|||
"""
|
||||
If api_base is not provided, use the default DashScope /chat/completions endpoint.
|
||||
"""
|
||||
if not api_base:
|
||||
api_base = "https://dashscope.aliyuncs.com/compatible-mode/v1"
|
||||
|
||||
if not api_base.endswith("/chat/completions"):
|
||||
api_base = f"{api_base}/chat/completions"
|
||||
|
||||
return api_base
|
||||
resolved_api_base: Final = self._resolve_chat_api_base(api_base)
|
||||
if resolved_api_base.endswith("/chat/completions"):
|
||||
return resolved_api_base
|
||||
return f"{resolved_api_base}/chat/completions"
|
||||
|
|
|
|||
|
|
@ -2,9 +2,89 @@
|
|||
Common utilities for the DashScope LLM provider.
|
||||
"""
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
import httpx
|
||||
|
||||
from litellm.llms.base_llm.chat.transformation import BaseLLMException
|
||||
from litellm.secret_managers.main import get_secret_str
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from litellm.llms.base_llm.embedding.transformation import BaseEmbeddingConfig
|
||||
from litellm.llms.base_llm.image_generation.transformation import (
|
||||
BaseImageGenerationConfig,
|
||||
)
|
||||
from litellm.llms.base_llm.rerank.transformation import BaseRerankConfig
|
||||
|
||||
|
||||
def get_dashscope_family_embedding_config(custom_llm_provider: str) -> "BaseEmbeddingConfig":
|
||||
if custom_llm_provider == "qwencloud":
|
||||
from litellm.llms.dashscope.qwencloud import QwenCloudEmbeddingConfig
|
||||
|
||||
return QwenCloudEmbeddingConfig()
|
||||
if custom_llm_provider == "qwen_ai_platform":
|
||||
from litellm.llms.dashscope.qwen_ai_platform import (
|
||||
QwenAIPlatformEmbeddingConfig,
|
||||
)
|
||||
|
||||
return QwenAIPlatformEmbeddingConfig()
|
||||
from litellm.llms.dashscope.embed.transformation import DashScopeEmbeddingConfig
|
||||
|
||||
return DashScopeEmbeddingConfig()
|
||||
|
||||
|
||||
def get_dashscope_family_rerank_config(custom_llm_provider: str) -> "BaseRerankConfig":
|
||||
if custom_llm_provider == "qwencloud":
|
||||
from litellm.llms.dashscope.qwencloud import QwenCloudRerankConfig
|
||||
|
||||
return QwenCloudRerankConfig()
|
||||
if custom_llm_provider == "qwen_ai_platform":
|
||||
from litellm.llms.dashscope.qwen_ai_platform import QwenAIPlatformRerankConfig
|
||||
|
||||
return QwenAIPlatformRerankConfig()
|
||||
from litellm.llms.dashscope.rerank.transformation import DashScopeRerankConfig
|
||||
|
||||
return DashScopeRerankConfig()
|
||||
|
||||
|
||||
def get_dashscope_family_image_generation_config(
|
||||
custom_llm_provider: str,
|
||||
) -> "BaseImageGenerationConfig":
|
||||
if custom_llm_provider == "qwencloud":
|
||||
from litellm.llms.dashscope.qwencloud import QwenCloudImageGenerationConfig
|
||||
|
||||
return QwenCloudImageGenerationConfig()
|
||||
if custom_llm_provider == "qwen_ai_platform":
|
||||
from litellm.llms.dashscope.qwen_ai_platform import (
|
||||
QwenAIPlatformImageGenerationConfig,
|
||||
)
|
||||
|
||||
return QwenAIPlatformImageGenerationConfig()
|
||||
from litellm.llms.dashscope.image_generation.transformation import (
|
||||
DashScopeImageGenerationConfig,
|
||||
)
|
||||
|
||||
return DashScopeImageGenerationConfig()
|
||||
|
||||
|
||||
def resolve_dashscope_family_api_key(custom_llm_provider: str, api_key: str | None) -> str | None:
|
||||
if custom_llm_provider == "dashscope":
|
||||
return api_key or get_secret_str("DASHSCOPE_API_KEY")
|
||||
return api_key or get_secret_str(f"{custom_llm_provider.upper()}_API_KEY") or get_secret_str("DASHSCOPE_API_KEY")
|
||||
|
||||
|
||||
def missing_dashscope_family_key_message(custom_llm_provider: str) -> str:
|
||||
if custom_llm_provider == "qwencloud":
|
||||
return (
|
||||
"Missing API key for QwenCloud. Set QWENCLOUD_API_KEY or "
|
||||
"DASHSCOPE_API_KEY environment variable or pass api_key parameter."
|
||||
)
|
||||
if custom_llm_provider == "qwen_ai_platform":
|
||||
return (
|
||||
"Missing API key for Qwen AI Platform. Set QWEN_AI_PLATFORM_API_KEY or "
|
||||
"DASHSCOPE_API_KEY environment variable or pass api_key parameter."
|
||||
)
|
||||
return "Missing API key for DashScope. Set DASHSCOPE_API_KEY environment variable or pass api_key parameter."
|
||||
|
||||
|
||||
class DashScopeError(BaseLLMException):
|
||||
|
|
|
|||
|
|
@ -110,7 +110,7 @@ def _calculate_completion_cost(
|
|||
return (breakdown.completion_tokens * output_cost) + (breakdown.reasoning_tokens * reasoning_cost)
|
||||
|
||||
|
||||
def cost_per_token(model: str, usage: Usage) -> tuple[float, float]:
|
||||
def cost_per_token(model: str, usage: Usage, custom_llm_provider: str = "dashscope") -> tuple[float, float]:
|
||||
"""
|
||||
Calculate cost per token for Dashscope models.
|
||||
|
||||
|
|
@ -119,11 +119,12 @@ def cost_per_token(model: str, usage: Usage) -> tuple[float, float]:
|
|||
Args:
|
||||
model: Model name without provider prefix
|
||||
usage: LiteLLM Usage block
|
||||
custom_llm_provider: The provider id the request resolved to; dashscope or one of its brand aliases
|
||||
|
||||
Returns:
|
||||
Tuple[float, float] - (prompt_cost_in_usd, completion_cost_in_usd)
|
||||
"""
|
||||
model_info: Final = get_model_info(model=model, custom_llm_provider="dashscope")
|
||||
model_info: Final = get_model_info(model=model, custom_llm_provider=custom_llm_provider)
|
||||
breakdown: Final = _extract_token_breakdown(usage)
|
||||
raw_tiers: Final = model_info.get("tiered_pricing")
|
||||
tiered_pricing: Final = raw_tiers if isinstance(raw_tiers, list) else None
|
||||
|
|
|
|||
|
|
@ -62,6 +62,17 @@ class DashScopeEmbeddingConfig(BaseEmbeddingConfig):
|
|||
# for drop_params=False before this method is called.
|
||||
return optional_params
|
||||
|
||||
def _resolve_api_key(self, api_key: str | None) -> str:
|
||||
resolved_api_key: Final = api_key if api_key is not None else get_secret_str("DASHSCOPE_API_KEY")
|
||||
if resolved_api_key is None:
|
||||
raise ValueError(
|
||||
"DashScope API key is required. Set 'DASHSCOPE_API_KEY' env var or pass api_key explicitly."
|
||||
)
|
||||
return resolved_api_key
|
||||
|
||||
def _resolve_embedding_api_base(self, api_base: str | None) -> str:
|
||||
return api_base or get_secret_str("DASHSCOPE_API_BASE") or DEFAULT_API_BASE
|
||||
|
||||
def validate_environment(
|
||||
self,
|
||||
headers: dict,
|
||||
|
|
@ -72,17 +83,11 @@ class DashScopeEmbeddingConfig(BaseEmbeddingConfig):
|
|||
api_key: str | None = None,
|
||||
api_base: str | None = None,
|
||||
) -> dict:
|
||||
if api_key is None:
|
||||
api_key = get_secret_str("DASHSCOPE_API_KEY")
|
||||
if api_key is None:
|
||||
raise ValueError(
|
||||
"DashScope API key is required. Set 'DASHSCOPE_API_KEY' env var or pass api_key explicitly."
|
||||
)
|
||||
default_headers: Final = {
|
||||
return {
|
||||
"Content-Type": "application/json",
|
||||
"Authorization": f"Bearer {api_key}",
|
||||
"Authorization": f"Bearer {self._resolve_api_key(api_key)}",
|
||||
**headers,
|
||||
}
|
||||
return {**default_headers, **headers}
|
||||
|
||||
def get_complete_url(
|
||||
self,
|
||||
|
|
@ -93,8 +98,7 @@ class DashScopeEmbeddingConfig(BaseEmbeddingConfig):
|
|||
litellm_params: dict,
|
||||
stream: bool | None = None,
|
||||
) -> str:
|
||||
base = api_base or get_secret_str("DASHSCOPE_API_BASE") or DEFAULT_API_BASE
|
||||
base = base.rstrip("/")
|
||||
base: Final = self._resolve_embedding_api_base(api_base).rstrip("/")
|
||||
if base.endswith("/embeddings"):
|
||||
return base
|
||||
return f"{base}/embeddings"
|
||||
|
|
|
|||
|
|
@ -91,6 +91,15 @@ class DashScopeImageGenerationConfig(BaseImageGenerationConfig):
|
|||
mapped[k] = v
|
||||
return mapped
|
||||
|
||||
def _resolve_api_key(self, api_key: str | None) -> str:
|
||||
resolved_api_key: Final = api_key or get_secret_str("DASHSCOPE_API_KEY")
|
||||
if not resolved_api_key:
|
||||
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 get_complete_url(
|
||||
self,
|
||||
api_base: str | None,
|
||||
|
|
@ -103,7 +112,7 @@ class DashScopeImageGenerationConfig(BaseImageGenerationConfig):
|
|||
image_api_base: Final = (
|
||||
api_base if api_base and not api_base.rstrip("/").endswith(CHAT_COMPATIBLE_MODE_PATH) else None
|
||||
)
|
||||
return image_api_base or get_secret_str("DASHSCOPE_API_BASE_IMAGE") or DEFAULT_API_BASE
|
||||
return self._resolve_image_api_base(image_api_base)
|
||||
|
||||
def validate_environment(
|
||||
self,
|
||||
|
|
@ -115,10 +124,7 @@ class DashScopeImageGenerationConfig(BaseImageGenerationConfig):
|
|||
api_key: str | None = None,
|
||||
api_base: str | None = None,
|
||||
) -> dict:
|
||||
final_api_key: Final = api_key or get_secret_str("DASHSCOPE_API_KEY")
|
||||
if not final_api_key:
|
||||
raise ValueError("DASHSCOPE_API_KEY is not set")
|
||||
headers["Authorization"] = f"Bearer {final_api_key}"
|
||||
headers["Authorization"] = f"Bearer {self._resolve_api_key(api_key)}"
|
||||
headers["Content-Type"] = "application/json"
|
||||
return headers
|
||||
|
||||
|
|
|
|||
62
litellm/llms/dashscope/qwen_ai_platform.py
Normal file
62
litellm/llms/dashscope/qwen_ai_platform.py
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
from typing import Final
|
||||
|
||||
from litellm.secret_managers.main import get_secret_str
|
||||
|
||||
from .chat.transformation import DashScopeChatConfig
|
||||
from .embed.transformation import DashScopeEmbeddingConfig
|
||||
from .image_generation.transformation import DashScopeImageGenerationConfig
|
||||
from .rerank.transformation import DashScopeRerankConfig
|
||||
|
||||
QWEN_AI_PLATFORM_API_BASE: Final = "https://dashscope.aliyuncs.com/compatible-mode/v1"
|
||||
QWEN_AI_PLATFORM_RERANK_API_BASE: Final = "https://dashscope.aliyuncs.com/compatible-api/v1/reranks"
|
||||
QWEN_AI_PLATFORM_IMAGE_API_BASE: Final = (
|
||||
"https://dashscope.aliyuncs.com/api/v1/services/aigc/multimodal-generation/generation"
|
||||
)
|
||||
|
||||
|
||||
def _resolve_qwen_ai_platform_api_key(api_key: str | None) -> str | None:
|
||||
return api_key or get_secret_str("QWEN_AI_PLATFORM_API_KEY") or get_secret_str("DASHSCOPE_API_KEY")
|
||||
|
||||
|
||||
def _require_qwen_ai_platform_api_key(api_key: str | None) -> str:
|
||||
resolved: Final = _resolve_qwen_ai_platform_api_key(api_key)
|
||||
if resolved is None:
|
||||
raise ValueError(
|
||||
"Qwen AI Platform API key is required. Set 'QWEN_AI_PLATFORM_API_KEY' or 'DASHSCOPE_API_KEY' env var "
|
||||
"or pass api_key explicitly."
|
||||
)
|
||||
return resolved
|
||||
|
||||
|
||||
class QwenAIPlatformChatConfig(DashScopeChatConfig):
|
||||
def _get_openai_compatible_provider_info(
|
||||
self, api_base: str | None, api_key: str | None
|
||||
) -> tuple[str | None, str | None]:
|
||||
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
|
||||
|
||||
|
||||
class QwenAIPlatformEmbeddingConfig(DashScopeEmbeddingConfig):
|
||||
def _resolve_api_key(self, api_key: str | None) -> str:
|
||||
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
|
||||
|
||||
|
||||
class QwenAIPlatformRerankConfig(DashScopeRerankConfig):
|
||||
def _resolve_api_key(self, api_key: str | None) -> str:
|
||||
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
|
||||
|
||||
|
||||
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
|
||||
62
litellm/llms/dashscope/qwencloud.py
Normal file
62
litellm/llms/dashscope/qwencloud.py
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
from typing import Final
|
||||
|
||||
from litellm.secret_managers.main import get_secret_str
|
||||
|
||||
from .chat.transformation import DashScopeChatConfig
|
||||
from .embed.transformation import DashScopeEmbeddingConfig
|
||||
from .image_generation.transformation import DashScopeImageGenerationConfig
|
||||
from .rerank.transformation import DashScopeRerankConfig
|
||||
|
||||
QWENCLOUD_API_BASE: Final = "https://dashscope-intl.aliyuncs.com/compatible-mode/v1"
|
||||
QWENCLOUD_RERANK_API_BASE: Final = "https://dashscope-intl.aliyuncs.com/compatible-api/v1/reranks"
|
||||
QWENCLOUD_IMAGE_API_BASE: Final = (
|
||||
"https://dashscope-intl.aliyuncs.com/api/v1/services/aigc/multimodal-generation/generation"
|
||||
)
|
||||
|
||||
|
||||
def _resolve_qwencloud_api_key(api_key: str | None) -> str | None:
|
||||
return api_key or get_secret_str("QWENCLOUD_API_KEY") or get_secret_str("DASHSCOPE_API_KEY")
|
||||
|
||||
|
||||
def _require_qwencloud_api_key(api_key: str | None) -> str:
|
||||
resolved: Final = _resolve_qwencloud_api_key(api_key)
|
||||
if resolved is None:
|
||||
raise ValueError(
|
||||
"QwenCloud API key is required. Set 'QWENCLOUD_API_KEY' or 'DASHSCOPE_API_KEY' env var "
|
||||
"or pass api_key explicitly."
|
||||
)
|
||||
return resolved
|
||||
|
||||
|
||||
class QwenCloudChatConfig(DashScopeChatConfig):
|
||||
def _get_openai_compatible_provider_info(
|
||||
self, api_base: str | None, api_key: str | None
|
||||
) -> tuple[str | None, str | None]:
|
||||
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
|
||||
|
||||
|
||||
class QwenCloudEmbeddingConfig(DashScopeEmbeddingConfig):
|
||||
def _resolve_api_key(self, api_key: str | None) -> str:
|
||||
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
|
||||
|
||||
|
||||
class QwenCloudRerankConfig(DashScopeRerankConfig):
|
||||
def _resolve_api_key(self, api_key: str | None) -> str:
|
||||
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
|
||||
|
||||
|
||||
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
|
||||
|
|
@ -58,19 +58,30 @@ class DashScopeRerankConfig(BaseRerankConfig):
|
|||
def __init__(self) -> None:
|
||||
pass
|
||||
|
||||
def _resolve_api_key(self, api_key: str | None) -> str:
|
||||
resolved_api_key: Final = api_key if api_key is not None else get_secret_str("DASHSCOPE_API_KEY")
|
||||
if resolved_api_key is None:
|
||||
raise ValueError(
|
||||
"DashScope API key is required. Set 'DASHSCOPE_API_KEY' env var or pass api_key explicitly."
|
||||
)
|
||||
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
|
||||
|
||||
def get_complete_url(
|
||||
self,
|
||||
api_base: str | None,
|
||||
model: str,
|
||||
optional_params: dict | None = None,
|
||||
) -> str:
|
||||
if api_base is None:
|
||||
api_base = get_secret_str("DASHSCOPE_API_BASE_RERANK") or DEFAULT_RERANK_URL
|
||||
resolved_api_base: Final = self._resolve_rerank_api_base(api_base)
|
||||
if resolved_api_base == DEFAULT_RERANK_URL:
|
||||
return resolved_api_base
|
||||
|
||||
if api_base == DEFAULT_RERANK_URL:
|
||||
return DEFAULT_RERANK_URL
|
||||
|
||||
cleaned: Final = api_base.rstrip("/")
|
||||
cleaned: Final = resolved_api_base.rstrip("/")
|
||||
if cleaned.endswith("/reranks") or cleaned.endswith("/rerank"):
|
||||
return cleaned
|
||||
|
||||
|
|
@ -88,19 +99,12 @@ class DashScopeRerankConfig(BaseRerankConfig):
|
|||
optional_params: dict | None = None,
|
||||
litellm_params: Mapping[str, object] | None = None,
|
||||
) -> dict:
|
||||
if api_key is None:
|
||||
api_key = get_secret_str("DASHSCOPE_API_KEY")
|
||||
if api_key is None:
|
||||
raise ValueError(
|
||||
"DashScope API key is required. Set 'DASHSCOPE_API_KEY' env var or pass api_key explicitly."
|
||||
)
|
||||
|
||||
default_headers: Final = {
|
||||
"Authorization": f"Bearer {api_key}",
|
||||
return {
|
||||
"Authorization": f"Bearer {self._resolve_api_key(api_key)}",
|
||||
"accept": "application/json",
|
||||
"content-type": "application/json",
|
||||
**headers,
|
||||
}
|
||||
return {**default_headers, **headers}
|
||||
|
||||
def get_supported_cohere_rerank_params(self, model: str) -> list:
|
||||
return ["query", "documents", "top_n", "return_documents"]
|
||||
|
|
|
|||
|
|
@ -6949,12 +6949,18 @@ def embedding(
|
|||
aembedding=aembedding,
|
||||
headers=headers,
|
||||
)
|
||||
elif custom_llm_provider == "dashscope":
|
||||
dashscope_key: Final = api_key or litellm.api_key or get_secret_str("DASHSCOPE_API_KEY")
|
||||
elif custom_llm_provider in ("dashscope", "qwencloud", "qwen_ai_platform"):
|
||||
from litellm.llms.dashscope.common_utils import (
|
||||
missing_dashscope_family_key_message,
|
||||
resolve_dashscope_family_api_key,
|
||||
)
|
||||
|
||||
dashscope_key: Final = resolve_dashscope_family_api_key(
|
||||
custom_llm_provider=custom_llm_provider,
|
||||
api_key=api_key or litellm.api_key,
|
||||
)
|
||||
if dashscope_key is None:
|
||||
raise ValueError(
|
||||
"Missing API key for DashScope. Set DASHSCOPE_API_KEY environment variable or pass api_key parameter."
|
||||
)
|
||||
raise ValueError(missing_dashscope_family_key_message(custom_llm_provider))
|
||||
if extra_headers is not None and isinstance(extra_headers, dict):
|
||||
headers = extra_headers
|
||||
else:
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -671,6 +671,42 @@
|
|||
"interactions": true
|
||||
}
|
||||
},
|
||||
"qwencloud": {
|
||||
"display_name": "QwenCloud (`qwencloud`)",
|
||||
"url": "https://docs.litellm.ai/docs/providers/qwencloud",
|
||||
"endpoints": {
|
||||
"chat_completions": true,
|
||||
"messages": true,
|
||||
"responses": true,
|
||||
"embeddings": true,
|
||||
"image_generations": true,
|
||||
"audio_transcriptions": false,
|
||||
"audio_speech": false,
|
||||
"moderations": false,
|
||||
"batches": false,
|
||||
"rerank": true,
|
||||
"a2a": true,
|
||||
"interactions": true
|
||||
}
|
||||
},
|
||||
"qwen_ai_platform": {
|
||||
"display_name": "Qwen AI Platform (`qwen_ai_platform`)",
|
||||
"url": "https://docs.litellm.ai/docs/providers/qwencloud",
|
||||
"endpoints": {
|
||||
"chat_completions": true,
|
||||
"messages": true,
|
||||
"responses": true,
|
||||
"embeddings": true,
|
||||
"image_generations": true,
|
||||
"audio_transcriptions": false,
|
||||
"audio_speech": false,
|
||||
"moderations": false,
|
||||
"batches": false,
|
||||
"rerank": true,
|
||||
"a2a": true,
|
||||
"interactions": true
|
||||
}
|
||||
},
|
||||
"databricks": {
|
||||
"display_name": "Databricks (`databricks`)",
|
||||
"url": "https://docs.litellm.ai/docs/providers/databricks",
|
||||
|
|
|
|||
|
|
@ -986,6 +986,62 @@
|
|||
],
|
||||
"default_model_placeholder": "gpt-3.5-turbo"
|
||||
},
|
||||
{
|
||||
"provider": "QwenCloud",
|
||||
"provider_display_name": "QwenCloud",
|
||||
"litellm_provider": "qwencloud",
|
||||
"credential_fields": [
|
||||
{
|
||||
"key": "api_key",
|
||||
"label": "QwenCloud API Key",
|
||||
"placeholder": null,
|
||||
"tooltip": null,
|
||||
"required": true,
|
||||
"field_type": "password",
|
||||
"options": null,
|
||||
"default_value": null
|
||||
},
|
||||
{
|
||||
"key": "api_base",
|
||||
"label": "API Base",
|
||||
"placeholder": "https://dashscope-intl.aliyuncs.com/compatible-mode/v1",
|
||||
"tooltip": "The base URL for QwenCloud. Defaults to https://dashscope-intl.aliyuncs.com/compatible-mode/v1 if not specified.",
|
||||
"required": true,
|
||||
"field_type": "text",
|
||||
"options": null,
|
||||
"default_value": "https://dashscope-intl.aliyuncs.com/compatible-mode/v1"
|
||||
}
|
||||
],
|
||||
"default_model_placeholder": "gpt-3.5-turbo"
|
||||
},
|
||||
{
|
||||
"provider": "Qwen_AI_Platform",
|
||||
"provider_display_name": "Qwen AI Platform",
|
||||
"litellm_provider": "qwen_ai_platform",
|
||||
"credential_fields": [
|
||||
{
|
||||
"key": "api_key",
|
||||
"label": "Qwen AI Platform API Key",
|
||||
"placeholder": null,
|
||||
"tooltip": null,
|
||||
"required": true,
|
||||
"field_type": "password",
|
||||
"options": null,
|
||||
"default_value": null
|
||||
},
|
||||
{
|
||||
"key": "api_base",
|
||||
"label": "API Base",
|
||||
"placeholder": "https://dashscope.aliyuncs.com/compatible-mode/v1",
|
||||
"tooltip": "The base URL for Qwen AI Platform. Defaults to https://dashscope.aliyuncs.com/compatible-mode/v1 if not specified.",
|
||||
"required": true,
|
||||
"field_type": "text",
|
||||
"options": null,
|
||||
"default_value": "https://dashscope.aliyuncs.com/compatible-mode/v1"
|
||||
}
|
||||
],
|
||||
"default_model_placeholder": "gpt-3.5-turbo"
|
||||
},
|
||||
{
|
||||
"provider": "Databricks",
|
||||
"provider_display_name": "Databricks",
|
||||
|
|
|
|||
|
|
@ -3771,6 +3771,8 @@ class LlmProviders(str, Enum):
|
|||
CODESTRAL = "codestral"
|
||||
TEXT_COMPLETION_CODESTRAL = "text-completion-codestral"
|
||||
DASHSCOPE = "dashscope"
|
||||
QWENCLOUD = "qwencloud"
|
||||
QWEN_AI_PLATFORM = "qwen_ai_platform"
|
||||
MODELSCOPE = "modelscope"
|
||||
MOONSHOT = "moonshot"
|
||||
PUBLICAI = "publicai"
|
||||
|
|
|
|||
|
|
@ -6586,11 +6586,11 @@ def validate_environment(
|
|||
keys_in_environment = True
|
||||
else:
|
||||
missing_keys.append("WANDB_API_KEY")
|
||||
elif custom_llm_provider == "dashscope":
|
||||
if "DASHSCOPE_API_KEY" in os.environ:
|
||||
elif custom_llm_provider in ("dashscope", "qwencloud", "qwen_ai_platform"):
|
||||
if f"{custom_llm_provider.upper()}_API_KEY" in os.environ or "DASHSCOPE_API_KEY" in os.environ:
|
||||
keys_in_environment = True
|
||||
else:
|
||||
missing_keys.append("DASHSCOPE_API_KEY")
|
||||
missing_keys.append(f"{custom_llm_provider.upper()}_API_KEY")
|
||||
elif custom_llm_provider == "modelscope":
|
||||
if "MODELSCOPE_API_KEY" in os.environ:
|
||||
keys_in_environment = True
|
||||
|
|
@ -8152,6 +8152,11 @@ class ProviderConfigManager:
|
|||
LlmProviders.NEBIUS: (lambda: litellm.NebiusConfig(), False),
|
||||
LlmProviders.WANDB: (lambda: litellm.WandbConfig(), False),
|
||||
LlmProviders.DASHSCOPE: (lambda: litellm.DashScopeChatConfig(), False),
|
||||
LlmProviders.QWENCLOUD: (lambda: litellm.QwenCloudChatConfig(), False),
|
||||
LlmProviders.QWEN_AI_PLATFORM: (
|
||||
lambda: litellm.QwenAIPlatformChatConfig(),
|
||||
False,
|
||||
),
|
||||
LlmProviders.MODELSCOPE: (lambda: litellm.ModelScopeChatConfig(), False),
|
||||
LlmProviders.MOONSHOT: (lambda: litellm.MoonshotChatConfig(), False),
|
||||
LlmProviders.DOCKER_MODEL_RUNNER: (
|
||||
|
|
@ -8366,12 +8371,16 @@ class ProviderConfigManager:
|
|||
)
|
||||
|
||||
return VolcEngineEmbeddingConfig()
|
||||
elif litellm.LlmProviders.DASHSCOPE == provider:
|
||||
from litellm.llms.dashscope.embed.transformation import (
|
||||
DashScopeEmbeddingConfig,
|
||||
elif provider in (
|
||||
litellm.LlmProviders.DASHSCOPE,
|
||||
litellm.LlmProviders.QWENCLOUD,
|
||||
litellm.LlmProviders.QWEN_AI_PLATFORM,
|
||||
):
|
||||
from litellm.llms.dashscope.common_utils import (
|
||||
get_dashscope_family_embedding_config,
|
||||
)
|
||||
|
||||
return DashScopeEmbeddingConfig()
|
||||
return get_dashscope_family_embedding_config(provider.value)
|
||||
elif litellm.LlmProviders.OVHCLOUD == provider:
|
||||
return litellm.OVHCloudEmbeddingConfig()
|
||||
elif litellm.LlmProviders.SNOWFLAKE == provider:
|
||||
|
|
@ -8444,12 +8453,16 @@ class ProviderConfigManager:
|
|||
return litellm.VoyageRerankConfig()
|
||||
elif litellm.LlmProviders.WATSONX == provider:
|
||||
return litellm.IBMWatsonXRerankConfig()
|
||||
elif litellm.LlmProviders.DASHSCOPE == provider:
|
||||
from litellm.llms.dashscope.rerank.transformation import (
|
||||
DashScopeRerankConfig,
|
||||
elif provider in (
|
||||
litellm.LlmProviders.DASHSCOPE,
|
||||
litellm.LlmProviders.QWENCLOUD,
|
||||
litellm.LlmProviders.QWEN_AI_PLATFORM,
|
||||
):
|
||||
from litellm.llms.dashscope.common_utils import (
|
||||
get_dashscope_family_rerank_config,
|
||||
)
|
||||
|
||||
return DashScopeRerankConfig()
|
||||
return get_dashscope_family_rerank_config(provider.value)
|
||||
return litellm.CohereRerankConfig()
|
||||
|
||||
@staticmethod
|
||||
|
|
@ -9122,12 +9135,16 @@ class ProviderConfigManager:
|
|||
)
|
||||
|
||||
return get_openrouter_image_generation_config(model)
|
||||
elif LlmProviders.DASHSCOPE == provider:
|
||||
from litellm.llms.dashscope.image_generation import (
|
||||
get_dashscope_image_generation_config,
|
||||
elif provider in (
|
||||
LlmProviders.DASHSCOPE,
|
||||
LlmProviders.QWENCLOUD,
|
||||
LlmProviders.QWEN_AI_PLATFORM,
|
||||
):
|
||||
from litellm.llms.dashscope.common_utils import (
|
||||
get_dashscope_family_image_generation_config,
|
||||
)
|
||||
|
||||
return get_dashscope_image_generation_config(model)
|
||||
return get_dashscope_family_image_generation_config(provider.value)
|
||||
elif LlmProviders.MODELSCOPE == provider:
|
||||
from litellm.llms.modelscope.image_generation import (
|
||||
get_modelscope_image_generation_config,
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -724,6 +724,42 @@
|
|||
"interactions": true
|
||||
}
|
||||
},
|
||||
"qwencloud": {
|
||||
"display_name": "QwenCloud (`qwencloud`)",
|
||||
"url": "https://docs.litellm.ai/docs/providers/qwencloud",
|
||||
"endpoints": {
|
||||
"chat_completions": true,
|
||||
"messages": true,
|
||||
"responses": true,
|
||||
"embeddings": true,
|
||||
"image_generations": true,
|
||||
"audio_transcriptions": false,
|
||||
"audio_speech": false,
|
||||
"moderations": false,
|
||||
"batches": false,
|
||||
"rerank": true,
|
||||
"a2a": true,
|
||||
"interactions": true
|
||||
}
|
||||
},
|
||||
"qwen_ai_platform": {
|
||||
"display_name": "Qwen AI Platform (`qwen_ai_platform`)",
|
||||
"url": "https://docs.litellm.ai/docs/providers/qwencloud",
|
||||
"endpoints": {
|
||||
"chat_completions": true,
|
||||
"messages": true,
|
||||
"responses": true,
|
||||
"embeddings": true,
|
||||
"image_generations": true,
|
||||
"audio_transcriptions": false,
|
||||
"audio_speech": false,
|
||||
"moderations": false,
|
||||
"batches": false,
|
||||
"rerank": true,
|
||||
"a2a": true,
|
||||
"interactions": true
|
||||
}
|
||||
},
|
||||
"databricks": {
|
||||
"display_name": "Databricks (`databricks`)",
|
||||
"url": "https://docs.litellm.ai/docs/providers/databricks",
|
||||
|
|
|
|||
331
tests/test_litellm/llms/dashscope/test_qwen_brand_aliases.py
Normal file
331
tests/test_litellm/llms/dashscope/test_qwen_brand_aliases.py
Normal file
|
|
@ -0,0 +1,331 @@
|
|||
import math
|
||||
|
||||
import pytest
|
||||
|
||||
import litellm
|
||||
from litellm import completion, get_llm_provider
|
||||
from litellm.llms.dashscope.chat.transformation import DashScopeChatConfig
|
||||
from litellm.llms.dashscope.cost_calculator import (
|
||||
cost_per_token as dashscope_cost_per_token,
|
||||
)
|
||||
from litellm.llms.dashscope.embed.transformation import DashScopeEmbeddingConfig
|
||||
from litellm.llms.dashscope.image_generation.transformation import (
|
||||
DashScopeImageGenerationConfig,
|
||||
)
|
||||
from litellm.llms.dashscope.qwen_ai_platform import (
|
||||
QWEN_AI_PLATFORM_API_BASE,
|
||||
QWEN_AI_PLATFORM_IMAGE_API_BASE,
|
||||
QWEN_AI_PLATFORM_RERANK_API_BASE,
|
||||
QwenAIPlatformChatConfig,
|
||||
QwenAIPlatformEmbeddingConfig,
|
||||
QwenAIPlatformImageGenerationConfig,
|
||||
QwenAIPlatformRerankConfig,
|
||||
)
|
||||
from litellm.llms.dashscope.qwencloud import (
|
||||
QWENCLOUD_API_BASE,
|
||||
QWENCLOUD_IMAGE_API_BASE,
|
||||
QWENCLOUD_RERANK_API_BASE,
|
||||
QwenCloudChatConfig,
|
||||
QwenCloudEmbeddingConfig,
|
||||
QwenCloudImageGenerationConfig,
|
||||
QwenCloudRerankConfig,
|
||||
)
|
||||
from litellm.llms.dashscope.rerank.transformation import DashScopeRerankConfig
|
||||
from litellm.types.utils import LlmProviders, Usage
|
||||
from litellm.utils import ProviderConfigManager
|
||||
|
||||
DASHSCOPE_FAMILY_ENV_VARS = [
|
||||
"DASHSCOPE_API_KEY",
|
||||
"DASHSCOPE_API_BASE",
|
||||
"DASHSCOPE_API_BASE_RERANK",
|
||||
"DASHSCOPE_API_BASE_IMAGE",
|
||||
"QWENCLOUD_API_KEY",
|
||||
"QWENCLOUD_API_BASE",
|
||||
"QWENCLOUD_API_BASE_RERANK",
|
||||
"QWENCLOUD_API_BASE_IMAGE",
|
||||
"QWEN_AI_PLATFORM_API_KEY",
|
||||
"QWEN_AI_PLATFORM_API_BASE",
|
||||
"QWEN_AI_PLATFORM_API_BASE_RERANK",
|
||||
"QWEN_AI_PLATFORM_API_BASE_IMAGE",
|
||||
]
|
||||
|
||||
BRAND_CASES = [
|
||||
pytest.param(
|
||||
{
|
||||
"provider": "qwencloud",
|
||||
"enum": LlmProviders.QWENCLOUD,
|
||||
"key_env": "QWENCLOUD_API_KEY",
|
||||
"base_env": "QWENCLOUD_API_BASE",
|
||||
"default_base": QWENCLOUD_API_BASE,
|
||||
"default_rerank_base": QWENCLOUD_RERANK_API_BASE,
|
||||
"default_image_base": QWENCLOUD_IMAGE_API_BASE,
|
||||
"chat_config": QwenCloudChatConfig,
|
||||
"embedding_config": QwenCloudEmbeddingConfig,
|
||||
"rerank_config": QwenCloudRerankConfig,
|
||||
"image_config": QwenCloudImageGenerationConfig,
|
||||
},
|
||||
id="qwencloud",
|
||||
),
|
||||
pytest.param(
|
||||
{
|
||||
"provider": "qwen_ai_platform",
|
||||
"enum": LlmProviders.QWEN_AI_PLATFORM,
|
||||
"key_env": "QWEN_AI_PLATFORM_API_KEY",
|
||||
"base_env": "QWEN_AI_PLATFORM_API_BASE",
|
||||
"default_base": QWEN_AI_PLATFORM_API_BASE,
|
||||
"default_rerank_base": QWEN_AI_PLATFORM_RERANK_API_BASE,
|
||||
"default_image_base": QWEN_AI_PLATFORM_IMAGE_API_BASE,
|
||||
"chat_config": QwenAIPlatformChatConfig,
|
||||
"embedding_config": QwenAIPlatformEmbeddingConfig,
|
||||
"rerank_config": QwenAIPlatformRerankConfig,
|
||||
"image_config": QwenAIPlatformImageGenerationConfig,
|
||||
},
|
||||
id="qwen_ai_platform",
|
||||
),
|
||||
]
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def clear_dashscope_family_env(monkeypatch):
|
||||
for env_var in DASHSCOPE_FAMILY_ENV_VARS:
|
||||
monkeypatch.delenv(env_var, raising=False)
|
||||
|
||||
|
||||
class TestQwenBrandProviderResolution:
|
||||
@pytest.mark.parametrize("brand", BRAND_CASES)
|
||||
def test_get_llm_provider_resolves_brand_default_base(self, brand):
|
||||
model, provider, api_key, api_base = get_llm_provider(f"{brand['provider']}/qwen-max", api_key="sk-explicit")
|
||||
assert model == "qwen-max"
|
||||
assert provider == brand["provider"]
|
||||
assert api_key == "sk-explicit"
|
||||
assert api_base == brand["default_base"]
|
||||
|
||||
def test_dashscope_resolution_unchanged(self):
|
||||
model, provider, api_key, api_base = get_llm_provider("dashscope/qwen-max", api_key="sk-explicit")
|
||||
assert model == "qwen-max"
|
||||
assert provider == "dashscope"
|
||||
assert api_base == "https://dashscope.aliyuncs.com/compatible-mode/v1"
|
||||
|
||||
@pytest.mark.parametrize("brand", BRAND_CASES)
|
||||
def test_brand_env_key_wins_over_dashscope_key(self, monkeypatch, brand):
|
||||
monkeypatch.setenv(brand["key_env"], "sk-brand")
|
||||
monkeypatch.setenv("DASHSCOPE_API_KEY", "sk-dashscope")
|
||||
_, _, api_key, _ = get_llm_provider(f"{brand['provider']}/qwen-max")
|
||||
assert api_key == "sk-brand"
|
||||
|
||||
@pytest.mark.parametrize("brand", BRAND_CASES)
|
||||
def test_dashscope_key_is_fallback(self, monkeypatch, brand):
|
||||
monkeypatch.setenv("DASHSCOPE_API_KEY", "sk-dashscope")
|
||||
_, _, api_key, _ = get_llm_provider(f"{brand['provider']}/qwen-max")
|
||||
assert api_key == "sk-dashscope"
|
||||
|
||||
@pytest.mark.parametrize("brand", BRAND_CASES)
|
||||
def test_dashscope_api_base_does_not_leak_into_brand(self, monkeypatch, brand):
|
||||
monkeypatch.setenv("DASHSCOPE_API_BASE", "https://legacy.example.com/v1")
|
||||
_, _, _, api_base = get_llm_provider(f"{brand['provider']}/qwen-max", api_key="sk-explicit")
|
||||
assert api_base == brand["default_base"]
|
||||
|
||||
@pytest.mark.parametrize("brand", BRAND_CASES)
|
||||
def test_brand_api_base_env_wins(self, monkeypatch, brand):
|
||||
monkeypatch.setenv(brand["base_env"], "https://brand.example.com/v1")
|
||||
_, _, _, api_base = get_llm_provider(f"{brand['provider']}/qwen-max", api_key="sk-explicit")
|
||||
assert api_base == "https://brand.example.com/v1"
|
||||
|
||||
|
||||
class TestQwenBrandConfigDispatch:
|
||||
@pytest.mark.parametrize("brand", BRAND_CASES)
|
||||
def test_chat_config(self, brand):
|
||||
config = ProviderConfigManager.get_provider_chat_config("qwen-max", brand["enum"])
|
||||
assert isinstance(config, brand["chat_config"])
|
||||
assert isinstance(config, DashScopeChatConfig)
|
||||
|
||||
@pytest.mark.parametrize("brand", BRAND_CASES)
|
||||
def test_embedding_config(self, brand):
|
||||
config = ProviderConfigManager.get_provider_embedding_config(model="text-embedding-v3", provider=brand["enum"])
|
||||
assert isinstance(config, brand["embedding_config"])
|
||||
assert isinstance(config, DashScopeEmbeddingConfig)
|
||||
|
||||
@pytest.mark.parametrize("brand", BRAND_CASES)
|
||||
def test_rerank_config(self, brand):
|
||||
config = ProviderConfigManager.get_provider_rerank_config(
|
||||
model="gte-rerank-v2",
|
||||
provider=brand["enum"],
|
||||
api_base=None,
|
||||
present_version_params=[],
|
||||
)
|
||||
assert isinstance(config, brand["rerank_config"])
|
||||
assert isinstance(config, DashScopeRerankConfig)
|
||||
|
||||
@pytest.mark.parametrize("brand", BRAND_CASES)
|
||||
def test_image_generation_config(self, brand):
|
||||
config = ProviderConfigManager.get_provider_image_generation_config(model="qwen-image", provider=brand["enum"])
|
||||
assert isinstance(config, brand["image_config"])
|
||||
assert isinstance(config, DashScopeImageGenerationConfig)
|
||||
|
||||
|
||||
class TestQwenBrandDefaultUrls:
|
||||
@pytest.mark.parametrize("brand", BRAND_CASES)
|
||||
def test_chat_complete_url(self, brand):
|
||||
url = brand["chat_config"]().get_complete_url(
|
||||
api_base=None,
|
||||
api_key="sk-test",
|
||||
model="qwen-max",
|
||||
optional_params={},
|
||||
litellm_params={},
|
||||
)
|
||||
assert url == f"{brand['default_base']}/chat/completions"
|
||||
|
||||
@pytest.mark.parametrize("brand", BRAND_CASES)
|
||||
def test_embedding_complete_url(self, brand):
|
||||
url = brand["embedding_config"]().get_complete_url(
|
||||
api_base=None,
|
||||
api_key="sk-test",
|
||||
model="text-embedding-v3",
|
||||
optional_params={},
|
||||
litellm_params={},
|
||||
)
|
||||
assert url == f"{brand['default_base']}/embeddings"
|
||||
|
||||
@pytest.mark.parametrize("brand", BRAND_CASES)
|
||||
def test_embedding_ignores_dashscope_api_base(self, monkeypatch, brand):
|
||||
monkeypatch.setenv("DASHSCOPE_API_BASE", "https://legacy.example.com/v1")
|
||||
url = brand["embedding_config"]().get_complete_url(
|
||||
api_base=None,
|
||||
api_key="sk-test",
|
||||
model="text-embedding-v3",
|
||||
optional_params={},
|
||||
litellm_params={},
|
||||
)
|
||||
assert url == f"{brand['default_base']}/embeddings"
|
||||
|
||||
@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")
|
||||
assert url == brand["default_rerank_base"]
|
||||
|
||||
@pytest.mark.parametrize("brand", BRAND_CASES)
|
||||
def test_rerank_env_override(self, monkeypatch, brand):
|
||||
monkeypatch.setenv(f"{brand['base_env']}_RERANK", "https://rerank.example.com/v1/reranks")
|
||||
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_image_generation_complete_url(self, brand):
|
||||
url = brand["image_config"]().get_complete_url(
|
||||
api_base=None,
|
||||
api_key="sk-test",
|
||||
model="qwen-image",
|
||||
optional_params={},
|
||||
litellm_params={},
|
||||
)
|
||||
assert url == brand["default_image_base"]
|
||||
|
||||
@pytest.mark.parametrize("brand", BRAND_CASES)
|
||||
def test_image_generation_ignores_chat_compatible_api_base(self, brand):
|
||||
url = brand["image_config"]().get_complete_url(
|
||||
api_base=brand["default_base"],
|
||||
api_key="sk-test",
|
||||
model="qwen-image",
|
||||
optional_params={},
|
||||
litellm_params={},
|
||||
)
|
||||
assert url == brand["default_image_base"]
|
||||
|
||||
@pytest.mark.parametrize("brand", BRAND_CASES)
|
||||
def test_validate_environment_requires_key(self, brand):
|
||||
with pytest.raises(ValueError, match="DASHSCOPE_API_KEY"):
|
||||
brand["embedding_config"]().validate_environment(
|
||||
headers={},
|
||||
model="text-embedding-v3",
|
||||
messages=[],
|
||||
optional_params={},
|
||||
litellm_params={},
|
||||
api_key=None,
|
||||
api_base=None,
|
||||
)
|
||||
|
||||
|
||||
class TestQwenBrandCostParity:
|
||||
@pytest.fixture(autouse=True)
|
||||
def setup_model_cost_map(self, monkeypatch):
|
||||
monkeypatch.setenv("LITELLM_LOCAL_MODEL_COST_MAP", "True")
|
||||
monkeypatch.setattr(litellm, "model_cost", litellm.get_model_cost_map(url=""))
|
||||
|
||||
@pytest.mark.parametrize("brand", BRAND_CASES)
|
||||
def test_get_model_info(self, brand):
|
||||
model_info = litellm.get_model_info(f"{brand['provider']}/qwen-max")
|
||||
dashscope_info = litellm.get_model_info("dashscope/qwen-max")
|
||||
assert model_info["litellm_provider"] == brand["provider"]
|
||||
assert model_info["input_cost_per_token"] == dashscope_info["input_cost_per_token"]
|
||||
assert model_info["output_cost_per_token"] == dashscope_info["output_cost_per_token"]
|
||||
|
||||
@pytest.mark.parametrize("brand", BRAND_CASES)
|
||||
def test_flat_pricing_matches_dashscope(self, brand):
|
||||
usage = Usage(prompt_tokens=1000, completion_tokens=500)
|
||||
brand_costs = dashscope_cost_per_token(model="qwen-max", usage=usage, custom_llm_provider=brand["provider"])
|
||||
dashscope_costs = dashscope_cost_per_token(model="qwen-max", usage=usage)
|
||||
assert brand_costs == dashscope_costs
|
||||
|
||||
@pytest.mark.parametrize("brand", BRAND_CASES)
|
||||
def test_tiered_pricing_matches_dashscope(self, brand):
|
||||
usage = Usage(prompt_tokens=300000, completion_tokens=300000)
|
||||
brand_costs = dashscope_cost_per_token(model="qwen-flash", usage=usage, custom_llm_provider=brand["provider"])
|
||||
dashscope_costs = dashscope_cost_per_token(model="qwen-flash", usage=usage)
|
||||
assert brand_costs == dashscope_costs
|
||||
tier_2 = litellm.get_model_info(f"{brand['provider']}/qwen-flash")["tiered_pricing"][1]
|
||||
assert math.isclose(brand_costs[0], 300000 * tier_2["input_cost_per_token"], rel_tol=1e-10)
|
||||
|
||||
@pytest.mark.parametrize("brand", BRAND_CASES)
|
||||
def test_public_cost_per_token_routes_to_dashscope_calculator(self, brand):
|
||||
brand_costs = litellm.cost_per_token(
|
||||
model=f"{brand['provider']}/qwen-max",
|
||||
prompt_tokens=1000,
|
||||
completion_tokens=500,
|
||||
custom_llm_provider=brand["provider"],
|
||||
)
|
||||
dashscope_costs = litellm.cost_per_token(
|
||||
model="dashscope/qwen-max",
|
||||
prompt_tokens=1000,
|
||||
completion_tokens=500,
|
||||
custom_llm_provider="dashscope",
|
||||
)
|
||||
assert brand_costs == dashscope_costs
|
||||
|
||||
|
||||
class TestQwenBrandCompletionMock:
|
||||
@pytest.mark.respx()
|
||||
@pytest.mark.parametrize("brand", BRAND_CASES)
|
||||
def test_completion_hits_brand_default_host(self, respx_mock, brand, monkeypatch):
|
||||
monkeypatch.setattr(litellm, "disable_aiohttp_transport", True)
|
||||
respx_mock.post(f"{brand['default_base']}/chat/completions").respond(
|
||||
json={
|
||||
"id": "chatcmpl-123",
|
||||
"object": "chat.completion",
|
||||
"created": 1677652288,
|
||||
"model": "qwen-turbo",
|
||||
"choices": [
|
||||
{
|
||||
"index": 0,
|
||||
"message": {"role": "assistant", "content": "Hey from LiteLLM!"},
|
||||
"finish_reason": "stop",
|
||||
}
|
||||
],
|
||||
"usage": {
|
||||
"prompt_tokens": 9,
|
||||
"completion_tokens": 12,
|
||||
"total_tokens": 21,
|
||||
},
|
||||
},
|
||||
status_code=200,
|
||||
)
|
||||
|
||||
response = completion(
|
||||
model=f"{brand['provider']}/qwen-turbo",
|
||||
messages=[{"role": "user", "content": "say hey from LiteLLM"}],
|
||||
api_key="fake-brand-key",
|
||||
)
|
||||
|
||||
assert response.choices[0].message.content == "Hey from LiteLLM!"
|
||||
request = respx_mock.calls[0].request
|
||||
assert request.url == f"{brand['default_base']}/chat/completions"
|
||||
assert request.headers["Authorization"] == "Bearer fake-brand-key"
|
||||
|
|
@ -150,6 +150,8 @@ export enum Providers {
|
|||
PETALS = "Petals",
|
||||
PG_VECTOR = "Pg Vector",
|
||||
PREDIBASE = "Predibase",
|
||||
Qwen_AI_Platform = "Qwen AI Platform",
|
||||
QwenCloud = "QwenCloud",
|
||||
RECRAFT = "Recraft",
|
||||
REPLICATE = "Replicate",
|
||||
RunwayML = "RunwayML",
|
||||
|
|
@ -262,6 +264,8 @@ export const provider_map: Record<string, string> = {
|
|||
PETALS: "petals",
|
||||
PG_VECTOR: "pg_vector",
|
||||
PREDIBASE: "predibase",
|
||||
Qwen_AI_Platform: "qwen_ai_platform",
|
||||
QwenCloud: "qwencloud",
|
||||
RECRAFT: "recraft",
|
||||
REPLICATE: "replicate",
|
||||
RunwayML: "runwayml",
|
||||
|
|
@ -357,6 +361,8 @@ export const providerLogoMap: Partial<Record<Providers, string>> = {
|
|||
[Providers.Openrouter]: openrouterLogo.src,
|
||||
[Providers.Oracle]: oracleLogo.src,
|
||||
[Providers.Perplexity]: perplexityAiLogo.src,
|
||||
[Providers.Qwen_AI_Platform]: qwenLogo.src,
|
||||
[Providers.QwenCloud]: qwenLogo.src,
|
||||
[Providers.RECRAFT]: recraftLogo.src,
|
||||
[Providers.REPLICATE]: replicateLogo.src,
|
||||
[Providers.RunwayML]: runwayLogo.src,
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue