From 7ebb17b5c3715694aaaf9af3265cf911be5786d7 Mon Sep 17 00:00:00 2001 From: binyangzhu000-sudo <224954946+binyangzhu000-sudo@users.noreply.github.com> Date: Fri, 7 Aug 2026 18:44:05 +0800 Subject: [PATCH] feat: add Atlas Cloud model provider Signed-off-by: binyangzhu000-sudo <224954946+binyangzhu000-sudo@users.noreply.github.com> --- .../constants/model_provider_constants.py | 2 + .../atlas_cloud_model_provider/__init__.py | 0 .../atlas_cloud_model_provider.py | 42 +++++++++ .../atlas_cloud_model_provider/constants.py | 1 + .../credential/__init__.py | 0 .../credential/llm.py | 92 +++++++++++++++++++ .../model/__init__.py | 0 .../atlas_cloud_model_provider/model/llm.py | 21 +++++ apps/models_provider/tests.py | 63 ++++++++++++- .../items/model/provider-data.ts | 5 + 10 files changed, 224 insertions(+), 2 deletions(-) create mode 100644 apps/models_provider/impl/atlas_cloud_model_provider/__init__.py create mode 100644 apps/models_provider/impl/atlas_cloud_model_provider/atlas_cloud_model_provider.py create mode 100644 apps/models_provider/impl/atlas_cloud_model_provider/constants.py create mode 100644 apps/models_provider/impl/atlas_cloud_model_provider/credential/__init__.py create mode 100644 apps/models_provider/impl/atlas_cloud_model_provider/credential/llm.py create mode 100644 apps/models_provider/impl/atlas_cloud_model_provider/model/__init__.py create mode 100644 apps/models_provider/impl/atlas_cloud_model_provider/model/llm.py diff --git a/apps/models_provider/constants/model_provider_constants.py b/apps/models_provider/constants/model_provider_constants.py index 4533e0fca9b..3d092d7387e 100644 --- a/apps/models_provider/constants/model_provider_constants.py +++ b/apps/models_provider/constants/model_provider_constants.py @@ -1,6 +1,7 @@ # coding=utf-8 from enum import Enum +from models_provider.impl.atlas_cloud_model_provider.atlas_cloud_model_provider import AtlasCloudModelProvider from models_provider.impl.aliyun_bai_lian_model_provider.aliyun_bai_lian_model_provider import \ AliyunBaiLianModelProvider from models_provider.impl.anthropic_model_provider.anthropic_model_provider import AnthropicModelProvider @@ -28,6 +29,7 @@ class ModelProvideConstants(Enum): + model_atlas_cloud_provider = AtlasCloudModelProvider() model_azure_provider = AzureModelProvider() model_wenxin_provider = WenxinModelProvider() model_ollama_provider = OllamaModelProvider() diff --git a/apps/models_provider/impl/atlas_cloud_model_provider/__init__.py b/apps/models_provider/impl/atlas_cloud_model_provider/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/apps/models_provider/impl/atlas_cloud_model_provider/atlas_cloud_model_provider.py b/apps/models_provider/impl/atlas_cloud_model_provider/atlas_cloud_model_provider.py new file mode 100644 index 00000000000..c8485d24c4c --- /dev/null +++ b/apps/models_provider/impl/atlas_cloud_model_provider/atlas_cloud_model_provider.py @@ -0,0 +1,42 @@ +from models_provider.base_model_provider import ( + IModelProvider, + ModelInfo, + ModelInfoManage, + ModelProvideInfo, + ModelTypeConst, +) +from models_provider.impl.atlas_cloud_model_provider.credential.llm import AtlasCloudLLMModelCredential +from models_provider.impl.atlas_cloud_model_provider.model.llm import AtlasCloudChatModel + + +atlas_cloud_llm_model_credential = AtlasCloudLLMModelCredential() +atlas_cloud_llm_list = [ + ModelInfo("google/gemini-2.5-flash", "", ModelTypeConst.LLM, atlas_cloud_llm_model_credential, AtlasCloudChatModel), + ModelInfo("google/gemini-2.5-pro", "", ModelTypeConst.LLM, atlas_cloud_llm_model_credential, AtlasCloudChatModel), + ModelInfo( + "anthropic/claude-sonnet-4.6", "", ModelTypeConst.LLM, atlas_cloud_llm_model_credential, AtlasCloudChatModel + ), + ModelInfo("openai/gpt-4o", "", ModelTypeConst.LLM, atlas_cloud_llm_model_credential, AtlasCloudChatModel), + ModelInfo( + "deepseek-ai/deepseek-v3.2", "", ModelTypeConst.LLM, atlas_cloud_llm_model_credential, AtlasCloudChatModel + ), + ModelInfo("moonshotai/kimi-k2.5", "", ModelTypeConst.LLM, atlas_cloud_llm_model_credential, AtlasCloudChatModel), + ModelInfo("zai-org/glm-5", "", ModelTypeConst.LLM, atlas_cloud_llm_model_credential, AtlasCloudChatModel), + ModelInfo("openai/gpt-5.4", "", ModelTypeConst.LLM, atlas_cloud_llm_model_credential, AtlasCloudChatModel), + ModelInfo("qwen/qwen3.7-max", "", ModelTypeConst.LLM, atlas_cloud_llm_model_credential, AtlasCloudChatModel), +] + +model_info_manage = ( + ModelInfoManage.builder() + .append_model_info_list(atlas_cloud_llm_list) + .append_default_model_info(atlas_cloud_llm_list[0]) + .build() +) + + +class AtlasCloudModelProvider(IModelProvider): + def get_model_info_manage(self): + return model_info_manage + + def get_model_provide_info(self): + return ModelProvideInfo(provider="model_atlas_cloud_provider", name="Atlas Cloud", icon="") diff --git a/apps/models_provider/impl/atlas_cloud_model_provider/constants.py b/apps/models_provider/impl/atlas_cloud_model_provider/constants.py new file mode 100644 index 00000000000..c5ebc43b8ee --- /dev/null +++ b/apps/models_provider/impl/atlas_cloud_model_provider/constants.py @@ -0,0 +1 @@ +ATLAS_CLOUD_API_BASE = "https://api.atlascloud.ai/v1" diff --git a/apps/models_provider/impl/atlas_cloud_model_provider/credential/__init__.py b/apps/models_provider/impl/atlas_cloud_model_provider/credential/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/apps/models_provider/impl/atlas_cloud_model_provider/credential/llm.py b/apps/models_provider/impl/atlas_cloud_model_provider/credential/llm.py new file mode 100644 index 00000000000..85c0e3d367e --- /dev/null +++ b/apps/models_provider/impl/atlas_cloud_model_provider/credential/llm.py @@ -0,0 +1,92 @@ +from typing import Dict + +from django.utils.translation import gettext, gettext_lazy as _ +from langchain_core.messages import HumanMessage + +from common import forms +from common.exception.app_exception import AppApiException +from common.forms import BaseForm, TooltipLabel +from common.utils.logger import maxkb_logger +from models_provider.base_model_provider import BaseModelCredential, ValidCode +from models_provider.impl.atlas_cloud_model_provider.constants import ATLAS_CLOUD_API_BASE + + +class AtlasCloudLLMModelParams(BaseForm): + temperature = forms.SliderField( + TooltipLabel( + _("Temperature"), + _("Higher values make the output more random, while lower values make it more focused and deterministic"), + ), + required=True, + default_value=0.7, + _min=0.1, + _max=1.0, + _step=0.01, + precision=2, + ) + + max_tokens = forms.SliderField( + TooltipLabel( + _("Output the maximum Tokens"), + _("Specify the maximum number of tokens that the model can generate"), + ), + required=True, + default_value=8192, + _min=1, + _max=100000, + _step=1, + precision=0, + ) + + +class AtlasCloudLLMModelCredential(BaseForm, BaseModelCredential): + def is_valid( + self, + model_type: str, + model_name, + model_credential: Dict[str, object], + model_params, + provider, + raise_exception=False, + ): + model_type_list = provider.get_model_type_list() + if not any(item.get("value") == model_type for item in model_type_list): + raise AppApiException( + ValidCode.valid_error.value, + gettext("{model_type} Model type is not supported").format(model_type=model_type), + ) + + for key in ["api_base", "api_key"]: + if key not in model_credential: + if raise_exception: + raise AppApiException( + ValidCode.valid_error.value, + gettext("{key} is required").format(key=key), + ) + return False + + try: + model = provider.get_model(model_type, model_name, model_credential, **model_params) + model.invoke([HumanMessage(content=gettext("Hello"))]) + except Exception as exc: + maxkb_logger.error("Exception: %s", exc, exc_info=True) + if isinstance(exc, AppApiException): + raise exc + if raise_exception: + raise AppApiException( + ValidCode.valid_error.value, + gettext("Verification failed, please check whether the parameters are correct: {error}").format( + error=str(exc) + ), + ) + return False + return True + + def encryption_dict(self, model: Dict[str, object]): + return {**model, "api_key": super().encryption(model.get("api_key", ""))} + + api_base = forms.TextInputField(_("API URL"), required=True, default_value=ATLAS_CLOUD_API_BASE) + api_key = forms.PasswordInputField("API Key", required=True) + + def get_model_params_setting_form(self, model_name): + return AtlasCloudLLMModelParams() diff --git a/apps/models_provider/impl/atlas_cloud_model_provider/model/__init__.py b/apps/models_provider/impl/atlas_cloud_model_provider/model/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/apps/models_provider/impl/atlas_cloud_model_provider/model/llm.py b/apps/models_provider/impl/atlas_cloud_model_provider/model/llm.py new file mode 100644 index 00000000000..c604bca095a --- /dev/null +++ b/apps/models_provider/impl/atlas_cloud_model_provider/model/llm.py @@ -0,0 +1,21 @@ +from typing import Dict + +from models_provider.base_model_provider import MaxKBBaseModel +from models_provider.impl.atlas_cloud_model_provider.constants import ATLAS_CLOUD_API_BASE +from models_provider.impl.base_chat_open_ai import BaseChatOpenAI + + +class AtlasCloudChatModel(MaxKBBaseModel, BaseChatOpenAI): + @staticmethod + def is_cache_model(): + return False + + @staticmethod + def new_instance(model_type, model_name, model_credential: Dict[str, object], **model_kwargs): + optional_params = MaxKBBaseModel.filter_optional_params(model_kwargs) + return AtlasCloudChatModel( + model=model_name, + openai_api_base=model_credential.get("api_base") or ATLAS_CLOUD_API_BASE, + openai_api_key=model_credential.get("api_key"), + **optional_params, + ) diff --git a/apps/models_provider/tests.py b/apps/models_provider/tests.py index 7ce503c2dd9..b6be95fc6f6 100644 --- a/apps/models_provider/tests.py +++ b/apps/models_provider/tests.py @@ -1,3 +1,62 @@ -from django.test import TestCase +from unittest import TestCase +from unittest.mock import patch -# Create your tests here. +from models_provider.impl.atlas_cloud_model_provider.atlas_cloud_model_provider import AtlasCloudModelProvider +from models_provider.impl.atlas_cloud_model_provider.constants import ATLAS_CLOUD_API_BASE +from models_provider.impl.atlas_cloud_model_provider.credential.llm import AtlasCloudLLMModelCredential +from models_provider.impl.atlas_cloud_model_provider.model.llm import AtlasCloudChatModel + + +class AtlasCloudModelProviderTest(TestCase): + def test_provider_metadata(self): + provider = AtlasCloudModelProvider() + + self.assertIsInstance(provider, AtlasCloudModelProvider) + self.assertEqual( + provider.get_model_provide_info().to_dict(), + {"provider": "model_atlas_cloud_provider", "name": "Atlas Cloud", "icon": ""}, + ) + + def test_provider_exposes_current_model_ids(self): + provider = AtlasCloudModelProvider() + + model_names = {model["name"] for model in provider.get_model_list("LLM")} + + self.assertIn("google/gemini-2.5-flash", model_names) + self.assertIn("anthropic/claude-sonnet-4.6", model_names) + self.assertIn("openai/gpt-5.4", model_names) + + def test_credential_defaults_to_atlas_cloud_api(self): + credential = AtlasCloudLLMModelCredential() + + self.assertEqual(credential.api_base.default_value, ATLAS_CLOUD_API_BASE) + + def test_model_uses_openai_compatible_configuration(self): + factory = AtlasCloudChatModel.new_instance + with patch( + "models_provider.impl.atlas_cloud_model_provider.model.llm.AtlasCloudChatModel" + ) as model_constructor: + model = factory( + "LLM", + "google/gemini-2.5-flash", + {"api_key": "test-key", "api_base": ""}, + temperature=0.2, + streaming=True, + model_id="internal-id", + ) + + self.assertIs(model, model_constructor.return_value) + model_constructor.assert_called_once_with( + model="google/gemini-2.5-flash", + openai_api_base=ATLAS_CLOUD_API_BASE, + openai_api_key="test-key", + temperature=0.2, + ) + + def test_api_key_is_masked(self): + credential = AtlasCloudLLMModelCredential() + + encrypted = credential.encryption_dict({"api_key": "test-secret-api-key", "api_base": ATLAS_CLOUD_API_BASE}) + + self.assertNotEqual(encrypted["api_key"], "test-secret-api-key") + self.assertEqual(encrypted["api_base"], ATLAS_CLOUD_API_BASE) diff --git a/ui/src/components/dynamics-form/items/model/provider-data.ts b/ui/src/components/dynamics-form/items/model/provider-data.ts index 5e7b39b82c6..8402cd35faf 100644 --- a/ui/src/components/dynamics-form/items/model/provider-data.ts +++ b/ui/src/components/dynamics-form/items/model/provider-data.ts @@ -108,5 +108,10 @@ export const providerList = [ "provider": "model_minimax_provider", "name": "MiniMax", "icon": "" + }, + { + "provider": "model_atlas_cloud_provider", + "name": "Atlas Cloud", + "icon": "" } ]