use std::sync::Arc;
use tokio::sync::Mutex;
use crate::embeddings::EmbeddingModel;
use crate::errors::{RagError, Result};
use crate::rerank::SimilarityReranker;
use crate::vector_store::Similarity;
#[cfg(feature = "fastembed")]
pub struct FastEmbedEmbeddingModel {
model: Mutex<Option<Arc<fastembed::TextEmbedding>>>,
options: fastembed::InitOptions,
}
#[cfg(feature = "fastembed")]
impl FastEmbedEmbeddingModel {
pub fn new() -> Self {
Self::with_options(fastembed::InitOptions::default())
}
pub fn with_model(model: fastembed::EmbeddingModel) -> Self {
Self::with_options(fastembed::InitOptions::new(model))
}
pub fn with_options(options: fastembed::InitOptions) -> Self {
Self {
model: Mutex::new(None),
options,
}
}
async fn get(&self) -> Result<Arc<fastembed::TextEmbedding>> {
let mut guard = self.model.lock().await;
if let Some(m) = guard.as_ref() {
return Ok(Arc::clone(m));
}
let m = Arc::new(
fastembed::TextEmbedding::try_new(self.options.clone())
.map_err(|e| RagError::EmbeddingError(format!("fastembed init: {e}")))?,
);
*guard = Some(Arc::clone(&m));
Ok(m)
}
}
#[cfg(feature = "fastembed")]
impl Default for FastEmbedEmbeddingModel {
fn default() -> Self {
Self::new()
}
}
#[cfg(feature = "fastembed")]
impl EmbeddingModel for FastEmbedEmbeddingModel {
async fn embed(&self, texts: Vec<String>) -> Result<Vec<Vec<f32>>> {
let model = self.get().await?;
tokio::task::spawn_blocking(move || model.embed(texts, None))
.await
.map_err(|e| RagError::EmbeddingError(format!("join error: {e}")))?
.map_err(|e| RagError::EmbeddingError(format!("fastembed embed: {e}")))
}
}
#[cfg(feature = "fastembed")]
pub struct FastEmbedReranker {
model: Mutex<Option<Arc<fastembed::TextRerank>>>,
options: fastembed::RerankInitOptions,
}
#[cfg(feature = "fastembed")]
impl FastEmbedReranker {
pub fn new() -> Self {
Self::with_model(fastembed::RerankerModel::BGERerankerBase)
}
pub fn with_model(model: fastembed::RerankerModel) -> Self {
Self::with_options(fastembed::RerankInitOptions::new(model))
}
pub fn with_options(options: fastembed::RerankInitOptions) -> Self {
Self {
model: Mutex::new(None),
options,
}
}
async fn get(&self) -> Result<Arc<fastembed::TextRerank>> {
let mut guard = self.model.lock().await;
if let Some(m) = guard.as_ref() {
return Ok(Arc::clone(m));
}
let m = Arc::new(
fastembed::TextRerank::try_new(self.options.clone())
.map_err(|e| RagError::EmbeddingError(format!("fastembed reranker init: {e}")))?,
);
*guard = Some(Arc::clone(&m));
Ok(m)
}
}
#[cfg(feature = "fastembed")]
impl Default for FastEmbedReranker {
fn default() -> Self {
Self::new()
}
}
#[cfg(feature = "fastembed")]
impl SimilarityReranker for FastEmbedReranker {
async fn rerank(&self, query: &str, items: Vec<Similarity>) -> Result<Vec<Similarity>> {
if items.is_empty() {
return Ok(items);
}
let model = self.get().await?;
let documents: Vec<String> = items.iter().map(|s| s.document.content.clone()).collect();
let query = query.to_string();
let results =
tokio::task::spawn_blocking(move || model.rerank(query, documents, false, None))
.await
.map_err(|e| RagError::EmbeddingError(format!("join error: {e}")))?
.map_err(|e| RagError::EmbeddingError(format!("fastembed rerank: {e}")))?;
let mut reranked = Vec::with_capacity(results.len());
for r in results {
if let Some(item) = items.get(r.index) {
let mut scored = item.clone();
scored.score = r.score;
reranked.push(scored);
}
}
Ok(reranked)
}
}
#[cfg(feature = "image-embeddings")]
pub struct FastEmbedImageEmbeddingModel {
model: Mutex<Option<Arc<fastembed::ImageEmbedding>>>,
options: fastembed::ImageInitOptions,
}
#[cfg(feature = "image-embeddings")]
impl FastEmbedImageEmbeddingModel {
pub fn new() -> Self {
Self::with_model(fastembed::ImageEmbeddingModel::ClipVitB32)
}
pub fn with_model(model: fastembed::ImageEmbeddingModel) -> Self {
Self::with_options(fastembed::ImageInitOptions::new(model))
}
pub fn with_options(options: fastembed::ImageInitOptions) -> Self {
Self {
model: Mutex::new(None),
options,
}
}
async fn get(&self) -> Result<Arc<fastembed::ImageEmbedding>> {
let mut guard = self.model.lock().await;
if let Some(m) = guard.as_ref() {
return Ok(Arc::clone(m));
}
let m = Arc::new(
fastembed::ImageEmbedding::try_new(self.options.clone())
.map_err(|e| RagError::EmbeddingError(format!("fastembed image init: {e}")))?,
);
*guard = Some(Arc::clone(&m));
Ok(m)
}
pub async fn embed_image(&self, image_bytes: Vec<u8>) -> Result<Vec<f32>> {
let model = self.get().await?;
let embedding = tokio::task::spawn_blocking(move || {
let images: &[&[u8]] = &[&image_bytes];
model.embed_bytes(images, None)
})
.await
.map_err(|e| RagError::EmbeddingError(format!("join error: {e}")))?
.map_err(|e| RagError::EmbeddingError(format!("fastembed image embed: {e}")))?;
embedding
.into_iter()
.next()
.ok_or_else(|| RagError::EmbeddingError("no image embedding returned".to_string()))
}
}
#[cfg(feature = "image-embeddings")]
impl Default for FastEmbedImageEmbeddingModel {
fn default() -> Self {
Self::new()
}
}