use std::sync::Arc;
use crate::error::LlmConnectorError;
use crate::protocols::{GenericProvider, OpenAIProtocol, AnthropicProtocol, AliyunProtocol, OllamaProtocol};
use crate::config::ProviderConfig;
use crate::types::{ChatRequest, ChatResponse};
pub struct LlmClient {
provider: Arc<dyn crate::protocols::Provider + Send + Sync>,
}
impl LlmClient {
#[allow(dead_code)]
pub(crate) fn from_provider(provider: Arc<dyn crate::protocols::Provider + Send + Sync>) -> Self {
Self { provider }
}
pub(crate) fn provider_dyn(&self) -> &dyn crate::protocols::Provider {
&*self.provider
}
pub fn openai(api_key: &str, base_url: Option<&str>) -> Self {
let base = base_url.unwrap_or("https://api.openai.com/v1");
let protocol = OpenAIProtocol::with_url(api_key, base);
let config = ProviderConfig::new(api_key)
.with_base_url(base);
let provider = GenericProvider::new(config, protocol)
.expect("Failed to create OpenAI provider");
Self { provider: Arc::new(provider) }
}
pub fn aliyun(api_key: &str) -> Self {
let protocol = AliyunProtocol::new(api_key);
let config = ProviderConfig::new(api_key)
.with_base_url("https://dashscope.aliyuncs.com/api/v1/services/aigc/text-generation/generation");
let provider = GenericProvider::new(config, protocol)
.expect("Failed to create Aliyun provider");
Self { provider: Arc::new(provider) }
}
pub fn ollama(base_url: Option<&str>) -> Self {
let base = base_url.unwrap_or("http://localhost:11434");
let protocol = OllamaProtocol::with_url(base);
let config = ProviderConfig::new("") .with_base_url(base);
let provider = GenericProvider::new(config, protocol)
.expect("Failed to create Ollama provider");
Self { provider: Arc::new(provider) }
}
pub fn longcat(api_key: &str, anthropic: bool) -> Self {
let base_url = if anthropic {
"https://api.longcat.chat/anthropic"
} else {
"https://api.longcat.chat/openai/v1"
};
let config = ProviderConfig::new(api_key).with_base_url(base_url);
if anthropic {
let protocol = AnthropicProtocol::with_url(api_key, base_url);
let provider = GenericProvider::new(config, protocol)
.expect("Failed to create LongCat Anthropic-compatible provider");
Self { provider: Arc::new(provider) }
} else {
let protocol = OpenAIProtocol::with_url(api_key, base_url);
let provider = GenericProvider::new(config, protocol)
.expect("Failed to create LongCat OpenAI-compatible provider");
Self { provider: Arc::new(provider) }
}
}
pub fn anthropic(api_key: &str) -> Self {
let protocol = AnthropicProtocol::new(api_key);
let config = ProviderConfig::new(api_key)
.with_base_url("https://api.anthropic.com");
let provider = GenericProvider::new(config, protocol)
.expect("Failed to create Anthropic provider");
Self {
provider: Arc::new(provider),
}
}
pub async fn chat(&self, request: &ChatRequest) -> Result<ChatResponse, LlmConnectorError> {
self.provider.chat(request).await
}
#[cfg(feature = "streaming")]
pub async fn chat_stream(&self, request: &ChatRequest) -> Result<crate::types::ChatStream, LlmConnectorError> {
self.provider.chat_stream(request).await
}
pub async fn fetch_models(&self) -> Result<Vec<String>, LlmConnectorError> {
self.provider.fetch_models().await
}
pub fn protocol_name(&self) -> &str {
self.provider.name()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn test_client_creation() {
let _openai = LlmClient::openai("test-key", None);
let _anthropic = LlmClient::anthropic("test-key");
let _aliyun = LlmClient::aliyun("test-key");
let _ollama = LlmClient::ollama(None);
}
#[test]
fn test_protocol_names() {
let openai_client = LlmClient::openai("test-key", None);
assert_eq!(openai_client.protocol_name(), "openai");
let anthropic_client = LlmClient::anthropic("test-key");
assert_eq!(anthropic_client.protocol_name(), "anthropic");
let aliyun_client = LlmClient::aliyun("test-key");
assert_eq!(aliyun_client.protocol_name(), "aliyun");
let ollama_client = LlmClient::ollama(None);
assert_eq!(ollama_client.protocol_name(), "ollama");
}
}