Skip to main content

litellm_rs/core/providers/replicate/
models.rs

1//! Replicate Model Registry
2//!
3//! Model registry for Replicate with support for LLM and image generation models
4
5use std::collections::HashMap;
6use std::sync::OnceLock;
7
8use crate::core::types::{model::ModelInfo, model::ProviderCapability};
9
10/// Model type for Replicate
11#[derive(Debug, Clone, PartialEq, Eq, Hash)]
12pub enum ReplicateModelType {
13    /// Text generation / Chat completion (LLM)
14    TextGeneration,
15    /// Image generation (Stable Diffusion, SDXL, Flux, etc.)
16    ImageGeneration,
17    /// Audio generation
18    AudioGeneration,
19    /// Video generation
20    VideoGeneration,
21    /// Other model types
22    Other,
23}
24
25/// Model specification for Replicate
26#[derive(Debug, Clone)]
27pub struct ReplicateModelSpec {
28    /// Model info
29    pub model_info: ModelInfo,
30    /// Model type
31    pub model_type: ReplicateModelType,
32    /// Default input parameters
33    pub default_params: HashMap<String, serde_json::Value>,
34}
35
36/// Replicate model registry
37pub struct ReplicateModelRegistry {
38    models: HashMap<String, ReplicateModelSpec>,
39}
40
41impl Default for ReplicateModelRegistry {
42    fn default() -> Self {
43        Self::new()
44    }
45}
46
47impl ReplicateModelRegistry {
48    /// Create a new model registry
49    pub fn new() -> Self {
50        let mut registry = Self {
51            models: HashMap::new(),
52        };
53        registry.load_models();
54        registry
55    }
56
57    /// Load default models
58    fn load_models(&mut self) {
59        // LLM Models
60        self.add_llm_models();
61
62        // Image Generation Models
63        self.add_image_models();
64    }
65
66    /// Add LLM models to the registry
67    fn add_llm_models(&mut self) {
68        let llm_models = vec![
69            // Meta Llama 2 models
70            (
71                "meta/llama-2-70b-chat",
72                "Llama 2 70B Chat",
73                4096,
74                None,
75                0.00065, // ~$0.65/1M tokens
76                0.00275, // ~$2.75/1M tokens
77            ),
78            (
79                "meta/llama-2-13b-chat",
80                "Llama 2 13B Chat",
81                4096,
82                None,
83                0.0001,
84                0.0005,
85            ),
86            (
87                "meta/llama-2-7b-chat",
88                "Llama 2 7B Chat",
89                4096,
90                None,
91                0.00005,
92                0.00025,
93            ),
94            // Meta Llama 3 models
95            (
96                "meta/meta-llama-3-70b-instruct",
97                "Llama 3 70B Instruct",
98                8192,
99                None,
100                0.00065,
101                0.00275,
102            ),
103            (
104                "meta/meta-llama-3-8b-instruct",
105                "Llama 3 8B Instruct",
106                8192,
107                None,
108                0.00005,
109                0.00025,
110            ),
111        ];
112
113        for (id, name, context_len, output_len, input_cost, output_cost) in llm_models {
114            let model_info = ModelInfo {
115                id: id.to_string(),
116                name: name.to_string(),
117                provider: "replicate".to_string(),
118                max_context_length: context_len,
119                max_output_length: output_len,
120                supports_streaming: true,
121                supports_tools: false, // Replicate LLMs generally don't support tool calling
122                supports_multimodal: false,
123                input_cost_per_1k_tokens: Some(input_cost),
124                output_cost_per_1k_tokens: Some(output_cost),
125                currency: "USD".to_string(),
126                capabilities: vec![
127                    ProviderCapability::ChatCompletion,
128                    ProviderCapability::ChatCompletionStream,
129                ],
130                created_at: None,
131                updated_at: None,
132                metadata: HashMap::new(),
133            };
134
135            self.models.insert(
136                id.to_string(),
137                ReplicateModelSpec {
138                    model_info,
139                    model_type: ReplicateModelType::TextGeneration,
140                    default_params: HashMap::new(),
141                },
142            );
143        }
144    }
145
146    /// Add image generation models to the registry
147    fn add_image_models(&mut self) {
148        self.models.insert(
149            "black-forest-labs/flux-2-pro".to_string(),
150            ReplicateModelSpec {
151                model_info: ModelInfo {
152                    id: "black-forest-labs/flux-2-pro".to_string(),
153                    name: "FLUX.2 Pro".to_string(),
154                    provider: "replicate".to_string(),
155                    max_context_length: 0,
156                    max_output_length: None,
157                    supports_streaming: false,
158                    supports_tools: false,
159                    supports_multimodal: false,
160                    input_cost_per_1k_tokens: None,
161                    output_cost_per_1k_tokens: None,
162                    currency: "USD".to_string(),
163                    capabilities: vec![ProviderCapability::ImageGeneration],
164                    created_at: None,
165                    updated_at: None,
166                    metadata: HashMap::from([
167                        ("pricing_unit".to_string(), serde_json::json!("megapixel")),
168                        (
169                            "source".to_string(),
170                            serde_json::json!("https://replicate.com/black-forest-labs/flux-2-pro"),
171                        ),
172                    ]),
173                },
174                model_type: ReplicateModelType::ImageGeneration,
175                default_params: HashMap::new(),
176            },
177        );
178        let image_models = vec![
179            // Stable Diffusion XL
180            (
181                "stability-ai/sdxl",
182                "Stable Diffusion XL",
183                "1024x1024",
184                None, // Hardware-time billing; not a fixed per-image tariff.
185            ),
186            (
187                "stability-ai/stable-diffusion",
188                "Stable Diffusion 2.1",
189                "768x768",
190                None,
191            ),
192            // FLUX models
193            (
194                "black-forest-labs/flux-schnell",
195                "FLUX Schnell",
196                "",
197                Some(0.003),
198            ),
199            ("black-forest-labs/flux-dev", "FLUX Dev", "", Some(0.025)),
200            ("black-forest-labs/flux-pro", "FLUX Pro", "", Some(0.055)),
201            // Other popular models
202            (
203                "bytedance/sdxl-lightning-4step",
204                "SDXL Lightning 4-Step",
205                "1024x1024",
206                None,
207            ),
208            (
209                "playgroundai/playground-v2.5-1024px-aesthetic",
210                "Playground v2.5",
211                "1024x1024",
212                None,
213            ),
214        ];
215
216        for (id, name, default_size, cost_per_image) in image_models {
217            let mut metadata = HashMap::new();
218            metadata.insert(
219                "default_size".to_string(),
220                serde_json::Value::String(default_size.to_string()),
221            );
222            if let Some(cost) = cost_per_image {
223                metadata.insert("cost_per_image".to_string(), serde_json::json!(cost));
224            } else {
225                metadata.insert(
226                    "pricing_unit".to_string(),
227                    serde_json::json!("hardware_time"),
228                );
229            }
230            // Community models require a concrete version at /predictions.
231            let version = match id {
232                "stability-ai/sdxl" => {
233                    Some("7762fd07cf82c948538e41f63f77d685e02b063e37e496e96eefd46c929f9bdc")
234                }
235                "stability-ai/stable-diffusion" => {
236                    Some("ac732df83cea7fff18b8472768c88ad041fa750ff7682a21affe81863cbe77e4")
237                }
238                "bytedance/sdxl-lightning-4step" => {
239                    Some("6f7a773af6fc3e8de9d5a3c00be77c17308914bf67772726aff83496ba1e3bbe")
240                }
241                "playgroundai/playground-v2.5-1024px-aesthetic" => {
242                    Some("a45f82a1382bed5c7aeb861dac7c7d191b0fdf74d8d57c4a0e6ed7d4d0bf7d24")
243                }
244                _ => None,
245            };
246            if let Some(version) = version {
247                metadata.insert("version".to_string(), serde_json::json!(version));
248            }
249
250            let model_info = ModelInfo {
251                id: id.to_string(),
252                name: name.to_string(),
253                provider: "replicate".to_string(),
254                max_context_length: 0, // Not applicable for image models
255                max_output_length: None,
256                supports_streaming: false,
257                supports_tools: false,
258                supports_multimodal: false,
259                input_cost_per_1k_tokens: None, // Image models don't use token pricing
260                output_cost_per_1k_tokens: None,
261                currency: "USD".to_string(),
262                capabilities: vec![ProviderCapability::ImageGeneration],
263                created_at: None,
264                updated_at: None,
265                metadata,
266            };
267
268            let mut default_params = HashMap::new();
269            // Parse default size into width and height
270            if let Some((w, h)) = default_size.split_once('x')
271                && let (Ok(width), Ok(height)) = (w.parse::<i64>(), h.parse::<i64>())
272            {
273                default_params.insert("width".to_string(), serde_json::json!(width));
274                default_params.insert("height".to_string(), serde_json::json!(height));
275            }
276
277            self.models.insert(
278                id.to_string(),
279                ReplicateModelSpec {
280                    model_info,
281                    model_type: ReplicateModelType::ImageGeneration,
282                    default_params,
283                },
284            );
285        }
286    }
287
288    /// Get all models
289    pub fn get_all_models(&self) -> Vec<ModelInfo> {
290        self.models
291            .values()
292            .map(|spec| spec.model_info.clone())
293            .collect()
294    }
295
296    /// Get models by type
297    pub fn get_models_by_type(&self, model_type: &ReplicateModelType) -> Vec<ModelInfo> {
298        self.models
299            .values()
300            .filter(|spec| &spec.model_type == model_type)
301            .map(|spec| spec.model_info.clone())
302            .collect()
303    }
304
305    /// Get model specification
306    pub fn get_model_spec(&self, model_id: &str) -> Option<&ReplicateModelSpec> {
307        let canonical = if super::config::ReplicateConfig::extract_version_hash(model_id).is_some()
308        {
309            model_id.split_once(':').map_or(model_id, |(name, _)| name)
310        } else {
311            model_id
312        };
313        self.models.get(canonical)
314    }
315
316    /// Check if model exists
317    pub fn has_model(&self, model_id: &str) -> bool {
318        self.get_model_spec(model_id).is_some()
319    }
320
321    /// Get model type
322    pub fn get_model_type(&self, model_id: &str) -> Option<ReplicateModelType> {
323        self.get_model_spec(model_id)
324            .map(|spec| spec.model_type.clone())
325    }
326
327    /// Get default parameters for a model
328    pub fn get_default_params(
329        &self,
330        model_id: &str,
331    ) -> Option<&HashMap<String, serde_json::Value>> {
332        self.get_model_spec(model_id)
333            .map(|spec| &spec.default_params)
334    }
335
336    /// Get LLM models (for chat completion)
337    pub fn get_llm_models(&self) -> Vec<ModelInfo> {
338        self.get_models_by_type(&ReplicateModelType::TextGeneration)
339    }
340
341    /// Get image generation models
342    pub fn get_image_models(&self) -> Vec<ModelInfo> {
343        self.get_models_by_type(&ReplicateModelType::ImageGeneration)
344    }
345}
346
347/// Global model registry
348static REPLICATE_REGISTRY: OnceLock<ReplicateModelRegistry> = OnceLock::new();
349
350/// Get the global Replicate model registry
351pub fn get_replicate_registry() -> &'static ReplicateModelRegistry {
352    REPLICATE_REGISTRY.get_or_init(ReplicateModelRegistry::new)
353}
354
355#[cfg(test)]
356mod tests {
357    use super::*;
358
359    #[test]
360    fn test_registry_creation() {
361        let registry = ReplicateModelRegistry::new();
362        assert!(!registry.get_all_models().is_empty());
363    }
364
365    #[test]
366    fn test_registry_has_llm_models() {
367        let registry = get_replicate_registry();
368        let llm_models = registry.get_llm_models();
369        assert!(!llm_models.is_empty());
370
371        // Check for specific Llama model
372        let has_llama = llm_models.iter().any(|m| m.id.contains("llama"));
373        assert!(has_llama);
374    }
375
376    #[test]
377    fn test_registry_has_image_models() {
378        let registry = get_replicate_registry();
379        let image_models = registry.get_image_models();
380        assert!(!image_models.is_empty());
381
382        // Check for SDXL
383        let has_sdxl = image_models.iter().any(|m| m.id.contains("sdxl"));
384        assert!(has_sdxl);
385    }
386
387    #[test]
388    fn test_get_model_spec() {
389        let registry = get_replicate_registry();
390        let spec = registry.get_model_spec("meta/llama-2-70b-chat");
391        assert!(spec.is_some());
392
393        let spec = spec.unwrap();
394        assert_eq!(spec.model_type, ReplicateModelType::TextGeneration);
395    }
396
397    #[test]
398    fn test_get_model_type() {
399        let registry = get_replicate_registry();
400
401        assert_eq!(
402            registry.get_model_type("meta/llama-2-70b-chat"),
403            Some(ReplicateModelType::TextGeneration)
404        );
405        assert_eq!(
406            registry.get_model_type("stability-ai/sdxl"),
407            Some(ReplicateModelType::ImageGeneration)
408        );
409        assert_eq!(registry.get_model_type("nonexistent"), None);
410    }
411
412    #[test]
413    fn test_has_model() {
414        let registry = get_replicate_registry();
415        assert!(registry.has_model("meta/llama-2-70b-chat"));
416        assert!(registry.has_model("stability-ai/sdxl"));
417        assert!(!registry.has_model("nonexistent"));
418    }
419
420    #[test]
421    fn test_model_info_properties() {
422        let registry = get_replicate_registry();
423        let models = registry.get_all_models();
424
425        for model in models {
426            assert!(!model.id.is_empty());
427            assert!(!model.name.is_empty());
428            assert_eq!(model.provider, "replicate");
429            assert_eq!(model.currency, "USD");
430        }
431    }
432
433    #[test]
434    fn test_llm_model_capabilities() {
435        let registry = get_replicate_registry();
436        let llm_models = registry.get_llm_models();
437
438        for model in llm_models {
439            assert!(
440                model
441                    .capabilities
442                    .contains(&ProviderCapability::ChatCompletion)
443            );
444            assert!(model.max_context_length > 0);
445        }
446    }
447
448    #[test]
449    fn test_image_model_capabilities() {
450        let registry = get_replicate_registry();
451        let image_models = registry.get_image_models();
452
453        for model in image_models {
454            assert!(
455                model
456                    .capabilities
457                    .contains(&ProviderCapability::ImageGeneration)
458            );
459        }
460    }
461
462    #[test]
463    fn test_image_model_default_params() {
464        let registry = get_replicate_registry();
465        let params = registry.get_default_params("stability-ai/sdxl");
466        assert!(params.is_some());
467
468        let params = params.unwrap();
469        assert!(params.contains_key("width"));
470        assert!(params.contains_key("height"));
471    }
472
473    #[test]
474    fn test_model_type_equality() {
475        assert_eq!(
476            ReplicateModelType::TextGeneration,
477            ReplicateModelType::TextGeneration
478        );
479        assert_ne!(
480            ReplicateModelType::TextGeneration,
481            ReplicateModelType::ImageGeneration
482        );
483    }
484
485    #[test]
486    fn test_default_impl() {
487        let registry = ReplicateModelRegistry::default();
488        assert!(!registry.get_all_models().is_empty());
489    }
490
491    #[test]
492    fn test_global_registry() {
493        let registry1 = get_replicate_registry();
494        let registry2 = get_replicate_registry();
495
496        // Should be the same instance
497        assert_eq!(
498            registry1.get_all_models().len(),
499            registry2.get_all_models().len()
500        );
501    }
502
503    #[test]
504    fn test_flux_models() {
505        let registry = get_replicate_registry();
506
507        assert!(registry.has_model("black-forest-labs/flux-schnell"));
508        assert!(registry.has_model("black-forest-labs/flux-dev"));
509        assert!(registry.has_model("black-forest-labs/flux-pro"));
510    }
511
512    #[test]
513    fn test_llama_3_models() {
514        let registry = get_replicate_registry();
515
516        assert!(registry.has_model("meta/meta-llama-3-70b-instruct"));
517        assert!(registry.has_model("meta/meta-llama-3-8b-instruct"));
518    }
519}