use fastembed::{TextEmbedding, InitOptions, EmbeddingModel};
use anyhow::Result;
use std::path::PathBuf;
fn resolve_cache_dir() -> PathBuf {
if let Ok(dir) = std::env::var("FASTEMBED_CACHE_DIR") {
return PathBuf::from(dir);
}
let home = std::env::var("HOME").unwrap_or_else(|_| ".".to_string());
PathBuf::from(home).join(".cache").join("fastembed")
}
pub struct EmbeddingsGenerator {
model: TextEmbedding,
}
impl EmbeddingsGenerator {
pub fn new() -> Result<Self> {
Self::with_model(EmbeddingModel::EmbeddingGemma300M)
}
pub fn with_model(model: EmbeddingModel) -> Result<Self> {
#[cfg(all(target_os = "macos", target_arch = "aarch64"))]
{
eprintln!("[perf] macOS aarch64 build detected; ONNX Runtime CoreML/Metal acceleration is enabled if available.");
if let Ok(val) = std::env::var("TOAK_EMBED_DEVICE") {
eprintln!("[perf] TOAK_EMBED_DEVICE={} (informational)", val);
}
}
#[cfg(all(target_os = "macos", target_arch = "aarch64"))]
let text_embedding = {
let try_init = |m: EmbeddingModel| {
TextEmbedding::try_new(
InitOptions::new(m)
.with_cache_dir(resolve_cache_dir())
.with_show_download_progress(true),
)
};
match try_init(model.clone()) {
Ok(ok) => {
let coreml_disabled = std::env::var("ORT_DISABLE_COREML").ok().unwrap_or_default();
if coreml_disabled == "1" {
eprintln!("[perf] ONNX Runtime CoreML disabled by ORT_DISABLE_COREML=1; using CPU backend.");
} else {
eprintln!("[perf] Attempting CoreML/Metal acceleration (CPU fallback if unavailable)...");
}
ok
}
Err(e) => {
eprintln!("[warn] fastembed initialization failed (CoreML path?): {}", e);
eprintln!("[warn] Retrying embeddings initialization with CPU backend (disabling CoreML).");
std::env::set_var("ORT_DISABLE_COREML", "1");
let retried = try_init(model)?;
eprintln!("[perf] Fallback successful: using CPU backend for embeddings.");
retried
}
}
};
#[cfg(not(all(target_os = "macos", target_arch = "aarch64")))]
let text_embedding = TextEmbedding::try_new(
InitOptions::new(model)
.with_cache_dir(resolve_cache_dir())
.with_show_download_progress(true),
)?;
Ok(Self {
model: text_embedding,
})
}
pub fn generate_embeddings(&mut self, texts: Vec<&str>, batch_size: Option<usize>) -> Result<Vec<Vec<f32>>> {
let embeddings = self.model.embed(texts, batch_size)?;
Ok(embeddings)
}
pub fn generate_embedding(&mut self, text: &str) -> Result<Vec<f32>> {
let embeddings = self.generate_embeddings(vec![text], None)?;
embeddings.into_iter().next()
.ok_or_else(|| anyhow::anyhow!("Failed to generate embedding"))
}
}