-
-
Notifications
You must be signed in to change notification settings - Fork 2.7k
Add Atlas Cloud chat completion provider #9288
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,40 @@ | ||
| from ..register import register_provider_adapter | ||
| from .openai_source import ProviderOpenAIOfficial | ||
|
|
||
| ATLASCLOUD_DEFAULT_API_BASE = "https://api.atlascloud.ai/v1" | ||
| ATLASCLOUD_DEFAULT_MODEL = "qwen/qwen3.5-flash" | ||
| ATLASCLOUD_MODELS = [ | ||
| "qwen/qwen3.5-flash", | ||
| "deepseek-ai/deepseek-v4-pro", | ||
| "deepseek-ai/deepseek-v4-flash", | ||
| ] | ||
|
|
||
|
|
||
| @register_provider_adapter( | ||
| "atlascloud_chat_completion", | ||
| "Atlas Cloud Chat Completion Provider Adapter", | ||
| ) | ||
| class ProviderAtlasCloud(ProviderOpenAIOfficial): | ||
| """Atlas Cloud provider using its OpenAI-compatible LLM endpoint.""" | ||
|
|
||
| def __init__( | ||
| self, | ||
| provider_config: dict, | ||
| provider_settings: dict, | ||
| ) -> None: | ||
| if not provider_config.get("api_base"): | ||
| provider_config["api_base"] = ATLASCLOUD_DEFAULT_API_BASE | ||
| if not provider_config.get("model"): | ||
| provider_config["model"] = ATLASCLOUD_DEFAULT_MODEL | ||
|
|
||
| super().__init__(provider_config, provider_settings) | ||
|
|
||
| async def get_models(self) -> list[str]: | ||
| try: | ||
| models = await super().get_models() | ||
| if models: | ||
| return models | ||
| except Exception: | ||
|
Comment on lines
+32
to
+37
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. suggestion (bug_risk): The broad exception handler in get_models silently swallows all errors, which could make debugging difficult. Catching Suggested implementation: async def get_models(self) -> list[str]:
try:
models = await super().get_models()
if models:
return models
except Exception as exc:
logger.warning(
"AtlasCloudSource.get_models: failed to fetch models from upstream; "
"falling back to static ATLASCLOUD_MODELS list.",
exc_info=exc,
)
return ATLASCLOUD_MODELSTo fully implement this change, ensure there is a module-level logger defined in this file, for example:
If this project uses a different logging convention (e.g., a shared logger utility), replace |
||
| pass | ||
|
|
||
| return ATLASCLOUD_MODELS.copy() | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -13,6 +13,7 @@ | |
| import astrbot.core.provider.sources.request_retry as request_retry | ||
| from astrbot.core.exceptions import EmptyModelOutputError | ||
| from astrbot.core.provider.entities import LLMResponse | ||
| from astrbot.core.provider.sources.atlascloud_source import ProviderAtlasCloud | ||
| from astrbot.core.provider.sources.groq_source import ProviderGroq | ||
| from astrbot.core.provider.sources.openai_source import ProviderOpenAIOfficial | ||
| from astrbot.core.utils.media_utils import ResolvedMediaData, file_uri_to_path | ||
|
|
@@ -60,6 +61,22 @@ def _make_groq_provider(overrides: dict | None = None) -> ProviderGroq: | |
| ) | ||
|
|
||
|
|
||
| def _make_atlascloud_provider( | ||
| overrides: dict | None = None, | ||
| ) -> ProviderAtlasCloud: | ||
| provider_config = { | ||
| "id": "test-atlascloud", | ||
| "type": "atlascloud_chat_completion", | ||
| "key": ["test-key"], | ||
| } | ||
| if overrides: | ||
| provider_config.update(overrides) | ||
| return ProviderAtlasCloud( | ||
| provider_config=provider_config, | ||
| provider_settings={}, | ||
| ) | ||
|
|
||
|
|
||
| def test_create_http_client_uses_openai_httpx_module(monkeypatch): | ||
| captured: dict[str, object] = {} | ||
|
|
||
|
|
@@ -120,6 +137,25 @@ def fake_import(name, globals=None, locals=None, fromlist=(), level=0): | |
| assert captured["httpx_module"] is openai_source_module.httpx | ||
|
|
||
|
|
||
| def test_atlascloud_provider_sets_default_openai_compatible_endpoint(): | ||
| provider = _make_atlascloud_provider() | ||
|
|
||
| assert str(provider.client.base_url).rstrip("/") == "https://api.atlascloud.ai/v1" | ||
| assert provider.get_model() == "qwen/qwen3.5-flash" | ||
|
Comment on lines
+140
to
+144
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. question (testing): Consider adding tests for explicit-but-falsy api_base/model values (e.g. empty strings)
Please add tests that exercise falsy-but-explicit values, for example:
This will clarify and lock in the expected behavior for falsy config values. |
||
|
|
||
|
|
||
| def test_atlascloud_provider_keeps_custom_endpoint_and_model(): | ||
| provider = _make_atlascloud_provider( | ||
| { | ||
| "api_base": "https://proxy.example.com/v1", | ||
| "model": "deepseek-ai/deepseek-v4-pro", | ||
| }, | ||
| ) | ||
|
|
||
| assert str(provider.client.base_url).rstrip("/") == "https://proxy.example.com/v1" | ||
| assert provider.get_model() == "deepseek-ai/deepseek-v4-pro" | ||
|
|
||
|
|
||
| @pytest.mark.asyncio | ||
| async def test_get_models_retries_transient_request_error(monkeypatch): | ||
| monkeypatch.setattr(request_retry, "REQUEST_RETRY_WAIT_MIN_S", 0) | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nitpick: Keep the default model DRY by reusing ATLASCLOUD_DEFAULT_MODEL in ATLASCLOUD_MODELS.
The default model string is defined both in
ATLASCLOUD_DEFAULT_MODELand inATLASCLOUD_MODELS, so any change would require updating two places. Referencing the constant in the list (e.g.,ATLASCLOUD_MODELS = [ATLASCLOUD_DEFAULT_MODEL, ...]) keeps them in sync.