Skip to main content

llm_stack_anthropic/
factory.rs

1//! Factory for building Anthropic providers from configuration.
2
3use llm_stack::registry::{ProviderConfig, ProviderFactory};
4use llm_stack::{DynProvider, LlmError};
5
6use crate::{AnthropicConfig, AnthropicProvider};
7
8/// Factory for creating [`AnthropicProvider`] instances from configuration.
9///
10/// Register this factory with the global registry to enable config-driven
11/// provider instantiation:
12///
13/// ```rust,no_run
14/// use llm_stack::ProviderRegistry;
15/// use llm_stack_anthropic::AnthropicFactory;
16///
17/// ProviderRegistry::global().register(Box::new(AnthropicFactory));
18/// ```
19///
20/// # Configuration
21///
22/// | Field | Required | Description |
23/// |-------|----------|-------------|
24/// | `provider` | Yes | Must be `"anthropic"` |
25/// | `api_key` | Yes | Anthropic API key |
26/// | `model` | Yes | Model identifier (e.g., `"claude-sonnet-4-20250514"`) |
27/// | `base_url` | No | Custom API endpoint |
28/// | `timeout` | No | Request timeout |
29/// | `extra.max_tokens` | No | Default max tokens (default: 4096) |
30/// | `extra.api_version` | No | API version header |
31#[derive(Debug, Clone, Copy, Default)]
32pub struct AnthropicFactory;
33
34impl ProviderFactory for AnthropicFactory {
35    fn name(&self) -> &'static str {
36        "anthropic"
37    }
38
39    fn build(&self, config: &ProviderConfig) -> Result<Box<dyn DynProvider>, LlmError> {
40        let api_key = config.api_key.clone().ok_or_else(|| {
41            LlmError::InvalidRequest("anthropic provider requires api_key".into())
42        })?;
43
44        if config.model.is_empty() {
45            return Err(LlmError::InvalidRequest(
46                "anthropic provider requires model".into(),
47            ));
48        }
49
50        let mut anthropic_config = AnthropicConfig {
51            api_key,
52            model: config.model.clone(),
53            client: config.client.clone(),
54            ..Default::default()
55        };
56
57        if let Some(base_url) = &config.base_url {
58            anthropic_config.base_url.clone_from(base_url);
59        }
60
61        if let Some(timeout) = config.timeout {
62            anthropic_config.timeout = Some(timeout);
63        }
64
65        if let Some(max_tokens) = config.get_extra_i64("max_tokens") {
66            anthropic_config.max_tokens =
67                u32::try_from(max_tokens).unwrap_or(anthropic_config.max_tokens);
68        }
69
70        if let Some(api_version) = config.get_extra_str("api_version") {
71            anthropic_config.api_version = api_version.to_string();
72        }
73
74        Ok(Box::new(AnthropicProvider::new(anthropic_config)))
75    }
76}
77
78/// Registers the Anthropic factory with the global registry.
79///
80/// Call this once at application startup to enable config-driven
81/// Anthropic provider creation.
82pub fn register_global() {
83    llm_stack::ProviderRegistry::global().register(Box::new(AnthropicFactory));
84}
85
86#[cfg(test)]
87mod tests {
88    use super::*;
89    use std::time::Duration;
90
91    #[test]
92    fn test_factory_name() {
93        let factory = AnthropicFactory;
94        assert_eq!(factory.name(), "anthropic");
95    }
96
97    #[test]
98    fn test_factory_build_success() {
99        let factory = AnthropicFactory;
100        let config = ProviderConfig::new("anthropic", "claude-3")
101            .api_key("sk-test")
102            .timeout(Duration::from_secs(30))
103            .extra("max_tokens", 2048i64);
104
105        let provider = factory.build(&config).unwrap();
106        assert_eq!(provider.metadata().name, "anthropic");
107        assert_eq!(provider.metadata().model, "claude-3");
108    }
109
110    #[test]
111    fn test_factory_missing_api_key() {
112        let factory = AnthropicFactory;
113        let config = ProviderConfig::new("anthropic", "claude-3");
114
115        let err = factory.build(&config).err().unwrap();
116        assert!(matches!(err, LlmError::InvalidRequest(_)));
117    }
118
119    #[test]
120    fn test_factory_empty_model() {
121        let factory = AnthropicFactory;
122        let config = ProviderConfig::new("anthropic", "").api_key("sk-test");
123
124        let err = factory.build(&config).err().unwrap();
125        assert!(matches!(err, LlmError::InvalidRequest(_)));
126    }
127}