diff --git a/docs/docs/models/providers/additional.md b/docs/docs/models/providers/additional.md index b25742005..420b38e10 100644 --- a/docs/docs/models/providers/additional.md +++ b/docs/docs/models/providers/additional.md @@ -2,8 +2,8 @@ title: Additional Providers social: title: Additional Providers - tagline: Configure Groq, Aliyun, OpenRouter, TensorZero, and generic endpoints. - description: Configure Groq, Aliyun, OpenRouter, TensorZero, and generic endpoints. + tagline: Configure Atlas Cloud, Groq, Aliyun, OpenRouter, TensorZero, and generic endpoints. + description: Configure Atlas Cloud, Groq, Aliyun, OpenRouter, TensorZero, and generic endpoints. alt: fast-agent social card — Additional model providers --- @@ -33,6 +33,7 @@ Run `fast-agent check` after adding credentials to confirm they are visible to f | Provider | Config key | API key environment variable | Default endpoint | Model string examples | | --- | --- | --- | --- | --- | +| Atlas Cloud | `atlascloud` | `ATLASCLOUD_API_KEY` | `https://api.atlascloud.ai/v1` | `atlascloud.qwen/qwen3.8-max` | | Groq | `groq` | `GROQ_API_KEY` | `https://api.groq.com/openai/v1` | `groq.openai/gpt-oss-120b` | | Aliyun | `aliyun` | `ALIYUN_API_KEY` | `https://dashscope-intl.aliyuncs.com/compatible-mode/v1` | `qwen-turbo`, `aliyun.qwen3-max` | | OpenRouter | `openrouter` | `OPENROUTER_API_KEY` | `https://openrouter.ai/api/v1` | `openrouter.google/gemini-2.5-pro-exp-03-25:free` | @@ -48,6 +49,21 @@ Run `fast-agent check` after adding credentials to confirm they are visible to f Use these when the provider exposes an OpenAI-compatible API but has its own credentials, model catalog, or small behavior differences. +### Atlas Cloud + +```yaml +atlascloud: + api_key: "${ATLASCLOUD_API_KEY}" +``` + +```bash +fast-agent --model atlascloud.qwen/qwen3.8-max +``` + +Atlas Cloud uses its OpenAI-compatible Chat Completions endpoint. The provider +defaults to `qwen/qwen3.8-max`; set `default_model` or pass an explicit model +string to use another model from the Atlas Cloud catalog. + ### Groq ```yaml diff --git a/examples/setup/fast-agent.yaml b/examples/setup/fast-agent.yaml index 38529cd1b..1d255d479 100644 --- a/examples/setup/fast-agent.yaml +++ b/examples/setup/fast-agent.yaml @@ -86,6 +86,12 @@ anthropic: # # base_url: "https://api.moonshot.ai/v1" # # default_model: "kimi-k3" +# Atlas Cloud OpenAI-compatible Chat Completions provider: +# atlascloud: +# api_key: "${ATLASCLOUD_API_KEY}" +# # base_url: "https://api.atlascloud.ai/v1" +# # default_model: "qwen/qwen3.8-max" + #shell_execution: # model-facing shell tools: auto (Grok shell/Process; others Bash/Process), # minimal_process, grok_shell, or native diff --git a/src/fast_agent/cli/commands/check_config.py b/src/fast_agent/cli/commands/check_config.py index bf61193b4..70a661b8f 100644 --- a/src/fast_agent/cli/commands/check_config.py +++ b/src/fast_agent/cli/commands/check_config.py @@ -126,6 +126,10 @@ class ProviderCatalogScope: display_name="OpenRouter", providers=(Provider.OPENROUTER,), ), + "atlascloud": ProviderCatalogScope( + display_name="Atlas Cloud", + providers=(Provider.ATLASCLOUD,), + ), } _PROVIDER_CATALOG_SCOPE_ALIASES: dict[str, str] = { @@ -147,6 +151,7 @@ class ProviderCatalogScope: "xai", "metaai", "openrouter", + "atlascloud", "responses", "codexresponses", ) diff --git a/src/fast_agent/config.py b/src/fast_agent/config.py index 361794679..749db8135 100644 --- a/src/fast_agent/config.py +++ b/src/fast_agent/config.py @@ -1513,6 +1513,26 @@ class OpenRouterSettings(BaseModel): model_config = ConfigDict(extra="allow", arbitrary_types_allowed=True) +class AtlasCloudSettings(BaseModel): + """Settings for Atlas Cloud's OpenAI-compatible API.""" + + api_key: str | None = Field(default=None, description="Atlas Cloud API key") + base_url: str | None = Field( + default=None, + description="Override API endpoint (default: https://api.atlascloud.ai/v1)", + ) + default_model: str | None = Field( + default=None, + description="Default model when Atlas Cloud is selected without an explicit model", + ) + default_headers: dict[str, str] | None = Field( + default=None, + description="Custom headers for all API requests", + ) + + model_config = ConfigDict(extra="allow", arbitrary_types_allowed=True) + + class AzureSettings(BaseModel): """Settings for using Azure OpenAI Service in the fast-agent application.""" @@ -2314,6 +2334,9 @@ def _migrate_legacy_mcp_settings(cls, values: Any) -> Any: openrouter: OpenRouterSettings | None = None """Settings for using OpenRouter models in the fast-agent application""" + atlascloud: AtlasCloudSettings | None = None + """Settings for using Atlas Cloud models in the fast-agent application""" + generic: GenericSettings | None = None """Settings for using Generic models in the fast-agent application""" diff --git a/src/fast_agent/llm/model_factory.py b/src/fast_agent/llm/model_factory.py index 20f417b12..51ca019c0 100644 --- a/src/fast_agent/llm/model_factory.py +++ b/src/fast_agent/llm/model_factory.py @@ -131,6 +131,10 @@ def __call__(self, **kwargs: Any) -> FastAgentLLMProtocol: ... "MetaAIResponsesLLM", ), Provider.OPENROUTER: ("fast_agent.llm.provider.openai.llm_openrouter", "OpenRouterLLM"), + Provider.ATLASCLOUD: ( + "fast_agent.llm.provider.openai.llm_atlascloud", + "AtlasCloudLLM", + ), Provider.TENSORZERO: ( "fast_agent.llm.provider.openai.llm_tensorzero_openai", "TensorZeroOpenAILLM", diff --git a/src/fast_agent/llm/model_selection.py b/src/fast_agent/llm/model_selection.py index bd02efcbc..ea1a078bf 100644 --- a/src/fast_agent/llm/model_selection.py +++ b/src/fast_agent/llm/model_selection.py @@ -135,6 +135,14 @@ class ModelSelectionCatalog: Provider.ZAI: (_builtin_entry("zaiglm", display_label="GLM 5.2"),), Provider.MOONSHOT: (_builtin_entry("kimik3", display_label="Kimi K3"),), Provider.OPENROUTER: (), + Provider.ATLASCLOUD: ( + CatalogModelEntry( + alias="qwen3.8-max", + model="atlascloud.qwen/qwen3.8-max", + display_label="Qwen3.8 Max", + fast=True, + ), + ), Provider.ALIYUN: ( _builtin_entry("qwen-turbo", fast=True), _builtin_entry("qwen3-max"), diff --git a/src/fast_agent/llm/provider/openai/llm_atlascloud.py b/src/fast_agent/llm/provider/openai/llm_atlascloud.py new file mode 100644 index 000000000..0690806c2 --- /dev/null +++ b/src/fast_agent/llm/provider/openai/llm_atlascloud.py @@ -0,0 +1,24 @@ +from fast_agent.llm.provider.openai.llm_openai_compatible import OpenAICompatibleLLM +from fast_agent.llm.provider_types import Provider +from fast_agent.types import RequestParams + +ATLASCLOUD_BASE_URL = "https://api.atlascloud.ai/v1" +DEFAULT_ATLASCLOUD_MODEL = "qwen/qwen3.8-max" + + +class AtlasCloudLLM(OpenAICompatibleLLM): + """Atlas Cloud provider using its OpenAI-compatible Chat Completions API.""" + + def __init__(self, **kwargs) -> None: + kwargs.pop("provider", None) + super().__init__(provider=Provider.ATLASCLOUD, **kwargs) + + def _initialize_default_params(self, kwargs: dict) -> RequestParams: + return self._initialize_default_params_with_model_fallback(kwargs, DEFAULT_ATLASCLOUD_MODEL) + + def _provider_base_url(self) -> str: + base_url = None + if self.context.config and self.context.config.atlascloud: + base_url = self.context.config.atlascloud.base_url + + return base_url if base_url else ATLASCLOUD_BASE_URL diff --git a/src/fast_agent/llm/provider_types.py b/src/fast_agent/llm/provider_types.py index ef20a98d8..f59515322 100644 --- a/src/fast_agent/llm/provider_types.py +++ b/src/fast_agent/llm/provider_types.py @@ -32,6 +32,7 @@ def config_name(self) -> str: GOOGLE = ("google", "Google") # For Google GenAI native library OPENAI = ("openai", "OpenAI") OPENROUTER = ("openrouter", "OpenRouter") + ATLASCLOUD = ("atlascloud", "Atlas Cloud") TENSORZERO = ("tensorzero", "TensorZero") # For TensorZero Gateway AZURE = ("azure", "Azure") # Azure OpenAI Service ALIYUN = ("aliyun", "Aliyun") # Aliyun Bailian OpenAI Service diff --git a/src/fast_agent/ui/model_picker_common.py b/src/fast_agent/ui/model_picker_common.py index 71cbbdde8..e4bcbd2c9 100644 --- a/src/fast_agent/ui/model_picker_common.py +++ b/src/fast_agent/ui/model_picker_common.py @@ -48,6 +48,7 @@ Provider.BEDROCK, Provider.ALIYUN, Provider.OPENROUTER, + Provider.ATLASCLOUD, Provider.FAST_AGENT, ) diff --git a/tests/unit/fast_agent/commands/test_check_config_model_catalog.py b/tests/unit/fast_agent/commands/test_check_config_model_catalog.py index 3aa44d416..3bf47222d 100644 --- a/tests/unit/fast_agent/commands/test_check_config_model_catalog.py +++ b/tests/unit/fast_agent/commands/test_check_config_model_catalog.py @@ -118,6 +118,15 @@ def test_show_provider_model_catalog_moonshot_includes_curated_model(capsys) -> assert "moonshot.kimi-k3" in _collapse(output) +def test_show_provider_model_catalog_atlascloud_includes_curated_model(capsys) -> None: + show_provider_model_catalog("atlascloud") + + output = capsys.readouterr().out + assert "Atlas Cloud model catalog (curated)" in output + assert "qwen3.8-max" in output + assert "atlascloud.qwen/qwen3.8-max" in _collapse(output) + + def test_show_models_overview_includes_provider_args_and_named_aliases( tmp_path: Path, capsys, diff --git a/tests/unit/fast_agent/llm/providers/test_provider_default_models.py b/tests/unit/fast_agent/llm/providers/test_provider_default_models.py index 7f4243563..0688d9d57 100644 --- a/tests/unit/fast_agent/llm/providers/test_provider_default_models.py +++ b/tests/unit/fast_agent/llm/providers/test_provider_default_models.py @@ -13,6 +13,7 @@ import os from fast_agent.config import ( + AtlasCloudSettings, AzureSettings, DeepSeekSettings, HuggingFaceSettings, @@ -25,6 +26,7 @@ from fast_agent.context import Context from fast_agent.llm.model_database import ModelDatabase from fast_agent.llm.provider.google.llm_google_native import GoogleNativeLLM +from fast_agent.llm.provider.openai.llm_atlascloud import AtlasCloudLLM from fast_agent.llm.provider.openai.llm_azure import AzureOpenAILLM from fast_agent.llm.provider.openai.llm_deepseek import DeepSeekResponsesLLM from fast_agent.llm.provider.openai.llm_generic import GenericLLM @@ -155,6 +157,26 @@ def test_openrouter_provider_base_url_prefers_config_over_env() -> None: os.environ["OPENROUTER_BASE_URL"] = original_env +def test_atlascloud_provider_uses_default_model_and_base_url() -> None: + llm = AtlasCloudLLM(context=Context(config=Settings()), model="") + + assert llm.default_request_params.model == "qwen/qwen3.8-max" + assert llm._base_url() == "https://api.atlascloud.ai/v1" + + +def test_atlascloud_provider_uses_config_overrides() -> None: + settings = Settings( + atlascloud=AtlasCloudSettings( + base_url="https://atlas-gateway.example/v1", + default_model="openai/gpt-oss-120b", + ) + ) + llm = AtlasCloudLLM(context=Context(config=settings), model="") + + assert llm.default_request_params.model == "openai/gpt-oss-120b" + assert llm._base_url() == "https://atlas-gateway.example/v1" + + def test_huggingface_provider_default_model_used_with_provider_suffix() -> None: settings = Settings( hf=HuggingFaceSettings( diff --git a/tests/unit/fast_agent/llm/test_model_factory.py b/tests/unit/fast_agent/llm/test_model_factory.py index e85c0645f..c9f825342 100644 --- a/tests/unit/fast_agent/llm/test_model_factory.py +++ b/tests/unit/fast_agent/llm/test_model_factory.py @@ -22,6 +22,7 @@ from fast_agent.llm.model_selection import ModelSelectionCatalog from fast_agent.llm.provider.anthropic.llm_anthropic import AnthropicLLM from fast_agent.llm.provider.anthropic.llm_anthropic_vertex import AnthropicVertexLLM +from fast_agent.llm.provider.openai.llm_atlascloud import AtlasCloudLLM from fast_agent.llm.provider.openai.llm_generic import GenericLLM from fast_agent.llm.provider.openai.llm_huggingface import HuggingFaceLLM from fast_agent.llm.provider.openai.llm_openai import OpenAILLM @@ -87,6 +88,18 @@ def test_full_model_strings(): assert config.reasoning_effort == exp_effort +def test_atlascloud_model_string_and_factory() -> None: + config = ModelFactory.parse_model_string("atlascloud.qwen/qwen3.8-max") + assert config.provider == Provider.ATLASCLOUD + assert config.model_name == "qwen/qwen3.8-max" + + factory = ModelFactory.create_factory("atlascloud.qwen/qwen3.8-max") + llm = factory(LlmAgent(AgentConfig(name="Atlas Cloud Test"))) + + assert isinstance(llm, AtlasCloudLLM) + assert llm.provider == Provider.ATLASCLOUD + + def test_deprecated_reasoning_suffix_is_rejected() -> None: with pytest.raises(ModelConfigError, match=r"Use '\?reasoning=' instead"): ModelFactory.parse_model_string("openai.o1.high") diff --git a/tests/unit/fast_agent/llm/test_model_selection_catalog.py b/tests/unit/fast_agent/llm/test_model_selection_catalog.py index 6073bc278..a247c7525 100644 --- a/tests/unit/fast_agent/llm/test_model_selection_catalog.py +++ b/tests/unit/fast_agent/llm/test_model_selection_catalog.py @@ -463,6 +463,15 @@ def test_openrouter_all_models_use_discovered_models_when_no_curated(monkeypatch assert models == ["openrouter.openai/gpt-4.1-mini"] +def test_atlascloud_catalog_includes_default_model() -> None: + entries = ModelSelectionCatalog.list_current_entries(Provider.ATLASCLOUD) + + assert len(entries) == 1 + assert entries[0].alias == "qwen3.8-max" + assert entries[0].model == "atlascloud.qwen/qwen3.8-max" + assert entries[0].fast is True + + def test_overlay_catalog_uses_explicit_environment_context( monkeypatch, tmp_path: Path,