llm-connector 0.2.1

A lightweight Rust library for protocol adaptation across multiple LLM providers. Focuses solely on converting between different LLM provider APIs and providing a unified OpenAI-compatible interface.
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
//! Protocol factory for dynamic protocol creation
//!
//! This module provides a factory pattern for creating protocol adapters dynamically
//! based on configuration, without hardcoding provider names.
//!
//! # Purpose
//!
//! The factory pattern allows the `ProviderRegistry` to create providers from YAML
//! configuration files without knowing the specific provider implementations at compile time.
//!
//! # Architecture
//!
//! ## Factory Trait
//!
//! The `ProtocolFactory` trait defines the interface for creating protocol adapters:
//! - `protocol_name()` - Returns the protocol identifier (e.g., "openai", "anthropic")
//! - `supported_providers()` - Lists all providers using this protocol
//! - `create_adapter()` - Creates a protocol adapter instance
//!
//! ## Built-in Factories
//!
//! - **`OpenAIProtocolFactory`** - Creates OpenAI-compatible adapters
//!   - Supports: DeepSeek, Zhipu, Moonshot, VolcEngine, Tencent, MiniMax, StepFun, LongCat
//!
//! - **`AnthropicProtocolFactory`** - Creates Anthropic adapters
//!   - Supports: Claude (Anthropic)
//!
//! - **`AliyunProtocolFactory`** - Creates Aliyun adapters
//!   - Supports: Qwen (Aliyun DashScope)
//!
//! ## Factory Registry
//!
//! The `ProtocolFactoryRegistry` manages all protocol factories and provides:
//! - Automatic registration of built-in factories
//! - Dynamic provider creation from configuration
//! - Protocol lookup by provider name
//!
//! # Example: Using with YAML Config
//!
//! ```yaml
//! # config.yaml
//! providers:
//!   deepseek:
//!     protocol: openai
//!     api_key: sk-xxx
//!   claude:
//!     protocol: anthropic
//!     api_key: sk-ant-xxx
//!   qwen:
//!     protocol: aliyun
//!     api_key: sk-xxx
//! ```
//!
//! ```rust,no_run
//! use llm_connector::config::RegistryConfig;
//! use llm_connector::registry::ProviderRegistry;
//!
//! # async fn example() -> Result<(), Box<dyn std::error::Error>> {
//! // Load configuration
//! let config = RegistryConfig::from_yaml_file("config.yaml")?;
//!
//! // Create registry (uses factories internally)
//! let registry = ProviderRegistry::from_config(config)?;
//!
//! // Get providers (created by factories)
//! let deepseek = registry.get("deepseek").unwrap();
//! let claude = registry.get("claude").unwrap();
//! let qwen = registry.get("qwen").unwrap();
//! # Ok(())
//! # }
//! ```
//!
//! # Example: Custom Factory
//!
//! ```rust
//! use llm_connector::protocols::factory::{ProtocolFactory, ProtocolFactoryRegistry};
//! use llm_connector::config::ProviderConfig;
//! use llm_connector::error::LlmConnectorError;
//!
//! struct MyCustomFactory;
//!
//! impl ProtocolFactory for MyCustomFactory {
//!     fn protocol_name(&self) -> &str {
//!         "custom"
//!     }
//!
//!     fn supported_providers(&self) -> Vec<&str> {
//!         vec!["my-provider"]
//!     }
//!
//!     fn create_adapter(
//!         &self,
//!         provider_name: &str,
//!         config: &ProviderConfig,
//!     ) -> Result<Box<dyn std::any::Any + Send>, LlmConnectorError> {
//!         // Create your custom adapter
//!         todo!()
//!     }
//! }
//!
//! // Register custom factory
//! let mut registry = ProtocolFactoryRegistry::new();
//! registry.register(Box::new(MyCustomFactory));
//! ```

use crate::config::ProviderConfig;
use crate::error::LlmConnectorError;
use crate::protocols::{AliyunProtocol, AnthropicProtocol};
use std::collections::HashMap;
use std::sync::{Arc, RwLock};

/// Trait for protocol factories
///
/// Implement this trait to create custom protocol factories that can
/// dynamically create protocol adapters based on configuration.
pub trait ProtocolFactory: Send + Sync {
    /// Get the protocol name (e.g., "openai", "anthropic")
    fn protocol_name(&self) -> &str;

    /// Get the list of provider names that use this protocol
    fn supported_providers(&self) -> Vec<&str>;

