mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-24 00:52:24 +00:00
Merge pull request #42284 from BerriAI/litellm_qianwen_ai_platform_rename
fix: rename the mainland China brand to Qianwen AI Platform
This commit is contained in:
commit
662e5b6e32
10 changed files with 94 additions and 9 deletions
|
|
@ -356,7 +356,7 @@ For MCP OAuth, an upstream may advertise dynamic client registration but refuse
|
|||
| [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) | ✅ | ✅ | ✅ | ✅ | ✅ | | | | | ✅ |
|
||||
| [Qianwen 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) | ✅ | ✅ | ✅ | | | | | | | |
|
||||
|
|
|
|||
|
|
@ -103,7 +103,7 @@ def missing_dashscope_family_key_message(custom_llm_provider: str) -> str:
|
|||
)
|
||||
if custom_llm_provider == "qwen_ai_platform":
|
||||
return (
|
||||
"Missing API key for Qwen AI Platform. Set QWEN_AI_PLATFORM_API_KEY or "
|
||||
"Missing API key for Qianwen 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."
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ 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 "
|
||||
"Qianwen 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
|
||||
|
|
|
|||
|
|
@ -691,7 +691,7 @@
|
|||
}
|
||||
},
|
||||
"qwen_ai_platform": {
|
||||
"display_name": "Qwen AI Platform (`qwen_ai_platform`)",
|
||||
"display_name": "Qianwen AI Platform (`qwen_ai_platform`)",
|
||||
"url": "https://docs.litellm.ai/docs/providers/qwencloud",
|
||||
"endpoints": {
|
||||
"chat_completions": true,
|
||||
|
|
|
|||
|
|
@ -1129,12 +1129,12 @@
|
|||
},
|
||||
{
|
||||
"provider": "Qwen_AI_Platform",
|
||||
"provider_display_name": "Qwen AI Platform",
|
||||
"provider_display_name": "Qianwen AI Platform",
|
||||
"litellm_provider": "qwen_ai_platform",
|
||||
"credential_fields": [
|
||||
{
|
||||
"key": "api_key",
|
||||
"label": "Qwen AI Platform API Key",
|
||||
"label": "Qianwen AI Platform API Key",
|
||||
"placeholder": null,
|
||||
"tooltip": null,
|
||||
"required": true,
|
||||
|
|
@ -1146,7 +1146,7 @@
|
|||
"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.",
|
||||
"tooltip": "The base URL for Qianwen AI Platform. Defaults to https://dashscope.aliyuncs.com/compatible-mode/v1 if not specified.",
|
||||
"required": true,
|
||||
"field_type": "text",
|
||||
"options": null,
|
||||
|
|
|
|||
|
|
@ -744,7 +744,7 @@
|
|||
}
|
||||
},
|
||||
"qwen_ai_platform": {
|
||||
"display_name": "Qwen AI Platform (`qwen_ai_platform`)",
|
||||
"display_name": "Qianwen AI Platform (`qwen_ai_platform`)",
|
||||
"url": "https://docs.litellm.ai/docs/providers/qwencloud",
|
||||
"endpoints": {
|
||||
"chat_completions": true,
|
||||
|
|
|
|||
|
|
@ -1,10 +1,14 @@
|
|||
import json
|
||||
import math
|
||||
import re
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
import litellm
|
||||
from litellm import completion, get_llm_provider
|
||||
from litellm.llms.dashscope.chat.transformation import DashScopeChatConfig
|
||||
from litellm.llms.dashscope.common_utils import missing_dashscope_family_key_message
|
||||
from litellm.llms.dashscope.cost_calculator import (
|
||||
cost_per_token as dashscope_cost_per_token,
|
||||
)
|
||||
|
|
@ -53,6 +57,7 @@ BRAND_CASES = [
|
|||
pytest.param(
|
||||
{
|
||||
"provider": "qwencloud",
|
||||
"display_name": "QwenCloud",
|
||||
"enum": LlmProviders.QWENCLOUD,
|
||||
"key_env": "QWENCLOUD_API_KEY",
|
||||
"base_env": "QWENCLOUD_API_BASE",
|
||||
|
|
@ -69,6 +74,7 @@ BRAND_CASES = [
|
|||
pytest.param(
|
||||
{
|
||||
"provider": "qwen_ai_platform",
|
||||
"display_name": "Qianwen AI Platform",
|
||||
"enum": LlmProviders.QWEN_AI_PLATFORM,
|
||||
"key_env": "QWEN_AI_PLATFORM_API_KEY",
|
||||
"base_env": "QWEN_AI_PLATFORM_API_BASE",
|
||||
|
|
@ -89,6 +95,13 @@ BRAND_CASES = [
|
|||
def clear_dashscope_family_env(monkeypatch):
|
||||
for env_var in DASHSCOPE_FAMILY_ENV_VARS:
|
||||
monkeypatch.delenv(env_var, raising=False)
|
||||
monkeypatch.setattr(litellm, "api_key", None)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def no_provider_traffic(respx_mock, monkeypatch):
|
||||
monkeypatch.setattr(litellm, "disable_aiohttp_transport", True)
|
||||
return respx_mock
|
||||
|
||||
|
||||
class TestQwenBrandProviderResolution:
|
||||
|
|
@ -250,6 +263,51 @@ class TestQwenBrandDefaultUrls:
|
|||
)
|
||||
|
||||
|
||||
class TestQwenBrandUserFacingNames:
|
||||
RETIRED_MAINLAND_NAME = "Qwen AI Platform"
|
||||
|
||||
@pytest.mark.parametrize("brand", BRAND_CASES)
|
||||
def test_missing_key_message_names_brand(self, brand):
|
||||
message = missing_dashscope_family_key_message(brand["provider"])
|
||||
assert brand["display_name"] in message
|
||||
assert brand["key_env"] in message
|
||||
assert self.RETIRED_MAINLAND_NAME not in message
|
||||
|
||||
@pytest.mark.parametrize("brand", BRAND_CASES)
|
||||
def test_embedding_without_key_names_brand(self, brand, no_provider_traffic):
|
||||
with pytest.raises(litellm.APIConnectionError, match=re.escape(brand["display_name"])) as exc_info:
|
||||
litellm.embedding(model=f"{brand['provider']}/text-embedding-v4", input=["hello"])
|
||||
assert self.RETIRED_MAINLAND_NAME not in str(exc_info.value)
|
||||
assert no_provider_traffic.calls.call_count == 0
|
||||
|
||||
@pytest.mark.parametrize("brand", BRAND_CASES)
|
||||
def test_rerank_without_key_names_brand(self, brand, no_provider_traffic):
|
||||
with pytest.raises(litellm.APIConnectionError, match=re.escape(brand["display_name"])) as exc_info:
|
||||
litellm.rerank(model=f"{brand['provider']}/gte-rerank-v2", query="q", documents=["a", "b"])
|
||||
assert self.RETIRED_MAINLAND_NAME not in str(exc_info.value)
|
||||
assert no_provider_traffic.calls.call_count == 0
|
||||
|
||||
@pytest.mark.parametrize("brand", BRAND_CASES)
|
||||
def test_image_generation_without_key_names_brand(self, brand, no_provider_traffic):
|
||||
with pytest.raises(litellm.APIConnectionError, match=re.escape(brand["display_name"])) as exc_info:
|
||||
litellm.image_generation(model=f"{brand['provider']}/qwen-image", prompt="a cup of coffee")
|
||||
assert self.RETIRED_MAINLAND_NAME not in str(exc_info.value)
|
||||
assert no_provider_traffic.calls.call_count == 0
|
||||
|
||||
@pytest.mark.parametrize("brand", BRAND_CASES)
|
||||
@pytest.mark.parametrize(
|
||||
"matrix_path",
|
||||
[
|
||||
Path(litellm.__file__).parent / "provider_endpoints_support_backup.json",
|
||||
Path(litellm.__file__).parent.parent / "provider_endpoints_support.json",
|
||||
],
|
||||
ids=["backup", "root"],
|
||||
)
|
||||
def test_supported_endpoints_matrix_display_name(self, brand, matrix_path):
|
||||
matrix = json.loads(matrix_path.read_text())
|
||||
assert matrix["providers"][brand["provider"]]["display_name"] == f"{brand['display_name']} (`{brand['provider']}`)"
|
||||
|
||||
|
||||
class TestQwenBrandCostParity:
|
||||
@pytest.fixture(autouse=True)
|
||||
def setup_model_cost_map(self, monkeypatch):
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import json
|
||||
import re
|
||||
from datetime import datetime, timezone
|
||||
from typing import Final
|
||||
|
|
@ -339,6 +340,25 @@ def test_cognition_provider_fields():
|
|||
assert fields_by_key["api_base"]["required"] is False
|
||||
|
||||
|
||||
def test_qwen_mainland_provider_fields_carry_the_qianwen_brand():
|
||||
app_instance = FastAPI()
|
||||
app_instance.include_router(router)
|
||||
test_client = TestClient(app_instance)
|
||||
|
||||
providers = test_client.get("/public/providers/fields").json()
|
||||
|
||||
mainland = next(p for p in providers if p["litellm_provider"] == "qwen_ai_platform")
|
||||
international = next(p for p in providers if p["litellm_provider"] == "qwencloud")
|
||||
|
||||
assert mainland["provider_display_name"] == "Qianwen AI Platform"
|
||||
assert international["provider_display_name"] == "QwenCloud"
|
||||
|
||||
mainland_fields = {f["key"]: f for f in mainland["credential_fields"]}
|
||||
assert mainland_fields["api_key"]["label"] == "Qianwen AI Platform API Key"
|
||||
assert "Qianwen AI Platform" in mainland_fields["api_base"]["tooltip"]
|
||||
assert "Qwen AI Platform" not in json.dumps(mainland)
|
||||
|
||||
|
||||
def test_chatgpt_provider_fields():
|
||||
app_instance = FastAPI()
|
||||
app_instance.include_router(router)
|
||||
|
|
|
|||
|
|
@ -133,6 +133,13 @@ describe("provider_info_helpers", () => {
|
|||
expect(result.logo).toBeTruthy();
|
||||
});
|
||||
|
||||
it("should resolve the qwen_ai_platform slug and Qwen_AI_Platform enum key to the Qianwen AI Platform display name", () => {
|
||||
expect(getProviderLogoAndName("qwen_ai_platform").displayName).toBe("Qianwen AI Platform");
|
||||
expect(getProviderLogoAndName("Qwen_AI_Platform").displayName).toBe("Qianwen AI Platform");
|
||||
expect(getProviderLogoAndName("qwencloud").displayName).toBe("QwenCloud");
|
||||
expect(getProviderLogoAndName("qwen_ai_platform").logo).toBe(providerLogoMap[Providers.Qwen_AI_Platform]);
|
||||
});
|
||||
|
||||
it("should return provider value as display name when no mapping exists", () => {
|
||||
const unknownProvider = "unknown_provider";
|
||||
const result = getProviderLogoAndName(unknownProvider);
|
||||
|
|
|
|||
|
|
@ -152,7 +152,7 @@ export enum Providers {
|
|||
PETALS = "Petals",
|
||||
PG_VECTOR = "Pg Vector",
|
||||
PREDIBASE = "Predibase",
|
||||
Qwen_AI_Platform = "Qwen AI Platform",
|
||||
Qwen_AI_Platform = "Qianwen AI Platform",
|
||||
QwenCloud = "QwenCloud",
|
||||
RECRAFT = "Recraft",
|
||||
REPLICATE = "Replicate",
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue