llm_kernel/embedding/
nomic_moe.rs1use crate::embedding::types::{EmbeddingProvider, EmbeddingResult};
18use crate::error::{KernelError, Result};
19
20pub struct NomicMoeProvider {
26 inner: fastembed::NomicV2MoeTextEmbedding,
27 model_id: String,
28 dim: usize,
29}
30
31pub const NOMIC_EMBED_TEXT_V2_MOE: &str = "nomic-ai/nomic-embed-text-v2-moe";
33
34const DEFAULT_MAX_LENGTH: usize = 512;
36
37impl NomicMoeProvider {
38 pub fn new() -> Result<Self> {
42 Self::with_options(
43 NOMIC_EMBED_TEXT_V2_MOE,
44 candle_core::Device::Cpu,
45 candle_core::DType::F32,
46 DEFAULT_MAX_LENGTH,
47 )
48 }
49
50 pub fn with_options(
52 model_id: &str,
53 device: candle_core::Device,
54 dtype: candle_core::DType,
55 max_length: usize,
56 ) -> Result<Self> {
57 let te = fastembed::NomicV2MoeTextEmbedding::from_hf(model_id, &device, dtype, max_length)
58 .map_err(KernelError::embedding)?;
59 let dim = te.config().hidden_size;
60 Ok(Self {
61 inner: te,
62 model_id: model_id.to_string(),
63 dim,
64 })
65 }
66
67 pub fn model_id(&self) -> &str {
69 &self.model_id
70 }
71}
72
73impl EmbeddingProvider for NomicMoeProvider {
74 fn dim(&self) -> usize {
75 self.dim
76 }
77
78 fn name(&self) -> &str {
79 &self.model_id
80 }
81
82 fn embed(&self, text: &str) -> Result<EmbeddingResult> {
83 let embeddings = self.inner.embed(&[text]).map_err(KernelError::embedding)?;
84 let vector = embeddings
85 .into_iter()
86 .next()
87 .ok_or_else(|| KernelError::Embedding("empty embedding output".into()))?;
88
89 let preview = if text.len() > 64 {
90 format!("{}…", &text[..64])
91 } else {
92 text.to_string()
93 };
94 Ok(EmbeddingResult {
95 vector,
96 text_preview: preview,
97 })
98 }
99
100 fn embed_batch(&self, texts: &[&str]) -> Result<Vec<EmbeddingResult>> {
101 if texts.is_empty() {
102 return Ok(vec![]);
103 }
104 let embeddings = self.inner.embed(texts).map_err(KernelError::embedding)?;
105 Ok(embeddings
106 .into_iter()
107 .zip(texts.iter())
108 .map(|(vector, &text)| {
109 let preview = if text.len() > 64 {
110 format!("{}…", &text[..64])
111 } else {
112 text.to_string()
113 };
114 EmbeddingResult {
115 vector,
116 text_preview: preview,
117 }
118 })
119 .collect())
120 }
121}
122
123#[cfg(test)]
124mod tests {
125 use super::*;
126
127 #[test]
128 fn model_id_constant() {
129 assert_eq!(NOMIC_EMBED_TEXT_V2_MOE, "nomic-ai/nomic-embed-text-v2-moe");
130 }
131
132 #[test]
133 #[ignore = "requires model download"]
134 fn embed_with_nomic_moe() {
135 let provider = NomicMoeProvider::new().unwrap();
136 let result = provider.embed("hello world").unwrap();
137 assert_eq!(result.vector.len(), 768);
139 assert_eq!(result.vector.len(), provider.dim());
140 }
141
142 #[test]
143 #[ignore = "requires model download"]
144 fn embed_batch_with_nomic_moe() {
145 let provider = NomicMoeProvider::new().unwrap();
146 let results = provider
147 .embed_batch(&["hello", "world", "foo bar"])
148 .unwrap();
149 assert_eq!(results.len(), 3);
150 for r in &results {
151 assert_eq!(r.vector.len(), 768);
152 }
153 }
154}