use anyhow::{anyhow, Result};
use fastembed::{InitOptions, TextEmbedding};
use parking_lot::Mutex;
use std::path::PathBuf;
use std::sync::Arc;
pub use fastembed::EmbeddingModel as Model;
pub type Embedding = Vec<f32>;
#[derive(Clone)]
pub struct EmbeddingEngine {
inner: Arc<Mutex<TextEmbedding>>,
}
impl EmbeddingEngine {
pub fn new(model: Model, cache_dir: Option<PathBuf>) -> Result<Self> {
let options = {
let opts = InitOptions::new(model);
match cache_dir {
Some(dir) => opts.with_cache_dir(dir),
None => opts,
}
};
let model = TextEmbedding::try_new(options)
.map_err(|e| anyhow!("failed to initialise embedding model: {e}"))?;
Ok(Self {
inner: Arc::new(Mutex::new(model)),
})
}
pub fn embed(&self, text: &str) -> Result<Embedding> {
self.inner
.lock()
.embed(vec![text], None)
.map_err(|e| anyhow!("embedding failed: {e}"))?
.into_iter()
.next()
.ok_or_else(|| anyhow!("model returned no embedding"))
}
pub fn embed_batch(&self, texts: &[&str]) -> Result<Vec<Embedding>> {
if texts.is_empty() {
return Ok(vec![]);
}
self.inner
.lock()
.embed(texts.to_vec(), None)
.map_err(|e| anyhow!("batch embedding failed: {e}"))
}
}