-
-
Notifications
You must be signed in to change notification settings - Fork 11k
feat(providers): add Bitdeer AI as a JSON-configured provider #36734
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: litellm_internal_staging
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -183,5 +183,12 @@ | |
| "max_completion_tokens": "max_tokens" | ||
| }, | ||
| "supported_endpoints": ["/v1/chat/completions", "/v1/responses", "/v1/embeddings"] | ||
| }, | ||
| "bitdeer-ai": { | ||
| "base_url": "https://api-inference.bitdeer.ai/v1", | ||
| "api_key_env": "BITDEER_API_KEY", | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Medium: API key exfiltration through an overridden base URL A caller can submit |
||
| "param_mappings": { | ||
| "max_completion_tokens": "max_tokens" | ||
| } | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -47176,6 +47176,56 @@ | |||||||||
| ], | ||||||||||
| "supports_audio_output": true | ||||||||||
| }, | ||||||||||
| "bitdeer-ai/moonshotai/Kimi-K3": { | ||||||||||
| "max_tokens": 1048576, | ||||||||||
| "max_input_tokens": 1048576, | ||||||||||
| "max_output_tokens": 131072, | ||||||||||
| "input_cost_per_token": 3e-06, | ||||||||||
| "output_cost_per_token": 1.5e-05, | ||||||||||
| "cache_read_input_token_cost": 3e-07, | ||||||||||
| "litellm_provider": "bitdeer-ai", | ||||||||||
| "mode": "chat", | ||||||||||
| "supports_function_calling": true, | ||||||||||
| "supports_tool_choice": true, | ||||||||||
| "supports_reasoning": true, | ||||||||||
| "supports_vision": true, | ||||||||||
| "supports_prompt_caching": true, | ||||||||||
| "source": "https://www.bitdeer.ai/en/pricing/ai-models" | ||||||||||
| }, | ||||||||||
| "bitdeer-ai/zai-org/GLM-5.2": { | ||||||||||
| "max_tokens": 1000000, | ||||||||||
| "max_input_tokens": 1000000, | ||||||||||
| "max_output_tokens": 131072, | ||||||||||
| "input_cost_per_token": 1.4e-06, | ||||||||||
| "output_cost_per_token": 4.4e-06, | ||||||||||
| "cache_read_input_token_cost": 2.6e-07, | ||||||||||
| "litellm_provider": "bitdeer-ai", | ||||||||||
| "mode": "chat", | ||||||||||
| "supports_function_calling": true, | ||||||||||
| "supports_tool_choice": true, | ||||||||||
| "supports_reasoning": true, | ||||||||||
| "supports_prompt_caching": true, | ||||||||||
| "source": "https://www.bitdeer.ai/en/pricing/ai-models" | ||||||||||
| }, | ||||||||||
| "bitdeer-ai/deepseek-ai/DeepSeek-V4-Flash": { | ||||||||||
| "supports_prompt_caching": true, | ||||||||||
| "source": "https://www.bitdeer.ai/en/pricing/ai-models" | ||||||||||
| }, | ||||||||||
|
Comment on lines
+47210
to
+47213
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
Knowledge Base Used: Cost Tracking and Budget Enforcement |
||||||||||
| "bitdeer-ai/deepseek-ai/DeepSeek-V4-Flash": { | ||||||||||
| "max_tokens": 1000000, | ||||||||||
| "max_input_tokens": 1000000, | ||||||||||
| "max_output_tokens": 384000, | ||||||||||
| "input_cost_per_token": 4e-08, | ||||||||||
| "output_cost_per_token": 8e-08, | ||||||||||
| "cache_read_input_token_cost": 1e-08, | ||||||||||
| "litellm_provider": "bitdeer-ai", | ||||||||||
| "mode": "chat", | ||||||||||
| "supports_function_calling": true, | ||||||||||
| "supports_tool_choice": true, | ||||||||||
| "supports_reasoning": true, | ||||||||||
| "supports_prompt_caching": true, | ||||||||||
| "source": "https://www.bitdeer.ai/en/pricing/ai-models" | ||||||||||
| }, | ||||||||||
| "fallback_generalizations": { | ||||||||||
| "rules": [ | ||||||||||
| { | ||||||||||
|
|
||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,120 @@ | ||
| """ | ||
| Tests for Bitdeer AI provider configuration and integration. | ||
| """ | ||
|
|
||
| import json | ||
| import os | ||
|
|
||
| import litellm | ||
|
|
||
| workspace_path = os.path.abspath(os.path.join(os.path.dirname(__file__), "../../../..")) | ||
|
|
||
|
|
||
| class TestBitdeerProviderConfig: | ||
| """Test Bitdeer AI provider configuration""" | ||
|
|
||
| def test_bitdeer_in_provider_list(self): | ||
| """Test that bitdeer is in the provider list""" | ||
| from litellm import LlmProviders | ||
|
|
||
| assert hasattr(LlmProviders, "BITDEER_AI") | ||
| assert LlmProviders.BITDEER_AI.value == "bitdeer-ai" | ||
| assert "bitdeer-ai" in litellm.provider_list | ||
|
|
||
| def test_bitdeer_json_config_exists(self): | ||
| """Test that bitdeer is configured in providers.json""" | ||
| from litellm.llms.openai_like.json_loader import JSONProviderRegistry | ||
|
|
||
| assert JSONProviderRegistry.exists("bitdeer-ai") | ||
|
|
||
| bitdeer_ai = JSONProviderRegistry.get("bitdeer-ai") | ||
| assert bitdeer_ai is not None | ||
| assert bitdeer_ai.base_url == "https://api-inference.bitdeer.ai/v1" | ||
| assert bitdeer_ai.api_key_env == "BITDEER_API_KEY" | ||
| assert bitdeer_ai.param_mappings.get("max_completion_tokens") == "max_tokens" | ||
|
|
||
| def test_bitdeer_in_openai_compatible_providers(self): | ||
| """Test that bitdeer is in the openai_compatible_providers list""" | ||
| from litellm.constants import openai_compatible_providers | ||
|
|
||
| assert "bitdeer-ai" in openai_compatible_providers | ||
|
|
||
| def test_bitdeer_provider_resolution(self): | ||
| """Test that provider resolution finds bitdeer and returns the default base URL""" | ||
| from litellm.litellm_core_utils.get_llm_provider_logic import get_llm_provider | ||
|
|
||
| model, provider, api_key, api_base = get_llm_provider( | ||
| model="bitdeer-ai/moonshotai/Kimi-K3", | ||
| custom_llm_provider=None, | ||
| api_base=None, | ||
| api_key=None, | ||
| ) | ||
|
|
||
| assert model == "moonshotai/Kimi-K3" | ||
| assert provider == "bitdeer-ai" | ||
| assert api_base == "https://api-inference.bitdeer.ai/v1" | ||
|
|
||
| def test_bitdeer_api_base_override(self): | ||
| """Test that an explicit api_base / api_key overrides the default""" | ||
| from litellm.litellm_core_utils.get_llm_provider_logic import get_llm_provider | ||
|
|
||
| model, provider, api_key, api_base = get_llm_provider( | ||
| model="bitdeer-ai/moonshotai/Kimi-K3", | ||
| custom_llm_provider=None, | ||
| api_base="https://custom.bitdeer.ai/v1", | ||
| api_key="sk-test", | ||
| ) | ||
|
|
||
| assert provider == "bitdeer-ai" | ||
| assert api_base == "https://custom.bitdeer.ai/v1" | ||
| assert api_key == "sk-test" | ||
|
|
||
| def test_bitdeer_url_autodetection(self): | ||
| """Test that api_base=api-inference.bitdeer.ai/v1 auto-sets custom_llm_provider=bitdeer""" | ||
| from litellm.litellm_core_utils.get_llm_provider_logic import get_llm_provider | ||
|
|
||
| model, provider, api_key, api_base = get_llm_provider( | ||
| model="moonshotai/Kimi-K3", | ||
| custom_llm_provider=None, | ||
| api_base="https://api-inference.bitdeer.ai/v1", | ||
| api_key=None, | ||
| ) | ||
| assert provider == "bitdeer-ai" | ||
| assert api_base == "https://api-inference.bitdeer.ai/v1" | ||
|
|
||
| def test_bitdeer_router_config(self): | ||
| """Test that bitdeer can be used in Router configuration""" | ||
| from litellm import Router | ||
|
|
||
| router = Router( | ||
| model_list=[ | ||
| { | ||
| "model_name": "bitdeer-ai-kimi-k3", | ||
| "litellm_params": { | ||
| "model": "bitdeer-ai/moonshotai/Kimi-K3", | ||
| "api_key": "test-key", | ||
| }, | ||
| } | ||
| ] | ||
| ) | ||
|
|
||
| assert len(router.model_list) == 1 | ||
| assert router.model_list[0]["model_name"] == "bitdeer-ai-kimi-k3" | ||
|
|
||
| def test_bitdeer_model_cost_map(self): | ||
| """Test that bitdeer model pricing is registered correctly""" | ||
| with open( | ||
| os.path.join(workspace_path, "model_prices_and_context_window.json") | ||
| ) as f: | ||
| model_cost = json.load(f) | ||
|
|
||
| expected_models = { | ||
| "bitdeer-ai/moonshotai/Kimi-K3": (3e-06, 1.5e-05), | ||
| "bitdeer-ai/zai-org/GLM-5.2": (1.4e-06, 4.4e-06), | ||
| "bitdeer-ai/deepseek-ai/DeepSeek-V4-Flash": (4e-08, 8e-08), | ||
| } | ||
| for model, (input_cost, output_cost) in expected_models.items(): | ||
| assert model in model_cost | ||
| assert model_cost[model]["litellm_provider"] == "bitdeer-ai" | ||
| assert model_cost[model]["input_cost_per_token"] == input_cost | ||
| assert model_cost[model]["output_cost_per_token"] == output_cost |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If
BITDEER_API_KEYis set, URL autodetection replaces the caller's explicitapi_keywith that environment value, causing authentication to use the wrong Bitdeer account or fail despite a valid explicit credential.Knowledge Base Used: LLM Provider Adapters