diff --git a/litellm/constants.py b/litellm/constants.py index 6918e40cad1..1c32adfbd46 100644 --- a/litellm/constants.py +++ b/litellm/constants.py @@ -778,6 +778,7 @@ openai_compatible_endpoints: List = [ "https://ai-gateway.vercel.sh/v1", "https://api.inference.wandb.ai/v1", "https://api.clarifai.com/v2/ext/openai/v1", + "https://qianfan.baidubce.com/v2", ] @@ -814,6 +815,7 @@ openai_compatible_providers: List = [ "novita", "meta_llama", "publicai", # PublicAI - JSON-configured provider + "qianfan", # Qianfan - JSON-configured provider "synthetic", # Synthetic - JSON-configured provider "apertis", # Apertis - JSON-configured provider "nano-gpt", # Nano-GPT - JSON-configured provider diff --git a/litellm/litellm_core_utils/get_llm_provider_logic.py b/litellm/litellm_core_utils/get_llm_provider_logic.py index ba6d438f16c..c6088db4040 100644 --- a/litellm/litellm_core_utils/get_llm_provider_logic.py +++ b/litellm/litellm_core_utils/get_llm_provider_logic.py @@ -362,6 +362,9 @@ def get_llm_provider( # noqa: PLR0915 elif endpoint == "https://api.inference.wandb.ai/v1": custom_llm_provider = "wandb" dynamic_api_key = get_secret_str("WANDB_API_KEY") + elif endpoint == "https://qianfan.baidubce.com/v2": + custom_llm_provider = "qianfan" + dynamic_api_key = api_key or get_secret_str("QIANFAN_API_KEY") if api_base is not None and not isinstance(api_base, str): raise Exception( diff --git a/litellm/llms/openai_like/providers.json b/litellm/llms/openai_like/providers.json index b5e5aa4ea28..7db1d24b55e 100644 --- a/litellm/llms/openai_like/providers.json +++ b/litellm/llms/openai_like/providers.json @@ -11,6 +11,14 @@ "convert_content_list_to_string": true } }, + "qianfan": { + "base_url": "https://qianfan.baidubce.com/v2", + "api_key_env": "QIANFAN_API_KEY", + "api_base_env": "QIANFAN_API_BASE", + "param_mappings": { + "max_completion_tokens": "max_tokens" + } + }, "helicone": { "base_url": "https://ai-gateway.helicone.ai/", "api_key_env": "HELICONE_API_KEY" diff --git a/litellm/types/utils.py b/litellm/types/utils.py index 00a7748309b..8bb4318eb46 100644 --- a/litellm/types/utils.py +++ b/litellm/types/utils.py @@ -3202,6 +3202,7 @@ class LlmProviders(str, Enum): OPENAI = "openai" CHATGPT = "chatgpt" OPENAI_LIKE = "openai_like" # embedding only + QIANFAN = "qianfan" JINA_AI = "jina_ai" XAI = "xai" ZAI = "zai" diff --git a/tests/test_litellm/llms/openai_like/test_qianfan.py b/tests/test_litellm/llms/openai_like/test_qianfan.py new file mode 100644 index 00000000000..ef390d53c9a --- /dev/null +++ b/tests/test_litellm/llms/openai_like/test_qianfan.py @@ -0,0 +1,128 @@ +import os +import sys +from unittest.mock import patch + +sys.path.insert(0, os.path.abspath("../../../../..")) + +import litellm +from litellm import Router +from litellm.llms.openai_like.dynamic_config import create_config_class +from litellm.llms.openai_like.json_loader import JSONProviderRegistry +from litellm.types.utils import LlmProviders +from litellm.utils import ProviderConfigManager + + +def test_qianfan_in_provider_list(): + assert "qianfan" in litellm.provider_list + assert "qianfan" in litellm.openai_compatible_providers + assert LlmProviders.QIANFAN == "qianfan" + + +def test_qianfan_json_config_exists(): + config = JSONProviderRegistry.get("qianfan") + + assert config is not None + assert config.base_url == "https://qianfan.baidubce.com/v2" + assert config.api_key_env == "QIANFAN_API_KEY" + assert config.api_base_env == "QIANFAN_API_BASE" + assert config.param_mappings["max_completion_tokens"] == "max_tokens" + + +def test_qianfan_dynamic_config_defaults(): + provider = JSONProviderRegistry.get("qianfan") + assert provider is not None + + config = create_config_class(provider)() + api_base, api_key = config._get_openai_compatible_provider_info( + api_base=None, api_key=None + ) + + assert api_base == "https://qianfan.baidubce.com/v2" + assert api_key is None + assert config.custom_llm_provider == "qianfan" + + +def test_qianfan_provider_detection_by_prefix(): + model, provider, _, api_base = litellm.get_llm_provider( + model="qianfan/ernie-4.5-turbo-128k" + ) + + assert provider == "qianfan" + assert model == "ernie-4.5-turbo-128k" + assert api_base == "https://qianfan.baidubce.com/v2" + + +def test_qianfan_provider_detection_by_api_base_preserves_explicit_key(): + model, provider, api_key, api_base = litellm.get_llm_provider( + model="ernie-4.5-turbo-128k", + api_base="https://qianfan.baidubce.com/v2", + api_key="test-qianfan-key", + ) + + assert model == "ernie-4.5-turbo-128k" + assert provider == "qianfan" + assert api_key == "test-qianfan-key" + assert api_base == "https://qianfan.baidubce.com/v2" + + +def test_qianfan_provider_detection_by_api_base_prefers_explicit_key_over_env(): + with patch.dict(os.environ, {"QIANFAN_API_KEY": "env-qianfan-key"}): + model, provider, api_key, api_base = litellm.get_llm_provider( + model="ernie-4.5-turbo-128k", + api_base="https://qianfan.baidubce.com/v2", + api_key="explicit-qianfan-key", + ) + + assert model == "ernie-4.5-turbo-128k" + assert provider == "qianfan" + assert api_key == "explicit-qianfan-key" + assert api_base == "https://qianfan.baidubce.com/v2" + + +def test_qianfan_router_config(): + router = Router( + model_list=[ + { + "model_name": "qianfan-router-model", + "litellm_params": { + "model": "qianfan/ernie-4.5-turbo-128k", + "api_key": "test-key", + }, + } + ] + ) + + assert len(router.model_list) == 1 + assert router.model_list[0]["model_name"] == "qianfan-router-model" + assert router.deployment_names == ["qianfan/ernie-4.5-turbo-128k"] + + +def test_qianfan_provider_config_manager(): + config = ProviderConfigManager.get_provider_chat_config( + model="ernie-4.5-turbo-128k", + provider=LlmProviders.QIANFAN, + ) + + assert config is not None + assert config.custom_llm_provider == "qianfan" + + +def test_qianfan_provider_config_manager_maps_max_completion_tokens(): + config = ProviderConfigManager.get_provider_chat_config( + model="ernie-4.5-turbo-128k", + provider=LlmProviders.QIANFAN, + ) + + mapped_params = config.map_openai_params( + non_default_params={ + "max_completion_tokens": 123, + "temperature": 0.7, + }, + optional_params={}, + model="ernie-4.5-turbo-128k", + drop_params=False, + ) + + assert mapped_params["max_tokens"] == 123 + assert "max_completion_tokens" not in mapped_params + assert mapped_params["temperature"] == 0.7