subx_cli/services/ai/
factory.rs

1//! AI client factory for creating provider instances based on configuration.
2//!
3//! This module provides a factory pattern implementation for creating AI service
4//! provider instances. It supports multiple AI providers and handles the creation
5//! and configuration of appropriate client instances based on the provided configuration.
6//!
7//! # Supported Providers
8//!
9//! - **OpenAI**: GPT-3.5, GPT-4, and other OpenAI models
10//! - **Anthropic**: Claude models (planned)
11//! - **Google**: Gemini models (planned)
12//! - **Local Models**: Local AI models via Ollama (planned)
13//!
14//! # Examples
15//!
16//! ```rust,ignore
17//! use subx_cli::services::ai::AIClientFactory;
18//! use subx_cli::config::AIConfig;
19//!
20//! // Create OpenAI client from configuration
21//! let config = AIConfig {
22//!     provider: "openai".to_string(),
23//!     api_key: Some("sk-...".to_string()),
24//!     model: "gpt-4.1".to_string(),
25//!     // ... other fields
26//! };
27//!
28//! let client = AIClientFactory::create_client(&config)?;
29//! let result = client.analyze_content(request).await?;
30//! ```
31use crate::config::AIConfig;
32use crate::error::SubXError;
33use crate::services::ai::{AIProvider, OpenAIClient};
34
35/// AI client factory for creating provider instances.
36///
37/// The `AIClientFactory` provides a centralized way to create AI provider
38/// instances based on configuration. It supports multiple AI service providers
39/// and handles the complexity of client initialization and configuration.
40///
41/// # Design Pattern
42///
43/// This factory follows the Abstract Factory pattern, providing a single
44/// interface for creating different types of AI clients while hiding the
45/// specific implementation details from the consumer.
46///
47/// # Provider Selection
48///
49/// The factory automatically selects the appropriate provider implementation
50/// based on the `provider` field in the configuration:
51/// - `"openai"` - Creates an OpenAI client instance
52/// - `"anthropic"` - Creates an Anthropic client (future support)
53/// - `"google"` - Creates a Google AI client (future support)
54/// - `"local"` - Creates a local model client (future support)
55///
56/// # Error Handling
57///
58/// The factory returns detailed errors for:
59/// - Unsupported provider names
60/// - Invalid configuration parameters
61/// - Missing required credentials
62/// - Network connectivity issues during client creation
63///
64/// # Examples
65///
66/// ```rust,ignore
67/// use subx_cli::services::ai::AIClientFactory;
68/// use subx_cli::config::AIConfig;
69///
70/// // Create a configured OpenAI client
71/// let config = AIConfig {
72///     provider: "openai".to_string(),
73///     api_key: Some("sk-your-api-key".to_string()),
74///     model: "gpt-4.1".to_string(),
75///     base_url: "https://api.openai.com/v1".to_string(),
76///     max_sample_length: 2000,
77///     temperature: 0.3,
78///     retry_attempts: 3,
79///     retry_delay_ms: 1000,
80/// };
81///
82/// let client = AIClientFactory::create_client(&config)?;
83/// // Client is ready for content analysis
84/// ```
85pub struct AIClientFactory;
86
87impl AIClientFactory {
88    /// Creates an AI provider instance based on the provided configuration.
89    ///
90    /// This method examines the `provider` field in the configuration and
91    /// instantiates the appropriate AI client implementation. The returned
92    /// client is fully configured and ready for use.
93    ///
94    /// # Arguments
95    ///
96    /// * `config` - AI configuration containing provider details and credentials
97    ///
98    /// # Returns
99    ///
100    /// Returns a boxed trait object implementing `AIProvider` that can be used
101    /// for content analysis and subtitle matching operations.
102    ///
103    /// # Errors
104    ///
105    /// This method returns an error if:
106    /// - The specified provider is not supported
107    /// - Required configuration fields are missing or invalid
108    /// - API credentials are invalid or missing
109    /// - Network connectivity issues prevent client initialization
110    ///
111    /// # Examples
112    ///
113    /// ```rust,ignore
114    /// use subx_cli::services::ai::AIClientFactory;
115    /// use subx_cli::config::AIConfig;
116    ///
117    /// let config = AIConfig {
118    ///     provider: "openai".to_string(),
119    ///     api_key: Some("sk-key".to_string()),
120    ///     model: "gpt-4.1".to_string(),
121    ///     // ... other configuration
122    /// };
123    ///
124    /// match AIClientFactory::create_client(&config) {
125    ///     Ok(client) => {
126    ///         // Use client for analysis
127    ///         let result = client.analyze_content(request).await?;
128    ///     }
129    ///     Err(e) => {
130    ///         eprintln!("Failed to create AI client: {}", e);
131    ///     }
132    /// }
133    /// ```
134    ///
135    /// # Supported Providers
136    ///
137    /// Current supported providers:
138    /// - `"openai"` - OpenAI GPT models with chat completion API
139    pub fn create_client(config: &AIConfig) -> crate::Result<Box<dyn AIProvider>> {
140        match config.provider.as_str() {
141            "openai" => Ok(Box::new(OpenAIClient::from_config(config)?)),
142            other => Err(SubXError::config(format!(
143                "Unsupported AI provider: {}",
144                other
145            ))),
146        }
147    }
148}
149
150#[cfg(test)]
151mod tests {
152    use super::*;
153    use crate::config::AIConfig;
154
155    #[test]
156    fn test_ai_factory_openai_provider() {
157        let config = AIConfig {
158            provider: "openai".to_string(),
159            api_key: Some("key".to_string()),
160            model: "m".to_string(),
161            base_url: "https://api.openai.com/v1".to_string(),
162            max_sample_length: 100,
163            temperature: 0.1,
164            max_tokens: 1000,
165            retry_attempts: 1,
166            retry_delay_ms: 10,
167            request_timeout_seconds: 30,
168        };
169        // Should successfully create OpenAIClient instance
170        let res = AIClientFactory::create_client(&config);
171        assert!(res.is_ok());
172    }
173
174    #[test]
175    fn test_ai_factory_invalid_provider() {
176        let config = AIConfig {
177            provider: "unknown".to_string(),
178            api_key: Some("key".to_string()),
179            model: "m".to_string(),
180            base_url: "https://api.openai.com/v1".to_string(),
181            max_sample_length: 100,
182            temperature: 0.1,
183            max_tokens: 1000,
184            retry_attempts: 1,
185            retry_delay_ms: 10,
186            request_timeout_seconds: 30,
187        };
188        // Unsupported provider should return error
189        let res = AIClientFactory::create_client(&config);
190        assert!(res.is_err());
191    }
192}