Skip to main content

sc/embeddings/
factory.rs

1//! Embedding provider factory.
2//!
3//! Handles provider detection and creation.
4
5use super::config::{get_embedding_settings, is_embeddings_enabled};
6use super::huggingface::HuggingFaceProvider;
7use super::ollama::OllamaProvider;
8use super::provider::{BoxedProvider, EmbeddingProvider};
9use super::types::EmbeddingProviderType;
10
11/// Available provider detection result.
12#[derive(Debug, Clone)]
13pub struct ProviderDetection {
14    /// List of available provider names.
15    pub available: Vec<String>,
16    /// Recommended provider (first available).
17    pub recommended: Option<String>,
18}
19
20/// Detect which embedding providers are available.
21pub async fn detect_available_providers() -> ProviderDetection {
22    let mut available = Vec::new();
23
24    // Check Ollama
25    let ollama = OllamaProvider::new();
26    if ollama.is_available().await {
27        available.push("ollama".to_string());
28    }
29
30    // Check HuggingFace (available if token is set)
31    if let Some(hf) = HuggingFaceProvider::new() {
32        if hf.is_available().await {
33            available.push("huggingface".to_string());
34        }
35    }
36
37    // Note: We don't support transformers.js in Rust CLI
38    // Users who need local inference without Ollama should use the TypeScript CLI
39
40    let recommended = available.first().cloned();
41
42    ProviderDetection {
43        available,
44        recommended,
45    }
46}
47
48/// Create an embedding provider based on configuration.
49///
50/// Priority:
51/// 1. Explicit provider in config
52/// 2. Auto-detect available provider (Ollama preferred)
53///
54/// Returns `None` if no provider is available or embeddings are disabled.
55pub async fn create_embedding_provider() -> Option<BoxedProvider> {
56    // Check if embeddings are enabled
57    if !is_embeddings_enabled() {
58        return None;
59    }
60
61    // Check for explicit provider in config
62    if let Ok(Some(settings)) = get_embedding_settings() {
63        if let Some(provider_type) = settings.provider {
64            return create_provider_by_type(provider_type).await;
65        }
66    }
67
68    // Auto-detect: try Ollama first, then HuggingFace
69    let ollama = OllamaProvider::new();
70    if ollama.is_available().await {
71        return Some(BoxedProvider::new(ollama));
72    }
73
74    if let Some(hf) = HuggingFaceProvider::new() {
75        if hf.is_available().await {
76            return Some(BoxedProvider::new(hf));
77        }
78    }
79
80    None
81}
82
83/// Create a specific provider by type.
84async fn create_provider_by_type(provider_type: EmbeddingProviderType) -> Option<BoxedProvider> {
85    match provider_type {
86        EmbeddingProviderType::Ollama => {
87            let provider = OllamaProvider::new();
88            if provider.is_available().await {
89                Some(BoxedProvider::new(provider))
90            } else {
91                None
92            }
93        }
94        EmbeddingProviderType::Huggingface => {
95            HuggingFaceProvider::new().map(BoxedProvider::new)
96        }
97        EmbeddingProviderType::Transformers => {
98            // Transformers.js is not supported in Rust CLI
99            // Users should use Ollama or HuggingFace instead
100            None
101        }
102        EmbeddingProviderType::Model2vec => {
103            // Model2Vec is handled separately as fast tier
104            // Use create_model2vec_provider() directly
105            super::model2vec::Model2VecProvider::try_new().map(BoxedProvider::new)
106        }
107    }
108}
109
110/// Create a provider with explicit configuration (for testing or CLI overrides).
111pub fn create_ollama_provider(endpoint: Option<String>, model: Option<String>) -> BoxedProvider {
112    BoxedProvider::new(OllamaProvider::with_config(endpoint, model))
113}
114
115/// Create a HuggingFace provider with explicit configuration.
116pub fn create_huggingface_provider(
117    endpoint: Option<String>,
118    model: Option<String>,
119    token: Option<String>,
120) -> Option<BoxedProvider> {
121    HuggingFaceProvider::with_config(endpoint, model, token)
122        .map(BoxedProvider::new)
123}