1use std::collections::HashMap;
6use std::sync::OnceLock;
7
8use crate::core::types::{model::ModelInfo, model::ProviderCapability};
9
10#[derive(Debug, Clone, PartialEq, Eq, Hash)]
12pub enum ReplicateModelType {
13 TextGeneration,
15 ImageGeneration,
17 AudioGeneration,
19 VideoGeneration,
21 Other,
23}
24
25#[derive(Debug, Clone)]
27pub struct ReplicateModelSpec {
28 pub model_info: ModelInfo,
30 pub model_type: ReplicateModelType,
32 pub default_params: HashMap<String, serde_json::Value>,
34}
35
36pub 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 pub fn new() -> Self {
50 let mut registry = Self {
51 models: HashMap::new(),
52 };
53 registry.load_models();
54 registry
55 }
56
57 fn load_models(&mut self) {
59 self.add_llm_models();
61
62 self.add_image_models();
64 }
65
66 fn add_llm_models(&mut self) {
68 let llm_models = vec![
69 (
71 "meta/llama-2-70b-chat",
72 "Llama 2 70B Chat",
73 4096,
74 None,
75 0.00065, 0.00275, ),
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 (
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, 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 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 (
181 "stability-ai/sdxl",
182 "Stable Diffusion XL",
183 "1024x1024",
184 None, ),
186 (
187 "stability-ai/stable-diffusion",
188 "Stable Diffusion 2.1",
189 "768x768",
190 None,
191 ),
192 (
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 (
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 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, max_output_length: None,
256 supports_streaming: false,
257 supports_tools: false,
258 supports_multimodal: false,
259 input_cost_per_1k_tokens: None, 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 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 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 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 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 pub fn has_model(&self, model_id: &str) -> bool {
318 self.get_model_spec(model_id).is_some()
319 }
320
321 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 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 pub fn get_llm_models(&self) -> Vec<ModelInfo> {
338 self.get_models_by_type(&ReplicateModelType::TextGeneration)
339 }
340
341 pub fn get_image_models(&self) -> Vec<ModelInfo> {
343 self.get_models_by_type(&ReplicateModelType::ImageGeneration)
344 }
345}
346
347static REPLICATE_REGISTRY: OnceLock<ReplicateModelRegistry> = OnceLock::new();
349
350pub 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 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 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 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}