diff --git a/cascadeflow/providers/__init__.py b/cascadeflow/providers/__init__.py index dd70def8..8354d190 100644 --- a/cascadeflow/providers/__init__.py +++ b/cascadeflow/providers/__init__.py @@ -18,6 +18,7 @@ _LAZY_PROVIDERS: dict[str, str] = { "AnthropicProvider": ".anthropic", + "AtlasCloudProvider": ".atlascloud", "DeepSeekProvider": ".deepseek", "GroqProvider": ".groq", "HuggingFaceProvider": ".huggingface", @@ -57,6 +58,7 @@ def _build_provider_registry() -> dict: registry = {} _name_to_key = { "OpenAIProvider": "openai", + "AtlasCloudProvider": "atlascloud", "AnthropicProvider": "anthropic", "OllamaProvider": "ollama", "GroqProvider": "groq", @@ -137,6 +139,7 @@ def get_available_providers() -> dict[str, BaseProvider]: "ModelResponse", "PROVIDER_CAPABILITIES", "OpenAIProvider", + "AtlasCloudProvider", "AnthropicProvider", "OllamaProvider", "GroqProvider", diff --git a/cascadeflow/providers/atlascloud.py b/cascadeflow/providers/atlascloud.py new file mode 100644 index 00000000..d320b73a --- /dev/null +++ b/cascadeflow/providers/atlascloud.py @@ -0,0 +1,53 @@ +"""Atlas Cloud provider implementation. + +Atlas Cloud exposes an OpenAI-compatible chat completions API, so the provider +reuses the OpenAI provider implementation with Atlas-specific defaults. + +Environment Variables: + ATLASCLOUD_API_KEY: Your Atlas Cloud API key + +Models: + - qwen/qwen3.5-flash: Fast default chat model + - deepseek-ai/deepseek-v4-pro: Reasoning-capable chat model +""" + +import os +from typing import Optional + +from .openai import OpenAIProvider + + +class AtlasCloudProvider(OpenAIProvider): + """Atlas Cloud provider using the OpenAI-compatible API.""" + + BASE_URL = "https://api.atlascloud.ai/v1" + + def __init__( + self, + api_key: Optional[str] = None, + base_url: Optional[str] = None, + **kwargs, + ): + """ + Initialize Atlas Cloud provider. + + Args: + api_key: Atlas Cloud API key (defaults to ATLASCLOUD_API_KEY env var) + base_url: Custom base URL (defaults to Atlas Cloud API) + **kwargs: Additional OpenAI provider options + """ + atlascloud_api_key = api_key or os.getenv("ATLASCLOUD_API_KEY") + + if not atlascloud_api_key: + raise ValueError( + "Atlas Cloud API key not found. " + "Set ATLASCLOUD_API_KEY environment variable or pass api_key parameter." + ) + + super().__init__(api_key=atlascloud_api_key, **kwargs) + self.base_url = base_url or self.BASE_URL + + @property + def name(self) -> str: + """Provider name.""" + return "atlascloud" diff --git a/cascadeflow/providers/base.py b/cascadeflow/providers/base.py index f133db14..80458fee 100644 --- a/cascadeflow/providers/base.py +++ b/cascadeflow/providers/base.py @@ -947,6 +947,7 @@ def _get_litellm_prefix(self) -> Optional[str]: # Map provider class names to LiteLLM prefixes provider_prefixes = { "OpenAIProvider": None, # Native format + "AtlasCloudProvider": "openai", # Atlas Cloud uses OpenAI-compatible format "AnthropicProvider": None, # Native format "GroqProvider": "groq", "TogetherProvider": "together_ai", diff --git a/docs/INSTALLATION.md b/docs/INSTALLATION.md index 6e44cdec..63a38d93 100644 --- a/docs/INSTALLATION.md +++ b/docs/INSTALLATION.md @@ -184,6 +184,7 @@ python examples/multi_provider.py | pydantic | Core validation | ✅ Always | requirements.txt | | httpx | Core HTTP client | ✅ Always | requirements.txt | | openai | OpenAIProvider | ❌ Optional | `[openai]` or `[providers]` or `[all]` | +| atlascloud | AtlasCloudProvider | ❌ Optional | core package + `ATLASCLOUD_API_KEY` | | anthropic | AnthropicProvider | ❌ Optional | `[anthropic]` or `[providers]` or `[all]` | | groq | GroqProvider | ❌ Optional | `[groq]` or `[providers]` or `[all]` | | huggingface-hub | HuggingFaceProvider | ❌ Optional | `[huggingface]` or `[all]` | diff --git a/tests/test_atlascloud.py b/tests/test_atlascloud.py new file mode 100644 index 00000000..999fa5ca --- /dev/null +++ b/tests/test_atlascloud.py @@ -0,0 +1,46 @@ +"""Tests for Atlas Cloud provider.""" + +import os +from unittest.mock import patch + +import pytest + +from cascadeflow.providers import PROVIDER_REGISTRY +from cascadeflow.providers.atlascloud import AtlasCloudProvider + + +def test_init_with_api_key(): + """Test initialization with explicit API key.""" + provider = AtlasCloudProvider(api_key="atlas-test-key") + + assert provider.api_key == "atlas-test-key" + assert provider.base_url == "https://api.atlascloud.ai/v1" + assert provider.name == "atlascloud" + + +def test_init_from_env(): + """Test initialization from ATLASCLOUD_API_KEY.""" + with patch.dict(os.environ, {"ATLASCLOUD_API_KEY": "atlas-env-key"}, clear=True): + provider = AtlasCloudProvider() + + assert provider.api_key == "atlas-env-key" + assert provider.base_url == "https://api.atlascloud.ai/v1" + + +def test_init_no_api_key(): + """Test initialization fails without Atlas Cloud API key.""" + with patch.dict(os.environ, {}, clear=True): + with pytest.raises(ValueError, match="Atlas Cloud API key not found"): + AtlasCloudProvider() + + +def test_custom_base_url(): + """Test custom Atlas Cloud-compatible base URL.""" + provider = AtlasCloudProvider(api_key="atlas-test-key", base_url="https://proxy.test/v1") + + assert provider.base_url == "https://proxy.test/v1" + + +def test_registered_provider(): + """Test Atlas Cloud is registered for cascade agents.""" + assert PROVIDER_REGISTRY["atlascloud"] is AtlasCloudProvider