use std::sync::{Arc, Mutex};
use async_trait::async_trait;
use cel_memory::{Embedder, EmbedderError, EmbedderResult};
use fastembed::{EmbeddingModel, InitOptions, TextEmbedding};
pub const DEFAULT_MODEL_NAME: &str = "bge-small-en-v1.5";
pub const DEFAULT_DIM: usize = 384;
pub struct FastEmbedEmbedder {
inner: Arc<Mutex<TextEmbedding>>,
dim: usize,
model_name: String,
}
impl FastEmbedEmbedder {
pub fn new() -> Result<Self, EmbedderError> {
let inner = TextEmbedding::try_new(InitOptions::new(EmbeddingModel::BGESmallENV15))
.map_err(|e| EmbedderError::Provider(format!("fastembed init: {e}")))?;
Ok(Self {
inner: Arc::new(Mutex::new(inner)),
dim: DEFAULT_DIM,
model_name: DEFAULT_MODEL_NAME.into(),
})
}
pub fn with_options(
opts: InitOptions,
dim: usize,
model_name: impl Into<String>,
) -> Result<Self, EmbedderError> {
let inner = TextEmbedding::try_new(opts)
.map_err(|e| EmbedderError::Provider(format!("fastembed init: {e}")))?;
Ok(Self {
inner: Arc::new(Mutex::new(inner)),
dim,
model_name: model_name.into(),
})
}
}
#[async_trait]
impl Embedder for FastEmbedEmbedder {
fn dim(&self) -> usize {
self.dim
}
fn model_name(&self) -> &str {
&self.model_name
}
async fn embed(&self, text: &str) -> EmbedderResult<Vec<f32>> {
let text = text.to_string();
let inner = Arc::clone(&self.inner);
let v = tokio::task::spawn_blocking(move || -> EmbedderResult<Vec<f32>> {
let mut guard = inner
.lock()
.map_err(|e| EmbedderError::Internal(format!("mutex poisoned: {e}")))?;
let out = guard
.embed(vec![text], None)
.map_err(|e| EmbedderError::Provider(format!("fastembed embed: {e}")))?;
out.into_iter()
.next()
.ok_or_else(|| EmbedderError::Provider("fastembed returned no vectors".into()))
})
.await
.map_err(|e| EmbedderError::Internal(format!("blocking task panicked: {e}")))??;
Ok(v)
}
async fn embed_batch(&self, texts: &[String]) -> EmbedderResult<Vec<Vec<f32>>> {
let texts = texts.to_vec();
let inner = Arc::clone(&self.inner);
let out = tokio::task::spawn_blocking(move || -> EmbedderResult<Vec<Vec<f32>>> {
let mut guard = inner
.lock()
.map_err(|e| EmbedderError::Internal(format!("mutex poisoned: {e}")))?;
guard
.embed(texts, None)
.map_err(|e| EmbedderError::Provider(format!("fastembed embed: {e}")))
})
.await
.map_err(|e| EmbedderError::Internal(format!("blocking task panicked: {e}")))??;
Ok(out)
}
}