mirror of
https://github.com/affaan-m/everything-claude-code.git
synced 2026-06-23 08:31:26 +08:00
feat: add Atlas Cloud as LLM/AI provider (#2279)
* feat: add Atlas Cloud as OpenAI-compatible LLM provider - Add Atlas Cloud env vars to .env.example (ATLAS_API_KEY, ATLAS_BASE_URL) - Add docs/ATLAS-CLOUD-GUIDE.md with configuration, model list, and usage example - Atlas Cloud provides 59+ LLM models via OpenAI-compatible API at https://api.atlascloud.ai/v1 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat(atlascloud): add Atlas Cloud provider implementation Wire Atlas Cloud in as a first-class OpenAI-compatible LLM provider, complementing the existing .env.example/docs entries. - src/llm/providers/atlas.py: AtlasProvider adapter (base_url https://api.atlascloud.ai/v1, default model deepseek-ai/deepseek-v4-pro); floors max_tokens to 512 for reasoning models; reads ATLAS_API_KEY (falls back to ATLASCLOUD_API_KEY), ATLAS_BASE_URL, ATLAS_MODEL - src/llm/core/types.py: add ProviderType.ATLAS - providers __init__/resolver: export + register AtlasProvider - tests: test_atlas_provider.py + resolver coverage for "atlas" Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,161 @@
|
||||
from types import SimpleNamespace
|
||||
|
||||
from llm.core.types import LLMInput, Message, ProviderType, Role, ToolCall, ToolDefinition
|
||||
from llm.providers.atlas import ATLAS_BASE_URL, DEFAULT_ATLAS_MAX_TOKENS, DEFAULT_ATLAS_MODEL, AtlasProvider
|
||||
|
||||
|
||||
def _tool() -> ToolDefinition:
|
||||
return ToolDefinition(
|
||||
name="search",
|
||||
description="Search",
|
||||
parameters={"type": "object", "properties": {"query": {"type": "string"}}},
|
||||
)
|
||||
|
||||
|
||||
class _Completions:
|
||||
def __init__(self, response: SimpleNamespace) -> None:
|
||||
self.params = None
|
||||
self.response = response
|
||||
|
||||
def create(self, **params):
|
||||
self.params = params
|
||||
return self.response
|
||||
|
||||
|
||||
class _Client:
|
||||
def __init__(self, response: SimpleNamespace) -> None:
|
||||
self.completions = _Completions(response)
|
||||
self.chat = SimpleNamespace(completions=self.completions)
|
||||
|
||||
|
||||
def _response(**overrides) -> SimpleNamespace:
|
||||
message = SimpleNamespace(content="ok", tool_calls=None)
|
||||
choice = SimpleNamespace(message=message, finish_reason="stop")
|
||||
defaults = {
|
||||
"choices": [choice],
|
||||
"model": DEFAULT_ATLAS_MODEL,
|
||||
"usage": SimpleNamespace(prompt_tokens=1, completion_tokens=2, total_tokens=3),
|
||||
}
|
||||
defaults.update(overrides)
|
||||
return SimpleNamespace(**defaults)
|
||||
|
||||
|
||||
def test_atlas_provider_defaults_to_atlas_cloud_endpoint(monkeypatch):
|
||||
monkeypatch.delenv("ATLAS_API_KEY", raising=False)
|
||||
monkeypatch.delenv("ATLASCLOUD_API_KEY", raising=False)
|
||||
monkeypatch.delenv("ATLAS_BASE_URL", raising=False)
|
||||
monkeypatch.delenv("ATLAS_MODEL", raising=False)
|
||||
|
||||
provider = AtlasProvider()
|
||||
|
||||
assert provider.provider_type == ProviderType.ATLAS
|
||||
assert provider.base_url == ATLAS_BASE_URL
|
||||
assert provider.get_default_model() == DEFAULT_ATLAS_MODEL
|
||||
assert provider.validate_config() is False
|
||||
|
||||
|
||||
def test_atlas_provider_reads_env_overrides(monkeypatch):
|
||||
monkeypatch.setenv("ATLAS_API_KEY", "atlas-key")
|
||||
monkeypatch.setenv("ATLAS_MODEL", "deepseek-ai/deepseek-v3.2")
|
||||
|
||||
provider = AtlasProvider()
|
||||
|
||||
assert provider.get_default_model() == "deepseek-ai/deepseek-v3.2"
|
||||
assert provider.validate_config() is True
|
||||
|
||||
|
||||
def test_atlas_provider_accepts_atlascloud_api_key_fallback(monkeypatch):
|
||||
monkeypatch.delenv("ATLAS_API_KEY", raising=False)
|
||||
monkeypatch.setenv("ATLASCLOUD_API_KEY", "atlascloud-key")
|
||||
|
||||
provider = AtlasProvider()
|
||||
|
||||
assert provider.api_key == "atlascloud-key"
|
||||
assert provider.validate_config() is True
|
||||
|
||||
|
||||
def test_atlas_provider_generates_openai_compatible_chat_completion():
|
||||
provider = AtlasProvider(api_key="test", default_model=DEFAULT_ATLAS_MODEL)
|
||||
client = _Client(_response(model=DEFAULT_ATLAS_MODEL))
|
||||
provider.client = client
|
||||
|
||||
output = provider.generate(
|
||||
LLMInput(
|
||||
messages=[Message(role=Role.USER, content="hi")],
|
||||
max_tokens=1024,
|
||||
tools=[_tool()],
|
||||
)
|
||||
)
|
||||
|
||||
assert output.content == "ok"
|
||||
assert output.model == DEFAULT_ATLAS_MODEL
|
||||
assert output.usage == {"prompt_tokens": 1, "completion_tokens": 2, "total_tokens": 3}
|
||||
assert client.completions.params["model"] == DEFAULT_ATLAS_MODEL
|
||||
assert client.completions.params["max_tokens"] == 1024
|
||||
assert "temperature" not in client.completions.params
|
||||
assert client.completions.params["tools"] == [
|
||||
{
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "search",
|
||||
"description": "Search",
|
||||
"parameters": {"type": "object", "properties": {"query": {"type": "string"}}},
|
||||
"strict": True,
|
||||
},
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
def test_atlas_provider_floors_max_tokens_for_reasoning_models():
|
||||
provider = AtlasProvider(api_key="test")
|
||||
client = _Client(_response())
|
||||
provider.client = client
|
||||
|
||||
# No max_tokens supplied -> floored to the reasoning default.
|
||||
provider.generate(LLMInput(messages=[Message(role=Role.USER, content="hi")]))
|
||||
assert client.completions.params["max_tokens"] == DEFAULT_ATLAS_MAX_TOKENS
|
||||
|
||||
# Too-small max_tokens is also raised to the floor.
|
||||
provider.generate(LLMInput(messages=[Message(role=Role.USER, content="hi")], max_tokens=16))
|
||||
assert client.completions.params["max_tokens"] == DEFAULT_ATLAS_MAX_TOKENS
|
||||
|
||||
|
||||
def test_atlas_provider_forwards_non_default_temperature():
|
||||
provider = AtlasProvider(api_key="test")
|
||||
client = _Client(_response())
|
||||
provider.client = client
|
||||
|
||||
provider.generate(LLMInput(messages=[Message(role=Role.USER, content="hi")], temperature=0.2))
|
||||
|
||||
assert client.completions.params["temperature"] == 0.2
|
||||
|
||||
|
||||
def test_atlas_provider_parses_tool_calls():
|
||||
provider = AtlasProvider(api_key="test")
|
||||
tool_call = SimpleNamespace(
|
||||
id="call_1",
|
||||
function=SimpleNamespace(name="search", arguments='{"query":"atlas"}'),
|
||||
)
|
||||
message = SimpleNamespace(content="", tool_calls=[tool_call])
|
||||
client = _Client(_response(choices=[SimpleNamespace(message=message, finish_reason="tool_calls")], usage=None))
|
||||
provider.client = client
|
||||
|
||||
output = provider.generate(LLMInput(messages=[Message(role=Role.USER, content="hi")]))
|
||||
|
||||
assert output.tool_calls == [ToolCall(id="call_1", name="search", arguments={"query": "atlas"})]
|
||||
assert output.usage is None
|
||||
|
||||
|
||||
def test_atlas_provider_preserves_malformed_tool_arguments():
|
||||
provider = AtlasProvider(api_key="test")
|
||||
tool_call = SimpleNamespace(
|
||||
id="call_1",
|
||||
function=SimpleNamespace(name="search", arguments="{not-json"),
|
||||
)
|
||||
message = SimpleNamespace(content="", tool_calls=[tool_call])
|
||||
client = _Client(_response(choices=[SimpleNamespace(message=message, finish_reason="tool_calls")]))
|
||||
provider.client = client
|
||||
|
||||
output = provider.generate(LLMInput(messages=[Message(role=Role.USER, content="hi")]))
|
||||
|
||||
assert output.tool_calls == [ToolCall(id="call_1", name="search", arguments={"raw": "{not-json"})]
|
||||
@@ -1,6 +1,6 @@
|
||||
import pytest
|
||||
from llm.core.types import ProviderType
|
||||
from llm.providers import AstraflowCNProvider, AstraflowProvider, ClaudeProvider, OpenAIProvider, OllamaProvider, get_provider
|
||||
from llm.providers import AstraflowCNProvider, AstraflowProvider, AtlasProvider, ClaudeProvider, OpenAIProvider, OllamaProvider, get_provider
|
||||
|
||||
|
||||
class TestGetProvider:
|
||||
@@ -29,6 +29,11 @@ class TestGetProvider:
|
||||
assert isinstance(provider, AstraflowCNProvider)
|
||||
assert provider.provider_type == ProviderType.ASTRAFLOW_CN
|
||||
|
||||
def test_get_atlas_provider(self):
|
||||
provider = get_provider("atlas")
|
||||
assert isinstance(provider, AtlasProvider)
|
||||
assert provider.provider_type == ProviderType.ATLAS
|
||||
|
||||
def test_get_provider_by_enum(self):
|
||||
provider = get_provider(ProviderType.CLAUDE)
|
||||
assert isinstance(provider, ClaudeProvider)
|
||||
|
||||
Reference in New Issue
Block a user