Add Atlas Cloud LLM provider

This commit is contained in:
nb213 2026-07-13 21:38:44 +08:00 committed by Valerio
parent 4072861677
commit 0124ab9df3
4 changed files with 92 additions and 7 deletions

View file

@ -1,5 +1,5 @@
/// Provider chain — tries providers in order until one succeeds.
/// Default order: Ollama (local, free) -> OpenAI -> Gemini -> Anthropic.
/// Default order: Ollama (local, free) -> OpenAI -> Atlas Cloud -> Gemini -> Anthropic.
/// Only includes providers that are actually configured/available.
use async_trait::async_trait;
use tracing::{debug, warn};
@ -7,8 +7,8 @@ use tracing::{debug, warn};
use crate::error::LlmError;
use crate::provider::{CompletionRequest, LlmProvider};
use crate::providers::{
anthropic::AnthropicProvider, gemini::GeminiProvider, ollama::OllamaProvider,
openai::OpenAiProvider,
anthropic::AnthropicProvider, atlascloud::AtlasCloudProvider, gemini::GeminiProvider,
ollama::OllamaProvider, openai::OpenAiProvider,
};
pub struct ProviderChain {
@ -16,7 +16,7 @@ pub struct ProviderChain {
}
impl ProviderChain {
/// Build the default chain: Ollama -> OpenAI -> Gemini -> Anthropic.
/// Build the default chain: Ollama -> OpenAI -> Atlas Cloud -> Gemini -> Anthropic.
/// Ollama is always added (availability checked at call time).
/// Cloud providers are only added if their API keys are configured.
/// Gemini sits ahead of Anthropic so Google Cloud credits are preferred,
@ -37,6 +37,11 @@ impl ProviderChain {
providers.push(Box::new(openai));
}
if let Some(atlascloud) = AtlasCloudProvider::new(None, None) {
debug!("atlascloud configured, adding to chain");
providers.push(Box::new(atlascloud));
}
if let Some(gemini) = GeminiProvider::new(None, None, None) {
debug!("gemini configured, adding to chain");
providers.push(Box::new(gemini));

View file

@ -0,0 +1,71 @@
/// Atlas Cloud provider — OpenAI-compatible chat completions with Atlas defaults.
use async_trait::async_trait;
use crate::error::LlmError;
use crate::provider::{CompletionRequest, LlmProvider};
use super::openai::OpenAiProvider;
pub struct AtlasCloudProvider {
inner: OpenAiProvider,
}
impl AtlasCloudProvider {
/// Returns `None` if no Atlas Cloud API key is available (param or env).
pub fn new(key_override: Option<String>, model: Option<String>) -> Option<Self> {
let key = super::load_api_key(key_override, "ATLASCLOUD_API_KEY")?;
let base_url = std::env::var("ATLASCLOUD_BASE_URL")
.ok()
.unwrap_or_else(|| "https://api.atlascloud.ai/v1".into());
let model = model.unwrap_or_else(|| "qwen/qwen3.5-flash".into());
let inner = OpenAiProvider::new(Some(key), Some(base_url), Some(model))?;
Some(Self { inner })
}
pub fn default_model(&self) -> &str {
self.inner.default_model()
}
}
#[async_trait]
impl LlmProvider for AtlasCloudProvider {
async fn complete(&self, request: &CompletionRequest) -> Result<String, LlmError> {
self.inner.complete(request).await
}
async fn is_available(&self) -> bool {
self.inner.is_available().await
}
fn name(&self) -> &str {
"atlascloud"
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn empty_key_returns_none() {
assert!(AtlasCloudProvider::new(Some(String::new()), None).is_none());
}
#[test]
fn explicit_key_constructs_with_atlas_defaults() {
let provider = AtlasCloudProvider::new(Some("test-key".into()), None)
.expect("should construct");
assert_eq!(provider.name(), "atlascloud");
assert_eq!(provider.default_model(), "qwen/qwen3.5-flash");
}
#[test]
fn explicit_model_override() {
let provider = AtlasCloudProvider::new(
Some("test-key".into()),
Some("deepseek-ai/deepseek-v4-pro".into()),
)
.expect("should construct");
assert_eq!(provider.default_model(), "deepseek-ai/deepseek-v4-pro");
}
}

View file

@ -1,4 +1,5 @@
pub mod anthropic;
pub mod atlascloud;
pub mod gemini;
pub mod ollama;
pub mod openai;