    /// Create a protocol adapter instance
    fn create_adapter(
        &self,
        provider_name: &str,
        config: &ProviderConfig,
    ) -> Result<Box<dyn std::any::Any + Send>, LlmConnectorError>;

    /// Check if this factory supports a given provider
    fn supports_provider(&self, provider_name: &str) -> bool {
        self.supported_providers().contains(&provider_name)
    }
}

/// Factory for OpenAI protocol
#[derive(Debug, Clone)]
pub struct OpenAIProtocolFactory;

impl ProtocolFactory for OpenAIProtocolFactory {
    fn protocol_name(&self) -> &str {
        "openai"
    }

    fn supported_providers(&self) -> Vec<&str> {
        vec![
            "deepseek",
            "zhipu",
            "moonshot",
            "volcengine",
            "tencent",
            "minimax",
            "stepfun",
            "longcat",
        ]
    }

    fn create_adapter(
        &self,
        provider_name: &str,
        _config: &ProviderConfig,
    ) -> Result<Box<dyn std::any::Any + Send>, LlmConnectorError> {
        let adapter = match provider_name {
            "deepseek" => crate::protocols::openai::deepseek(),
            "zhipu" => crate::protocols::openai::zhipu(),
            "moonshot" => crate::protocols::openai::moonshot(),
            "volcengine" => crate::protocols::openai::volcengine(),
            "tencent" => crate::protocols::openai::tencent(),
            "minimax" => crate::protocols::openai::minimax(),
            "stepfun" => crate::protocols::openai::stepfun(),
            "longcat" => crate::protocols::openai::longcat(),
            _ => {
                return Err(LlmConnectorError::UnsupportedModel(format!(
                    "Unknown OpenAI-compatible provider: {}",
                    provider_name
                )))
            }
        };

        Ok(Box::new(adapter))
    }
}

/// Factory for Anthropic protocol
#[derive(Debug, Clone)]
pub struct AnthropicProtocolFactory;

impl ProtocolFactory for AnthropicProtocolFactory {
    fn protocol_name(&self) -> &str {
        "anthropic"
    }

    fn supported_providers(&self) -> Vec<&str> {
        vec!["anthropic", "claude"]
    }

    fn create_adapter(
        &self,
        _provider_name: &str,
        config: &ProviderConfig,
    ) -> Result<Box<dyn std::any::Any + Send>, LlmConnectorError> {
        let adapter = AnthropicProtocol::new(config.base_url.as_deref());
        Ok(Box::new(adapter))
    }
}

/// Factory for Aliyun protocol
#[derive(Debug, Clone)]
pub struct AliyunProtocolFactory;

impl ProtocolFactory for AliyunProtocolFactory {
    fn protocol_name(&self) -> &str {
        "aliyun"
    }

    fn supported_providers(&self) -> Vec<&str> {
        vec!["aliyun", "dashscope", "qwen"]
    }

    fn create_adapter(
        &self,
        _provider_name: &str,
        config: &ProviderConfig,
    ) -> Result<Box<dyn std::any::Any + Send>, LlmConnectorError> {
        let adapter = AliyunProtocol::new(config.base_url.as_deref());
        Ok(Box::new(adapter))
    }
}

/// Protocol factory registry
///
/// Manages all registered protocol factories and provides methods to
/// create protocol adapters dynamically.
#[derive(Clone)]
pub struct ProtocolFactoryRegistry {
    factories: Arc<RwLock<HashMap<String, Arc<dyn ProtocolFactory>>>>,
    provider_to_protocol: Arc<RwLock<HashMap<String, String>>>,
}

impl ProtocolFactoryRegistry {
    /// Create a new empty registry
    pub fn new() -> Self {
        Self {
            factories: Arc::new(RwLock::new(HashMap::new())),
            provider_to_protocol: Arc::new(RwLock::new(HashMap::new())),
        }
    }

    /// Create a registry with default factories
    pub fn with_defaults() -> Self {
        let registry = Self::new();
        registry.register_default_factories();
        registry
    }

    /// Register default protocol factories
    pub fn register_default_factories(&self) {
        self.register(Arc::new(OpenAIProtocolFactory));
        self.register(Arc::new(AnthropicProtocolFactory));
        self.register(Arc::new(AliyunProtocolFactory));
    }

