Skip to main content

paladin_llm/
provider_factory.rs

1//! # LLM Provider Factory
2//!
3//! Creates [`paladin_ports::output::llm_port::LlmPort`] adapter instances by
4//! provider name. Supports any combination of the `openai`, `anthropic`, and
5//! `deepseek` feature flags.
6
7use std::sync::Arc;
8
9use paladin_ports::output::llm_port::LlmPort;
10use thiserror::Error;
11
12/// Errors that can be returned by [`LlmProviderFactory`].
13#[derive(Debug, Error)]
14pub enum ProviderFactoryError {
15    /// The requested provider name is not recognised.
16    #[error("Unknown provider: {0}. Supported providers: openai, deepseek, anthropic")]
17    UnknownProvider(String),
18
19    /// Required environment variables or configuration are missing.
20    #[error("Provider configuration missing: {0}")]
21    ConfigurationMissing(String),
22
23    /// The provider adapter could not be constructed.
24    #[error("Failed to create provider adapter: {0}")]
25    AdapterCreationFailed(String),
26}
27
28/// Factory for creating LLM provider adapters by name.
29///
30/// Each provider is only available when the corresponding feature flag is
31/// enabled (`openai`, `anthropic`, or `deepseek`).
32///
33/// # Example
34///
35/// ```rust,no_run
36/// # #[cfg(feature = "openai")]
37/// # {
38/// use paladin_llm::provider_factory::LlmProviderFactory;
39///
40/// let factory = LlmProviderFactory::new();
41/// let provider = factory.create("openai").expect("OPENAI_API_KEY must be set");
42/// # }
43/// ```
44pub struct LlmProviderFactory;
45
46impl LlmProviderFactory {
47    /// Create a new provider factory.
48    pub fn new() -> Self {
49        Self
50    }
51
52    /// Create an [`LlmPort`] adapter by provider name.
53    ///
54    /// Configuration is loaded from environment variables (see each adapter's
55    /// `*Config::from_env()` for the expected variable names).
56    ///
57    /// # Errors
58    ///
59    /// Returns [`ProviderFactoryError`] if the provider name is unknown, a
60    /// required environment variable is absent, or the adapter fails to
61    /// initialise.
62    pub fn create(&self, provider_name: &str) -> Result<Arc<dyn LlmPort>, ProviderFactoryError> {
63        match provider_name.to_lowercase().as_str() {
64            #[cfg(feature = "openai")]
65            "openai" => {
66                use crate::openai::{OpenAIAdapter, OpenAIConfig};
67                let config = OpenAIConfig::from_env().map_err(|e| {
68                    ProviderFactoryError::ConfigurationMissing(format!(
69                        "OpenAI configuration error: {}. Ensure OPENAI_API_KEY is set.",
70                        e
71                    ))
72                })?;
73                let adapter = OpenAIAdapter::new(config).map_err(|e| {
74                    ProviderFactoryError::AdapterCreationFailed(format!(
75                        "Failed to create OpenAI adapter: {}",
76                        e
77                    ))
78                })?;
79                Ok(Arc::new(adapter))
80            }
81            #[cfg(feature = "deepseek")]
82            "deepseek" => {
83                use crate::deepseek::{DeepSeekAdapter, DeepSeekConfig};
84                let config = DeepSeekConfig::from_env().map_err(|e| {
85                    ProviderFactoryError::ConfigurationMissing(format!(
86                        "DeepSeek configuration error: {}. Ensure DEEPSEEK_API_KEY is set.",
87                        e
88                    ))
89                })?;
90                let adapter = DeepSeekAdapter::new(config).map_err(|e| {
91                    ProviderFactoryError::AdapterCreationFailed(format!(
92                        "Failed to create DeepSeek adapter: {}",
93                        e
94                    ))
95                })?;
96                Ok(Arc::new(adapter))
97            }
98            #[cfg(feature = "anthropic")]
99            "anthropic" => {
100                use crate::anthropic::{AnthropicAdapter, AnthropicConfig};
101                let config = AnthropicConfig::from_env().map_err(|e| {
102                    ProviderFactoryError::ConfigurationMissing(format!(
103                        "Anthropic configuration error: {}. Ensure ANTHROPIC_API_KEY is set.",
104                        e
105                    ))
106                })?;
107                let adapter = AnthropicAdapter::new(config).map_err(|e| {
108                    ProviderFactoryError::AdapterCreationFailed(format!(
109                        "Failed to create Anthropic adapter: {}",
110                        e
111                    ))
112                })?;
113                Ok(Arc::new(adapter))
114            }
115            other => Err(ProviderFactoryError::UnknownProvider(other.to_string())),
116        }
117    }
118
119    /// Return the name of the first available provider based on environment
120    /// variables, or `None` if no API keys are set.
121    ///
122    /// Priority: OpenAI → DeepSeek → Anthropic.
123    pub fn get_default_provider() -> Option<String> {
124        if std::env::var("OPENAI_API_KEY").is_ok() {
125            return Some("openai".to_string());
126        }
127        if std::env::var("DEEPSEEK_API_KEY").is_ok() {
128            return Some("deepseek".to_string());
129        }
130        if std::env::var("ANTHROPIC_API_KEY").is_ok() {
131            return Some("anthropic".to_string());
132        }
133        None
134    }
135
136    /// Return the names of all providers that have API keys configured.
137    pub fn list_available_providers() -> Vec<String> {
138        let mut providers = Vec::new();
139        if std::env::var("OPENAI_API_KEY").is_ok() {
140            providers.push("openai".to_string());
141        }
142        if std::env::var("DEEPSEEK_API_KEY").is_ok() {
143            providers.push("deepseek".to_string());
144        }
145        if std::env::var("ANTHROPIC_API_KEY").is_ok() {
146            providers.push("anthropic".to_string());
147        }
148        providers
149    }
150}
151
152impl Default for LlmProviderFactory {
153    fn default() -> Self {
154        Self::new()
155    }
156}
157
158#[cfg(test)]
159mod tests {
160    use super::*;
161
162    #[test]
163    fn test_factory_creation() {
164        let factory = LlmProviderFactory::new();
165        assert_eq!(std::mem::size_of_val(&factory), 0);
166    }
167
168    #[test]
169    fn test_unknown_provider_returns_error() {
170        let factory = LlmProviderFactory::new();
171        let result = factory.create("bogus_provider");
172        assert!(result.is_err());
173        if let Err(ProviderFactoryError::UnknownProvider(name)) = result {
174            assert_eq!(name, "bogus_provider");
175        } else {
176            panic!("Expected UnknownProvider error");
177        }
178    }
179
180    #[test]
181    fn test_list_available_providers_returns_vec() {
182        // Smoke test — just ensure it returns without panicking.
183        let _ = LlmProviderFactory::list_available_providers();
184    }
185}