Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .env.example
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
OPENAI_API_KEY=<your-openai-api-key>
ATLASCLOUD_API_KEY=<your-atlascloud-api-key>
ANTHROPIC_API_KEY=<your-anthropic-api-key>
VOYAGE_API_KEY=<your-voyage-api-key>
AZURE_EMBED_ENDPOINT=<your-azure-embed-endpoint>
AZURE_EMBED_DEPLOYMENT=<your-azure-embed-deployment>
AZURE_EMBED_API_VERSION=<your-azure-embed-api-version>
AZURE_EMBED_API_KEY=<your-azure-embed-api-key>
EXA_API_KEY=<your-exa-api-key>
EXA_API_KEY=<your-exa-api-key>
16 changes: 15 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ EvoAgentX is an open-source framework for building, evaluating, and evolving LLM
Agents don’t just work—they learn. EvoAgentX improves workflows using self-evolving algorithms.
- 🧩 **Plug-and-Play Compatibility**

Easily integrate original [OpenAI](https://github.com/EvoAgentX/EvoAgentX/blob/main/evoagentx/models/openai_model.py) and [qwen](https://github.com/EvoAgentX/EvoAgentX/blob/main/evoagentx/models/aliyun_model.py) or other popular models, including Claude, Deepseek, kimi models through ([LiteLLM](https://github.com/EvoAgentX/EvoAgentX/blob/main/evoagentx/models/litellm_model.py), [siliconflow](https://github.com/EvoAgentX/EvoAgentX/blob/main/evoagentx/models/siliconflow_model.py) or [openrouter](https://github.com/EvoAgentX/EvoAgentX/blob/main/evoagentx/models/openrouter_model.py)). If you want to use LLMs locally deployed on your own machine, you can try LiteLLM.
Easily integrate original [OpenAI](https://github.com/EvoAgentX/EvoAgentX/blob/main/evoagentx/models/openai_model.py) and [qwen](https://github.com/EvoAgentX/EvoAgentX/blob/main/evoagentx/models/aliyun_model.py) or other popular models, including Claude, Deepseek, kimi models through ([LiteLLM](https://github.com/EvoAgentX/EvoAgentX/blob/main/evoagentx/models/litellm_model.py), [siliconflow](https://github.com/EvoAgentX/EvoAgentX/blob/main/evoagentx/models/siliconflow_model.py), [openrouter](https://github.com/EvoAgentX/EvoAgentX/blob/main/evoagentx/models/openrouter_model.py), or [Atlas Cloud](https://www.atlascloud.ai/)). If you want to use LLMs locally deployed on your own machine, you can try LiteLLM.

- 🧰 **Comprehensive Built-in Tools**

Expand Down Expand Up @@ -248,6 +248,20 @@ llm = OpenAILLM(config=openai_config)
# Generate a response from the LLM
response = llm.generate(prompt="What is Agentic Workflow?")
```

Atlas Cloud is available as an optional OpenAI-compatible provider:

```python
import os
from evoagentx.models import AtlasCloudConfig, AtlasCloudLLM

llm = AtlasCloudLLM(
config=AtlasCloudConfig(
model="openai/gpt-5.6-luna",
atlascloud_key=os.getenv("ATLASCLOUD_API_KEY"),
)
)
```
> 📖 More details on supported models and config options: [LLM module guide](./docs/modules/llm.md).


Expand Down
1 change: 1 addition & 0 deletions evoagentx/models/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,4 @@
from .openrouter_model import *
from .aliyun_model import *
from .novita_model import *
from .atlascloud_model import *
47 changes: 47 additions & 0 deletions evoagentx/models/atlascloud_model.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
from openai import AsyncOpenAI, OpenAI
from openai.types.chat import ChatCompletion, ChatCompletionChunk

from ..core.logging import logger
from ..core.registry import register_model
from .model_configs import AtlasCloudConfig
from .model_utils import Cost, cost_manager
from .openai_model import OpenAILLM

ATLASCLOUD_BASE_URL = "https://api.atlascloud.ai/v1"


@register_model(config_cls=AtlasCloudConfig, alias=["atlascloud"])
class AtlasCloudLLM(OpenAILLM):
"""Atlas Cloud client for its OpenAI-compatible chat-completions API."""

def init_model(self):
self._client = None
self._async_client = None
self._default_ignore_fields = [
"llm_type",
"atlascloud_key",
"output_response",
]

def _init_client(self, config: AtlasCloudConfig):
return OpenAI(api_key=config.atlascloud_key, base_url=ATLASCLOUD_BASE_URL)

def _init_async_client(self, config: AtlasCloudConfig):
return AsyncOpenAI(api_key=config.atlascloud_key, base_url=ATLASCLOUD_BASE_URL)

def _update_cost(self, response: ChatCompletion | ChatCompletionChunk):
usage = getattr(response, "usage", None)
if usage is None:
logger.warning(
f"[AtlasCloudLLM] usage is missing from response "
f"(id={getattr(response, 'id', '?')}); tokens will not be recorded."
)
return
cost_manager.update_cost(
cost=Cost(
input_tokens=usage.prompt_tokens,
output_tokens=usage.completion_tokens,
cost=0.0,
),
model=self.config.model,
)
28 changes: 28 additions & 0 deletions evoagentx/models/model_configs.py
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,34 @@ def __str__(self):
return self.model


class AtlasCloudConfig(LLMConfig):

llm_type: str = "AtlasCloudLLM"
atlascloud_key: Optional[str] = Field(
default=None,
description="the API key used to authenticate Atlas Cloud requests",
)

# generation parameters
temperature: Optional[float] = Field(default=None, description="the temperature used to scaling logits")
max_tokens: Optional[int] = Field(default=None, description="maximum number of generated tokens")
max_completion_tokens: Optional[int] = Field(default=None, description="An upper bound for the number of tokens that can be generated for a completion, including visible output tokens and reasoning tokens.")
top_p: Optional[float] = Field(default=None, description="Only sample from tokens with cumulative probability greater than top_p when generating text.")
n: Optional[int] = Field(default=None, description="How many chat completion choices to generate for each input message.")
stream: Optional[bool] = Field(default=None, description="If set to true, it sends partial message deltas as they become available.")
stream_options: Optional[dict] = Field(default=None, description="Options for streaming responses.")
timeout: Optional[Union[float, int]] = Field(default=None, description="Timeout in seconds for completion requests.")

# tools and output format
tools: Optional[List] = Field(default=None, description="A list of tools the model may call.")
tool_choice: Optional[Union[str, dict]] = Field(default=None, description="Controls which tool the model may call.")
parallel_tool_calls: Optional[bool] = Field(default=None, description="Whether to enable parallel function calling during tool use.")
response_format: Optional[Union[BaseModel, dict]] = Field(default=None, description="An object specifying the format that the model must output.")

def __str__(self):
return self.model


class NovitaConfig(LLMConfig):

# LLM keys
Expand Down
63 changes: 63 additions & 0 deletions tests/src/models/test_atlascloud_model.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
from unittest.mock import MagicMock

from openai.types.completion_usage import CompletionUsage

from evoagentx.core.registry import MODEL_REGISTRY
from evoagentx.models.atlascloud_model import ATLASCLOUD_BASE_URL, AtlasCloudLLM
from evoagentx.models.model_configs import AtlasCloudConfig
from evoagentx.models.model_utils import cost_manager


def _config() -> AtlasCloudConfig:
return AtlasCloudConfig(
model="openai/gpt-5.6-luna",
atlascloud_key="test-key",
output_response=False,
)


def test_atlascloud_model_is_registered():
assert MODEL_REGISTRY.get_model("AtlasCloudLLM") is AtlasCloudLLM
assert MODEL_REGISTRY.get_model("atlascloud") is AtlasCloudLLM
assert MODEL_REGISTRY.get_model_config("atlascloud") is AtlasCloudConfig


def test_atlascloud_clients_use_the_provider_endpoint(mocker):
sync_client = mocker.patch("evoagentx.models.atlascloud_model.OpenAI")
async_client = mocker.patch("evoagentx.models.atlascloud_model.AsyncOpenAI")
llm = AtlasCloudLLM(config=_config())

llm._init_client(llm.config)
llm._init_async_client(llm.config)

sync_client.assert_called_once_with(
api_key="test-key", base_url=ATLASCLOUD_BASE_URL
)
async_client.assert_called_once_with(
api_key="test-key", base_url=ATLASCLOUD_BASE_URL
)


def test_atlascloud_credentials_are_not_sent_as_completion_params():
llm = AtlasCloudLLM(config=_config())

assert llm.get_completion_params() == {"model": "openai/gpt-5.6-luna"}


def test_atlascloud_records_tokens_without_guessing_cost():
model = "openai/gpt-5.6-luna"
cost_manager.input_tokens.clear()
cost_manager.output_tokens.clear()
cost_manager.total_tokens.clear()
cost_manager.cost_per_model.clear()
response = MagicMock(
id="atlas-test",
usage=CompletionUsage(prompt_tokens=12, completion_tokens=5, total_tokens=17),
)

AtlasCloudLLM(config=_config())._update_cost(response)

assert cost_manager.input_tokens[model] == 12
assert cost_manager.output_tokens[model] == 5
assert cost_manager.total_tokens[model] == 17
assert cost_manager.cost_per_model[model] == 0.0