    /// Register a protocol factory
    pub fn register(&self, factory: Arc<dyn ProtocolFactory>) {
        let protocol_name = factory.protocol_name().to_string();

        // Register factory
        self.factories
            .write()
            .unwrap()
            .insert(protocol_name.clone(), factory.clone());

        // Register provider mappings
        let mut provider_map = self.provider_to_protocol.write().unwrap();
        for provider in factory.supported_providers() {
            provider_map.insert(provider.to_string(), protocol_name.clone());
        }
    }

    /// Get a factory by protocol name
    pub fn get_factory(&self, protocol_name: &str) -> Option<Arc<dyn ProtocolFactory>> {
        self.factories.read().unwrap().get(protocol_name).cloned()
    }

    /// Get the protocol name for a provider
    pub fn get_protocol_for_provider(&self, provider_name: &str) -> Option<String> {
        self.provider_to_protocol
            .read()
            .unwrap()
            .get(provider_name)
            .cloned()
    }

    /// Create a protocol adapter for a provider
    pub fn create_for_provider(
        &self,
        provider_name: &str,
        config: &ProviderConfig,
    ) -> Result<Box<dyn std::any::Any + Send>, LlmConnectorError> {
        // Find the protocol for this provider
        let protocol_name = self
            .get_protocol_for_provider(provider_name)
            .ok_or_else(|| {
                LlmConnectorError::UnsupportedModel(format!("Unknown provider: {}", provider_name))
            })?;

        // Get the factory
        let factory = self.get_factory(&protocol_name).ok_or_else(|| {
            LlmConnectorError::ProviderError(format!(
                "No factory registered for protocol: {}",
                protocol_name
            ))
        })?;

        // Create the adapter
        factory.create_adapter(provider_name, config)
    }

    /// List all registered protocols
    pub fn list_protocols(&self) -> Vec<String> {
        self.factories.read().unwrap().keys().cloned().collect()
    }

    /// List all supported providers
    pub fn list_providers(&self) -> Vec<String> {
        self.provider_to_protocol
            .read()
            .unwrap()
            .keys()
            .cloned()
            .collect()
    }

    /// Get all providers for a protocol
    pub fn get_providers_for_protocol(&self, protocol_name: &str) -> Vec<String> {
        self.provider_to_protocol
            .read()
            .unwrap()
            .iter()
            .filter(|(_, proto)| proto.as_str() == protocol_name)
            .map(|(provider, _)| provider.clone())
            .collect()
    }
}

impl Default for ProtocolFactoryRegistry {
    fn default() -> Self {
        Self::with_defaults()
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_openai_factory() {
        let factory = OpenAIProtocolFactory;
        assert_eq!(factory.protocol_name(), "openai");
        assert!(factory.supports_provider("deepseek"));
        assert!(factory.supports_provider("zhipu"));
        assert!(!factory.supports_provider("claude"));
    }

    #[test]
    fn test_anthropic_factory() {
        let factory = AnthropicProtocolFactory;
        assert_eq!(factory.protocol_name(), "anthropic");
        assert!(factory.supports_provider("anthropic"));
        assert!(factory.supports_provider("claude"));
        assert!(!factory.supports_provider("deepseek"));
    }

    #[test]
    fn test_registry() {
        let registry = ProtocolFactoryRegistry::with_defaults();

        // Test protocol lookup
        assert!(registry.get_factory("openai").is_some());
        assert!(registry.get_factory("anthropic").is_some());
        assert!(registry.get_factory("aliyun").is_some());

        // Test provider lookup
        assert_eq!(
            registry.get_protocol_for_provider("deepseek"),
            Some("openai".to_string())
        );
        assert_eq!(
            registry.get_protocol_for_provider("claude"),
            Some("anthropic".to_string())
        );
        assert_eq!(
            registry.get_protocol_for_provider("qwen"),
            Some("aliyun".to_string())
        );
    }

    #[test]
    fn test_list_protocols() {
        let registry = ProtocolFactoryRegistry::with_defaults();
        let protocols = registry.list_protocols();

        assert!(protocols.contains(&"openai".to_string()));
        assert!(protocols.contains(&"anthropic".to_string()));
        assert!(protocols.contains(&"aliyun".to_string()));
    }

    #[test]
    fn test_list_providers() {
        let registry = ProtocolFactoryRegistry::with_defaults();
        let providers = registry.list_providers();

        assert!(providers.contains(&"deepseek".to_string()));
        assert!(providers.contains(&"claude".to_string()));
        assert!(providers.contains(&"qwen".to_string()));
    }
}