1use std::num::NonZeroUsize;
7use std::sync::{Arc, Mutex};
8
9use async_trait::async_trait;
10use fastembed::{EmbeddingModel, InitOptions, TextEmbedding};
11use lru::LruCache;
12use tokio::task::spawn_blocking;
13
14use crate::embedding::Embedding;
15use crate::error::EmbedError;
16use crate::provider::EmbeddingProvider;
17
18pub struct BgeM3Provider {
23 model: Arc<Mutex<TextEmbedding>>,
25
26 cache: Mutex<LruCache<u64, Vec<f32>>>,
28}
29
30impl BgeM3Provider {
31 pub fn new() -> Result<Self, EmbedError> {
33 Self::with_cache_size(1000)
34 }
35
36 pub fn with_cache_size(cache_size: usize) -> Result<Self, EmbedError> {
38 let model = TextEmbedding::try_new(InitOptions::new(EmbeddingModel::BGELargeENV15))
39 .map_err(|e| EmbedError::ModelInit(e.to_string()))?;
40
41 let cache = Mutex::new(LruCache::new(
42 NonZeroUsize::new(cache_size).unwrap_or(NonZeroUsize::new(1).unwrap()),
43 ));
44
45 Ok(Self {
46 model: Arc::new(Mutex::new(model)),
47 cache,
48 })
49 }
50
51 fn get_cached(&self, text_hash: u64) -> Option<Vec<f32>> {
53 self.cache.lock().ok()?.get(&text_hash).cloned()
54 }
55
56 fn set_cached(&self, text_hash: u64, vector: Vec<f32>) {
58 if let Ok(mut cache) = self.cache.lock() {
59 cache.put(text_hash, vector);
60 }
61 }
62}
63
64#[async_trait]
65impl EmbeddingProvider for BgeM3Provider {
66 async fn embed(&self, texts: &[&str]) -> Result<Vec<Embedding>, EmbedError> {
67 if texts.is_empty() {
68 return Ok(Vec::new());
69 }
70
71 for text in texts {
73 if text.is_empty() {
74 return Err(EmbedError::EmptyInput);
75 }
76 }
77
78 let hashes: Vec<u64> = texts
80 .iter()
81 .map(|t| xxhash_rust::xxh64::xxh64(t.as_bytes(), 0))
82 .collect();
83
84 let mut results = vec![None; texts.len()];
85 let mut to_embed: Vec<(usize, String)> = Vec::new();
86
87 for (i, (text, hash)) in texts.iter().zip(hashes.iter()).enumerate() {
88 if let Some(cached) = self.get_cached(*hash) {
89 results[i] = Some(Embedding::from_normalized(cached, *hash));
90 } else {
91 to_embed.push((i, text.to_string()));
92 }
93 }
94
95 if !to_embed.is_empty() {
97 let texts_to_embed: Vec<String> = to_embed.iter().map(|(_, t)| t.clone()).collect();
98
99 let model = Arc::clone(&self.model);
101 let embeddings = spawn_blocking(move || {
102 let guard = model.lock().map_err(|e| EmbedError::Embedding(e.to_string()))?;
103 guard.embed(texts_to_embed, None)
104 .map_err(|e| EmbedError::Embedding(e.to_string()))
105 })
106 .await
107 .map_err(|e| EmbedError::Embedding(e.to_string()))??;
108
109 for ((idx, _), vector) in to_embed.iter().zip(embeddings.into_iter()) {
110 let hash = hashes[*idx];
111 self.set_cached(hash, vector.clone());
112 results[*idx] = Some(Embedding::new(vector, hash));
113 }
114 }
115
116 Ok(results.into_iter().map(|r| r.unwrap()).collect())
117 }
118
119 fn dimensions(&self) -> usize {
120 1024 }
122
123 fn model_id(&self) -> &str {
124 "bge-large-en-v1.5"
125 }
126
127 fn max_batch_size(&self) -> usize {
128 32
129 }
130}
131
132#[cfg(test)]
133mod tests {
134 use super::*;
138
139 #[tokio::test]
140 #[ignore = "requires model download"]
141 async fn test_bge_m3_embed() {
142 let provider = BgeM3Provider::new().unwrap();
143 let embedding = provider.embed_one("Hello, world!").await.unwrap();
144
145 assert_eq!(embedding.dimensions(), 1024);
146 assert!(embedding.is_normalized());
147 }
148
149 #[tokio::test]
150 #[ignore = "requires model download"]
151 async fn test_bge_m3_batch() {
152 let provider = BgeM3Provider::new().unwrap();
153 let texts = vec!["Hello", "World", "Test"];
154 let embeddings = provider.embed(&texts).await.unwrap();
155
156 assert_eq!(embeddings.len(), 3);
157 }
158
159 #[tokio::test]
160 #[ignore = "requires model download"]
161 async fn test_bge_m3_cache() {
162 let provider = BgeM3Provider::new().unwrap();
163
164 let e1 = provider.embed_one("test text").await.unwrap();
165 let e2 = provider.embed_one("test text").await.unwrap();
166
167 assert_eq!(e1.text_hash, e2.text_hash);
169 }
170
171 #[tokio::test]
172 #[ignore = "requires model download"]
173 async fn test_bge_m3_chinese() {
174 let provider = BgeM3Provider::new().unwrap();
175 let embedding = provider.embed_one("你好世界").await.unwrap();
176
177 assert_eq!(embedding.dimensions(), 1024);
178 }
179}