From fc3a52c49ecaefcc13afacebb579f458f5bda403 Mon Sep 17 00:00:00 2001 From: gorkii Date: Wed, 5 Aug 2026 02:26:13 -0400 Subject: [PATCH] feat(provider): add Salad AI Gateway --- litellm/constants.py | 2 + litellm/llms/openai_like/providers.json | 8 ++ ...odel_prices_and_context_window_backup.json | 17 +++ .../provider_endpoints_support_backup.json | 17 +++ litellm/types/utils.py | 1 + model_prices_and_context_window.json | 17 +++ provider_endpoints_support.json | 17 +++ .../openai_like/test_salad_cloud_provider.py | 100 ++++++++++++++++++ 8 files changed, 179 insertions(+) create mode 100644 tests/test_litellm/llms/openai_like/test_salad_cloud_provider.py diff --git a/litellm/constants.py b/litellm/constants.py index 264f595027f..25a537bf0b0 100644 --- a/litellm/constants.py +++ b/litellm/constants.py @@ -736,6 +736,7 @@ openai_compatible_endpoints: Final[list] = [ "https://api.inference.wandb.ai/v1", "https://api.clarifai.com/v2/ext/openai/v1", "https://api.libertai.io/v1", + "https://ai.salad.cloud/v1", "https://pinstripes.io/v1", "https://api.meta.ai/v1", ] @@ -783,6 +784,7 @@ openai_compatible_providers: Final[list] = [ "chutes", # Chutes - JSON-configured provider "parasail", # Parasail - JSON-configured provider "libertai", # LibertAI - JSON-configured provider + "salad_cloud", "featherless_ai", "nscale", "nebius", diff --git a/litellm/llms/openai_like/providers.json b/litellm/llms/openai_like/providers.json index 164100d4194..773cf060c11 100644 --- a/litellm/llms/openai_like/providers.json +++ b/litellm/llms/openai_like/providers.json @@ -159,6 +159,14 @@ "max_completion_tokens": "max_tokens" } }, + "salad_cloud": { + "base_url": "https://ai.salad.cloud/v1", + "api_key_env": "SALAD_CLOUD_API_KEY", + "api_base_env": "SALAD_CLOUD_API_BASE", + "param_mappings": { + "max_completion_tokens": "max_tokens" + } + }, "empiriolabs": { "base_url": "https://api.empiriolabs.ai/v1", "api_key_env": "EMPIRIOLABS_API_KEY", diff --git a/litellm/model_prices_and_context_window_backup.json b/litellm/model_prices_and_context_window_backup.json index 02b3cde217a..42c9c346fd0 100644 --- a/litellm/model_prices_and_context_window_backup.json +++ b/litellm/model_prices_and_context_window_backup.json @@ -44210,6 +44210,23 @@ "supports_reasoning": true, "source": "https://docs.libertai.io/apis/text/" }, + "salad_cloud/qwen3.6-35b-a3b": { + "max_tokens": 262144, + "max_input_tokens": 262144, + "max_output_tokens": 262144, + "input_cost_per_token": 9e-08, + "output_cost_per_token": 6e-07, + "litellm_provider": "salad_cloud", + "mode": "chat", + "supports_function_calling": true, + "supports_tool_choice": true, + "supports_system_messages": true, + "supports_vision": true, + "supports_reasoning": true, + "supports_response_schema": true, + "supports_native_streaming": true, + "source": "https://docs.salad.com/ai-gateway/reference/models" + }, "libertai/qwen3.5-122b-a10b": { "max_tokens": 262144, "max_input_tokens": 262144, diff --git a/litellm/provider_endpoints_support_backup.json b/litellm/provider_endpoints_support_backup.json index dd7712aabca..ee947b03c05 100644 --- a/litellm/provider_endpoints_support_backup.json +++ b/litellm/provider_endpoints_support_backup.json @@ -1305,6 +1305,23 @@ "a2a": false } }, + "salad_cloud": { + "display_name": "Salad AI Gateway (`salad_cloud`)", + "url": "https://docs.salad.com/ai-gateway/tutorials/getting-started", + "endpoints": { + "chat_completions": true, + "messages": true, + "responses": false, + "embeddings": false, + "image_generations": false, + "audio_transcriptions": false, + "audio_speech": false, + "moderations": false, + "batches": false, + "rerank": false, + "a2a": false + } + }, "litellm_proxy": { "display_name": "LiteLLM Proxy (`litellm_proxy`)", "url": "https://docs.litellm.ai/docs/providers/litellm_proxy", diff --git a/litellm/types/utils.py b/litellm/types/utils.py index 77f83c5b6f8..bfa68782461 100644 --- a/litellm/types/utils.py +++ b/litellm/types/utils.py @@ -3688,6 +3688,7 @@ class LlmProviders(str, Enum): XIAOMI_MIMO = "xiaomi_mimo" TENSORMESH = "tensormesh" LIBERTAI = "libertai" + SALAD_CLOUD = "salad_cloud" PINSTRIPES = "pinstripes" DARKBLOOM = "darkbloom" META = "meta" diff --git a/model_prices_and_context_window.json b/model_prices_and_context_window.json index bc7330ec99c..6a6f8fd7320 100644 --- a/model_prices_and_context_window.json +++ b/model_prices_and_context_window.json @@ -44331,6 +44331,23 @@ "supports_reasoning": true, "source": "https://docs.libertai.io/apis/text/" }, + "salad_cloud/qwen3.6-35b-a3b": { + "max_tokens": 262144, + "max_input_tokens": 262144, + "max_output_tokens": 262144, + "input_cost_per_token": 9e-08, + "output_cost_per_token": 6e-07, + "litellm_provider": "salad_cloud", + "mode": "chat", + "supports_function_calling": true, + "supports_tool_choice": true, + "supports_system_messages": true, + "supports_vision": true, + "supports_reasoning": true, + "supports_response_schema": true, + "supports_native_streaming": true, + "source": "https://docs.salad.com/ai-gateway/reference/models" + }, "libertai/qwen3.5-122b-a10b": { "max_tokens": 262144, "max_input_tokens": 262144, diff --git a/provider_endpoints_support.json b/provider_endpoints_support.json index 65db63dc045..aa4e73c5b67 100644 --- a/provider_endpoints_support.json +++ b/provider_endpoints_support.json @@ -1420,6 +1420,23 @@ "a2a": false } }, + "salad_cloud": { + "display_name": "Salad AI Gateway (`salad_cloud`)", + "url": "https://docs.salad.com/ai-gateway/tutorials/getting-started", + "endpoints": { + "chat_completions": true, + "messages": true, + "responses": false, + "embeddings": false, + "image_generations": false, + "audio_transcriptions": false, + "audio_speech": false, + "moderations": false, + "batches": false, + "rerank": false, + "a2a": false + } + }, "litellm_proxy": { "display_name": "LiteLLM Proxy (`litellm_proxy`)", "url": "https://docs.litellm.ai/docs/providers/litellm_proxy", diff --git a/tests/test_litellm/llms/openai_like/test_salad_cloud_provider.py b/tests/test_litellm/llms/openai_like/test_salad_cloud_provider.py new file mode 100644 index 00000000000..27387b5a712 --- /dev/null +++ b/tests/test_litellm/llms/openai_like/test_salad_cloud_provider.py @@ -0,0 +1,100 @@ +import litellm + + +class TestSaladCloudProviderConfig: + def test_provider_registration(self): + from litellm import LlmProviders + + assert LlmProviders.SALAD_CLOUD.value == "salad_cloud" + assert "salad_cloud" in litellm.provider_list + + def test_json_config(self): + from litellm.llms.openai_like.json_loader import JSONProviderRegistry + + assert JSONProviderRegistry.exists("salad_cloud") + provider = JSONProviderRegistry.get("salad_cloud") + assert provider is not None + assert provider.base_url == "https://ai.salad.cloud/v1" + assert provider.api_key_env == "SALAD_CLOUD_API_KEY" + assert provider.api_base_env == "SALAD_CLOUD_API_BASE" + assert provider.param_mappings.get("max_completion_tokens") == "max_tokens" + + def test_provider_resolution(self): + from litellm.litellm_core_utils.get_llm_provider_logic import get_llm_provider + + model, provider, _, api_base = get_llm_provider( + model="salad_cloud/qwen3.6-35b-a3b", + custom_llm_provider=None, + api_base=None, + api_key=None, + ) + + assert model == "qwen3.6-35b-a3b" + assert provider == "salad_cloud" + assert api_base == "https://ai.salad.cloud/v1" + + def test_api_base_override(self): + from litellm.litellm_core_utils.get_llm_provider_logic import get_llm_provider + + _, provider, api_key, api_base = get_llm_provider( + model="salad_cloud/qwen3.6-35b-a3b", + custom_llm_provider=None, + api_base="https://custom.example.com/v1", + api_key="test-key", + ) + + assert provider == "salad_cloud" + assert api_base == "https://custom.example.com/v1" + assert api_key == "test-key" + + def test_model_metadata(self): + import json + from pathlib import Path + + cost_map_path = ( + Path(litellm.__file__).parent + / "model_prices_and_context_window_backup.json" + ) + info = json.loads(cost_map_path.read_text())["salad_cloud/qwen3.6-35b-a3b"] + + assert info["litellm_provider"] == "salad_cloud" + assert info["mode"] == "chat" + assert info["max_input_tokens"] == 262144 + assert info["max_output_tokens"] == 262144 + assert info["input_cost_per_token"] == 0.09e-6 + assert info["output_cost_per_token"] == 0.60e-6 + assert info["supports_vision"] is True + assert info["supports_reasoning"] is True + assert info["supports_response_schema"] is True + assert info["supports_native_streaming"] is True + + def test_router_config(self): + from litellm import Router + + router = Router( + model_list=[ + { + "model_name": "salad-chat", + "litellm_params": { + "model": "salad_cloud/qwen3.6-35b-a3b", + "api_key": "test-key", + }, + } + ] + ) + + assert router.model_list[0]["model_name"] == "salad-chat" + + def test_supported_endpoints_matrix(self): + import json + from pathlib import Path + + backup_path = ( + Path(litellm.__file__).parent / "provider_endpoints_support_backup.json" + ) + matrix = json.loads(backup_path.read_text()) + + endpoints = matrix["providers"]["salad_cloud"]["endpoints"] + assert endpoints["chat_completions"] is True + assert endpoints["messages"] is True + assert endpoints["responses"] is False