recall_echo/graph/
embed.rs1use std::path::Path;
8
9use fastembed::{EmbeddingModel, InitOptions, TextEmbedding};
10
11use super::error::GraphError;
12
13pub trait Embedder: Send + Sync {
15 fn embed(&self, texts: Vec<&str>) -> Result<Vec<Vec<f32>>, GraphError>;
16 fn embed_single(&self, text: &str) -> Result<Vec<f32>, GraphError>;
17 fn dimensions(&self) -> usize;
18}
19
20pub struct FastEmbedder {
22 model: TextEmbedding,
23}
24
25impl FastEmbedder {
26 pub fn new(cache_dir: &Path) -> Result<Self, GraphError> {
27 let options = InitOptions::new(EmbeddingModel::BGESmallENV15)
28 .with_cache_dir(cache_dir.to_path_buf())
29 .with_show_download_progress(true);
30
31 let model =
32 TextEmbedding::try_new(options).map_err(|e| GraphError::Embed(e.to_string()))?;
33 Ok(Self { model })
34 }
35}
36
37pub struct LazyEmbedder {
45 cache_dir: std::path::PathBuf,
46 cell: std::sync::OnceLock<FastEmbedder>,
47 init_lock: std::sync::Mutex<()>,
48}
49
50impl LazyEmbedder {
51 pub fn new(cache_dir: &Path) -> Self {
52 Self {
53 cache_dir: cache_dir.to_path_buf(),
54 cell: std::sync::OnceLock::new(),
55 init_lock: std::sync::Mutex::new(()),
56 }
57 }
58
59 pub fn get(&self) -> Result<&FastEmbedder, GraphError> {
61 if let Some(e) = self.cell.get() {
62 return Ok(e);
63 }
64 let _guard = self
66 .init_lock
67 .lock()
68 .map_err(|_| GraphError::Embed("embedder init lock poisoned".into()))?;
69 if self.cell.get().is_none() {
70 let embedder = FastEmbedder::new(&self.cache_dir)?;
71 let _ = self.cell.set(embedder);
72 }
73 self.cell
74 .get()
75 .ok_or_else(|| GraphError::Embed("embedder cell empty after init".into()))
76 }
77}
78
79impl Embedder for FastEmbedder {
80 fn embed(&self, texts: Vec<&str>) -> Result<Vec<Vec<f32>>, GraphError> {
81 let docs: Vec<String> = texts.into_iter().map(|t| t.to_string()).collect();
82 let embeddings = self
83 .model
84 .embed(docs, None)
85 .map_err(|e| GraphError::Embed(e.to_string()))?;
86 Ok(embeddings)
87 }
88
89 fn embed_single(&self, text: &str) -> Result<Vec<f32>, GraphError> {
90 let embeddings = self
91 .model
92 .embed(vec![text.to_string()], None)
93 .map_err(|e| GraphError::Embed(e.to_string()))?;
94 embeddings
95 .into_iter()
96 .next()
97 .ok_or_else(|| GraphError::Embed("no embedding returned".into()))
98 }
99
100 fn dimensions(&self) -> usize {
101 384
102 }
103}