diff --git a/litellm/constants.py b/litellm/constants.py index fb765c0226c..d1f0013d474 100644 --- a/litellm/constants.py +++ b/litellm/constants.py @@ -615,6 +615,7 @@ LITELLM_CHAT_PROVIDERS = [ "dashscope", "moonshot", "publicai", + "skypool", "v0", "heroku", "oci", @@ -775,6 +776,7 @@ openai_compatible_endpoints: List = [ "https://nano-gpt.com/api/v1", "https://api.poe.com/v1", "https://llm.chutes.ai/v1/", + "https://a.skypool.xyz/v1", "https://api.v0.dev/v1", "https://api.morphllm.com/v1", "https://api.lambda.ai/v1", @@ -824,6 +826,7 @@ openai_compatible_providers: List = [ "nano-gpt", # Nano-GPT - JSON-configured provider "poe", # Poe - JSON-configured provider "chutes", # Chutes - JSON-configured provider + "skypool", # Skypool - JSON-configured provider "featherless_ai", "nscale", "nebius", diff --git a/litellm/llms/openai_like/providers.json b/litellm/llms/openai_like/providers.json index b5e5aa4ea28..910e8040d3c 100644 --- a/litellm/llms/openai_like/providers.json +++ b/litellm/llms/openai_like/providers.json @@ -114,5 +114,13 @@ "param_mappings": { "max_completion_tokens": "max_tokens" } + }, + "skypool": { + "base_url": "https://a.skypool.xyz/v1", + "api_key_env": "SKYPOOL_API_KEY", + "api_base_env": "SKYPOOL_API_BASE", + "param_mappings": { + "max_completion_tokens": "max_tokens" + } } } diff --git a/litellm/types/utils.py b/litellm/types/utils.py index 282baff07fe..cfb49265c6f 100644 --- a/litellm/types/utils.py +++ b/litellm/types/utils.py @@ -3354,6 +3354,7 @@ class LlmProviders(str, Enum): NANOGPT = "nano-gpt" POE = "poe" CHUTES = "chutes" + SKYPOOL = "skypool" XIAOMI_MIMO = "xiaomi_mimo" LITELLM_AGENT = "litellm_agent" CURSOR = "cursor" diff --git a/provider_endpoints_support.json b/provider_endpoints_support.json index 388752b032e..d31a8310d18 100644 --- a/provider_endpoints_support.json +++ b/provider_endpoints_support.json @@ -2063,6 +2063,22 @@ "interactions": true } }, + "skypool": { + "display_name": "Skypool (`skypool`)", + "endpoints": { + "chat_completions": true, + "messages": false, + "responses": false, + "embeddings": false, + "image_generations": false, + "audio_transcriptions": false, + "audio_speech": false, + "moderations": false, + "batches": false, + "rerank": false, + "a2a": false + } + }, "synthetic": { "display_name": "Synthetic (`synthetic`)", "endpoints": { diff --git a/tests/litellm/llms/openai_like/test_skypool_provider.py b/tests/litellm/llms/openai_like/test_skypool_provider.py new file mode 100644 index 00000000000..9987650057b --- /dev/null +++ b/tests/litellm/llms/openai_like/test_skypool_provider.py @@ -0,0 +1,87 @@ +""" +Unit tests for the Skypool OpenAI-compatible provider. +""" + +import os +import sys + +sys.path.insert( + 0, os.path.abspath(os.path.join(os.path.dirname(__file__), "../../../..")) +) + +from litellm.llms.openai_like.dynamic_config import create_config_class +from litellm.llms.openai_like.json_loader import JSONProviderRegistry + +SKYPOOL_BASE_URL = "https://a.skypool.xyz/v1" + + +def _get_config(): + provider = JSONProviderRegistry.get("skypool") + assert provider is not None + config_class = create_config_class(provider) + return config_class() + + +def test_skypool_provider_registered(): + provider = JSONProviderRegistry.get("skypool") + assert provider is not None + assert provider.base_url == SKYPOOL_BASE_URL + assert provider.api_key_env == "SKYPOOL_API_KEY" + assert provider.api_base_env == "SKYPOOL_API_BASE" + assert provider.param_mappings["max_completion_tokens"] == "max_tokens" + + +def test_skypool_resolves_env_api_key(monkeypatch): + config = _get_config() + monkeypatch.setenv("SKYPOOL_API_KEY", "test-key") + api_base, api_key = config._get_openai_compatible_provider_info(None, None) + assert api_base == SKYPOOL_BASE_URL + assert api_key == "test-key" + + +def test_skypool_resolves_env_api_base(monkeypatch): + config = _get_config() + monkeypatch.setenv("SKYPOOL_API_BASE", "https://example.com/v1") + api_base, api_key = config._get_openai_compatible_provider_info(None, "test-key") + assert api_base == "https://example.com/v1" + assert api_key == "test-key" + + +def test_skypool_complete_url_appends_endpoint(): + config = _get_config() + url = config.get_complete_url( + api_base=SKYPOOL_BASE_URL, + api_key="test-key", + model="skypool/gemma4:26b", + optional_params={}, + litellm_params={}, + stream=False, + ) + assert url == f"{SKYPOOL_BASE_URL}/chat/completions" + + +def test_skypool_provider_resolution(): + from litellm.litellm_core_utils.get_llm_provider_logic import get_llm_provider + + model, provider, api_key, api_base = get_llm_provider( + model="skypool/gemma4:26b", + custom_llm_provider=None, + api_base=None, + api_key=None, + ) + + assert model == "gemma4:26b" + assert provider == "skypool" + assert api_base == SKYPOOL_BASE_URL + + +def test_skypool_provider_config_manager(): + from litellm import LlmProviders + from litellm.utils import ProviderConfigManager + + config = ProviderConfigManager.get_provider_chat_config( + model="gemma4:26b", provider=LlmProviders.SKYPOOL + ) + + assert config is not None + assert config.custom_llm_provider == "skypool" diff --git a/ui/litellm-dashboard/public/assets/logos/skypool.png b/ui/litellm-dashboard/public/assets/logos/skypool.png new file mode 100644 index 00000000000..17b5f848a0d Binary files /dev/null and b/ui/litellm-dashboard/public/assets/logos/skypool.png differ diff --git a/ui/litellm-dashboard/src/components/provider_info_helpers.test.tsx b/ui/litellm-dashboard/src/components/provider_info_helpers.test.tsx index ab22b0ef49a..ec5e6952897 100644 --- a/ui/litellm-dashboard/src/components/provider_info_helpers.test.tsx +++ b/ui/litellm-dashboard/src/components/provider_info_helpers.test.tsx @@ -62,6 +62,12 @@ describe("provider_info_helpers", () => { expect(result.logo).toBe(providerLogoMap[Providers.Groq]); }); + it("should map skypool provider value to Skypool display name and logo", () => { + const result = getProviderLogoAndName("skypool"); + expect(result.displayName).toBe(Providers.Skypool); + expect(result.logo).toBe(providerLogoMap[Providers.Skypool]); + }); + it("should handle provider values case-insensitively", () => { const result = getProviderLogoAndName("OPENAI"); expect(result.displayName).toBe(Providers.OpenAI); @@ -138,6 +144,10 @@ describe("provider_info_helpers", () => { expect(getPlaceholder(Providers.Snowflake)).toBe("snowflake/mistral-7b"); }); + it("should return skypool placeholder for Skypool provider", () => { + expect(getPlaceholder(Providers.Skypool)).toBe("skypool/gemma4:26b"); + }); + it("should return voyage placeholder for Voyage provider", () => { expect(getPlaceholder(Providers.Voyage)).toBe("voyage/"); }); diff --git a/ui/litellm-dashboard/src/components/provider_info_helpers.tsx b/ui/litellm-dashboard/src/components/provider_info_helpers.tsx index 105951114ca..c9ed1e454e0 100644 --- a/ui/litellm-dashboard/src/components/provider_info_helpers.tsx +++ b/ui/litellm-dashboard/src/components/provider_info_helpers.tsx @@ -87,6 +87,7 @@ export enum Providers { Sambanova = "Sambanova", SAP = "SAP Generative AI Hub", Snowflake = "Snowflake", + Skypool = "Skypool", TEXT_COMPLETION_CODESTRAL = "Text-Completion-Codestral", TogetherAI = "TogetherAI", TOPAZ = "Topaz", @@ -195,6 +196,7 @@ export const provider_map: Record = { Sambanova: "sambanova", SAP: "sap", Snowflake: "snowflake", + Skypool: "skypool", TEXT_COMPLETION_CODESTRAL: "text-completion-codestral", TogetherAI: "together_ai", TOPAZ: "topaz", @@ -286,6 +288,7 @@ export const providerLogoMap: Record = { [Providers.Sambanova]: `${asset_logos_folder}sambanova.svg`, [Providers.SAP]: `${asset_logos_folder}sap.png`, [Providers.Snowflake]: `${asset_logos_folder}snowflake.svg`, + [Providers.Skypool]: `${asset_logos_folder}skypool.png`, [Providers.TEXT_COMPLETION_CODESTRAL]: `${asset_logos_folder}mistral.svg`, [Providers.TogetherAI]: `${asset_logos_folder}togetherai.svg`, [Providers.TOPAZ]: `${asset_logos_folder}topaz.svg`, @@ -352,6 +355,8 @@ export const getPlaceholder = (selectedProvider: string): string => { return "oci/xai.grok-4"; } else if (selectedProvider == Providers.Snowflake) { return "snowflake/mistral-7b"; + } else if (selectedProvider == Providers.Skypool) { + return "skypool/gemma4:26b"; } else if (selectedProvider == Providers.Voyage) { return "voyage/"; } else if (selectedProvider == Providers.JinaAI) {