use crate::core::language_models::{BaseChatModel, LLMResult};
use crate::error::Error;
use crate::schema::Message;
use crate::RunnableConfig;
use async_trait::async_trait;
use std::sync::Arc;
pub struct LLMClient {
inner: Arc<dyn BaseChatModel<Error = Error> + Send + Sync>,
}
impl std::fmt::Debug for LLMClient {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("LLMClient")
.field("model_name", &self.inner.model_name())
.finish()
}
}
impl LLMClient {
pub fn from_env() -> Result<Self, String> {
if std::env::var("OPENAI_API_KEY").is_ok() {
let config = crate::language_models::openai::OpenAIConfig::from_env_result()?;
return Ok(Self::openai(config));
}
if std::env::var("ANTHROPIC_API_KEY").is_ok() {
let config =
crate::language_models::providers::anthropic::config::AnthropicConfig::from_env_result()?;
return Ok(Self::anthropic(config));
}
if std::env::var("OLLAMA_BASE_URL").is_ok() {
let config = crate::language_models::ollama::config::OllamaConfig::from_env_result()?;
return Ok(Self::ollama(config));
}
Err(
"No LLM provider detected. Set one of: OPENAI_API_KEY, ANTHROPIC_API_KEY, OLLAMA_BASE_URL"
.to_string(),
)
}
pub fn openai(config: crate::language_models::openai::OpenAIConfig) -> Self {
let llm = crate::language_models::OpenAIChat::new(config);
Self {
inner: crate::core::language_models::wrap_chat_model(llm),
}
}
pub fn anthropic(
config: crate::language_models::providers::anthropic::config::AnthropicConfig,
) -> Self {
let llm = crate::language_models::providers::anthropic::AnthropicChat::new(config);
Self {
inner: crate::core::language_models::wrap_chat_model(llm),
}
}
pub fn ollama(config: crate::language_models::ollama::config::OllamaConfig) -> Self {
let llm = crate::language_models::ollama::chat::OllamaChat::with_config(config);
Self {
inner: crate::core::language_models::wrap_chat_model(llm),
}
}
pub fn gemini(config: crate::language_models::providers::gemini::GeminiConfig) -> Self {
let llm = crate::language_models::providers::gemini::GeminiChat::new(config);
Self {
inner: crate::core::language_models::wrap_chat_model(llm),
}
}
pub fn deepseek(config: crate::language_models::providers::deepseek::DeepSeekConfig) -> Self {
let llm = crate::language_models::providers::deepseek::DeepSeekChat::new(config);
Self {
inner: crate::core::language_models::wrap_chat_model(llm),
}
}
pub fn qwen(config: crate::language_models::providers::qwen::QwenConfig) -> Self {
let llm = crate::language_models::providers::qwen::QwenChat::new(config);
Self {
inner: crate::core::language_models::wrap_chat_model(llm),
}
}
pub fn moonshot(config: crate::language_models::providers::moonshot::MoonshotConfig) -> Self {
let llm = crate::language_models::providers::moonshot::MoonshotChat::new(config);
Self {
inner: crate::core::language_models::wrap_chat_model(llm),
}
}
pub fn zhipu(config: crate::language_models::providers::zhipu::ZhipuConfig) -> Self {
let llm = crate::language_models::providers::zhipu::ZhipuChat::new(config);
Self {
inner: crate::core::language_models::wrap_chat_model(llm),
}
}
pub fn from_llm<L>(llm: L) -> Self
where
L: BaseChatModel + Send + Sync + 'static,
L::Error: Into<Error>,
{
Self {
inner: crate::core::language_models::wrap_chat_model(llm),
}
}
pub fn from_arc(llm: Arc<dyn BaseChatModel<Error = Error> + Send + Sync>) -> Self {
Self { inner: llm }
}
pub fn into_inner(self) -> Arc<dyn BaseChatModel<Error = Error> + Send + Sync> {
self.inner
}
pub fn inner(&self) -> &Arc<dyn BaseChatModel<Error = Error> + Send + Sync> {
&self.inner
}
}
use crate::core::language_models::BaseLanguageModel;
use crate::core::runnables::Runnable;
use futures_util::Stream;
#[async_trait]
impl Runnable<Vec<Message>, LLMResult> for LLMClient {
type Error = Error;
async fn invoke(
&self,
input: Vec<Message>,
config: Option<RunnableConfig>,
) -> Result<LLMResult, Error> {
self.inner.invoke(input, config).await
}
async fn batch(
&self,
inputs: Vec<Vec<Message>>,
config: Option<RunnableConfig>,
) -> Result<Vec<LLMResult>, Error> {
self.inner.batch(inputs, config).await
}
async fn stream(
&self,
input: Vec<Message>,
config: Option<RunnableConfig>,
) -> Result<std::pin::Pin<Box<dyn Stream<Item = Result<LLMResult, Error>> + Send>>, Error> {
self.inner.stream(input, config).await
}
}
impl BaseLanguageModel<Vec<Message>, LLMResult> for LLMClient {
fn model_name(&self) -> &str {
self.inner.model_name()
}
fn get_num_tokens(&self, text: &str) -> usize {
self.inner.get_num_tokens(text)
}
fn temperature(&self) -> Option<f32> {
self.inner.temperature()
}
fn max_tokens(&self) -> Option<usize> {
self.inner.max_tokens()
}
fn with_temperature(self, _temp: f32) -> Self
where
Self: Sized,
{
self
}
fn with_max_tokens(self, _max: usize) -> Self
where
Self: Sized,
{
self
}
}
#[async_trait]
impl BaseChatModel for LLMClient {
async fn chat(
&self,
messages: Vec<Message>,
config: Option<RunnableConfig>,
) -> Result<LLMResult, Error> {
self.inner.chat(messages, config).await
}
async fn stream_chat(
&self,
messages: Vec<Message>,
config: Option<RunnableConfig>,
) -> Result<std::pin::Pin<Box<dyn Stream<Item = Result<String, Error>> + Send>>, Error>
{
self.inner.stream_chat(messages, config).await
}
fn bind_tools(
&self,
tools: Vec<crate::core::tools::ToolDefinition>,
) -> Option<Box<dyn BaseChatModel<Error = Error> + Send + Sync>> {
self.inner.bind_tools(tools)
}
}
impl std::ops::Deref for LLMClient {
type Target = dyn BaseChatModel<Error = Error> + Send + Sync;
fn deref(&self) -> &Self::Target {
&*self.inner
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::language_models::{OpenAIChat, OpenAIConfig};
#[test]
fn test_from_llm_openai() {
let config = OpenAIConfig::new("test_key");
let _client = LLMClient::from_llm(OpenAIChat::new(config));
}
#[test]
fn test_openai_constructor() {
let config = OpenAIConfig::new("test_key");
let _client = LLMClient::openai(config);
}
#[test]
fn test_from_arc() {
let config = OpenAIConfig::new("test_key");
let arc = crate::core::language_models::wrap_chat_model(OpenAIChat::new(config));
let _client = LLMClient::from_arc(arc);
}
#[test]
fn test_into_inner() {
let config = OpenAIConfig::new("test_key");
let client = LLMClient::openai(config);
let _arc = client.into_inner();
}
#[test]
fn test_from_env_no_keys() {
std::env::remove_var("OPENAI_API_KEY");
std::env::remove_var("ANTHROPIC_API_KEY");
std::env::remove_var("OLLAMA_BASE_URL");
let result = LLMClient::from_env();
assert!(result.is_err());
assert!(result.unwrap_err().contains("No LLM provider detected"));
}
#[test]
fn test_deref_works() {
let config = OpenAIConfig::new("test_key");
let client = LLMClient::openai(config);
let _name = client.model_name();
}
}