Merge pull request #22 from matthias-Q/fix_fastembed_cache

fix: specify cache location for fastembed_cache
This commit is contained in:
Sam Valladares 2026-04-02 01:27:01 -05:00 committed by GitHub
commit 0dd010a138
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 40 additions and 18 deletions

View file

@ -33,18 +33,26 @@ pub const BATCH_SIZE: usize = 32;
/// Result type for model initialization
static EMBEDDING_MODEL_RESULT: OnceLock<Result<Mutex<TextEmbedding>, String>> = OnceLock::new();
/// Get the default cache directory for fastembed models
/// Uses FASTEMBED_CACHE_PATH env var, or falls back to platform cache directory
fn get_cache_dir() -> std::path::PathBuf {
/// Get the default cache directory for fastembed models.
///
/// Resolution order:
/// 1. `FASTEMBED_CACHE_PATH` env var (explicit override)
/// 2. Platform cache dir via `directories::ProjectDirs`
/// - Linux: `$XDG_CACHE_HOME/vestige/fastembed` (typically `~/.cache/vestige/fastembed`)
/// - macOS: `~/Library/Caches/vestige/fastembed`
/// - Windows: `%LOCALAPPDATA%\vestige\cache\fastembed`
/// 3. `~/.cache/vestige/fastembed` (home-dir fallback)
/// 4. `.fastembed_cache` relative to CWD (absolute last resort, should never trigger)
pub(crate) fn get_cache_dir() -> std::path::PathBuf {
if let Ok(path) = std::env::var("FASTEMBED_CACHE_PATH") {
return std::path::PathBuf::from(path);
}
// Use platform-appropriate cache directory via directories crate
// macOS: ~/Library/Caches/com.vestige.core/fastembed
// Linux: ~/.cache/vestige/fastembed
// Windows: %LOCALAPPDATA%\vestige\cache\fastembed
if let Some(proj_dirs) = directories::ProjectDirs::from("com", "vestige", "core") {
// qualifier="" produces a clean app-name-only path on Linux/Windows;
// on macOS the qualifier is used for the bundle ID so we keep it empty
// to get ~/Library/Caches/vestige/fastembed rather than
// ~/Library/Caches/com.vestige.vestige/fastembed.
if let Some(proj_dirs) = directories::ProjectDirs::from("", "vestige", "vestige") {
return proj_dirs.cache_dir().join("fastembed");
}
@ -53,7 +61,7 @@ fn get_cache_dir() -> std::path::PathBuf {
return base_dirs.home_dir().join(".cache/vestige/fastembed");
}
// Last resort fallback (shouldn't happen)
// Last resort fallback (shouldn't happen in practice)
std::path::PathBuf::from(".fastembed_cache")
}
@ -210,9 +218,7 @@ impl Default for EmbeddingService {
impl EmbeddingService {
/// Create a new embedding service
pub fn new() -> Self {
Self {
_unused: (),
}
Self { _unused: () }
}
/// Check if the model is ready
@ -240,9 +246,13 @@ impl EmbeddingService {
/// Get the model name
pub fn model_name(&self) -> &'static str {
#[cfg(feature = "nomic-v2")]
{ "nomic-ai/nomic-embed-text-v2-moe" }
{
"nomic-ai/nomic-embed-text-v2-moe"
}
#[cfg(not(feature = "nomic-v2"))]
{ "nomic-ai/nomic-embed-text-v1.5" }
{
"nomic-ai/nomic-embed-text-v1.5"
}
}
/// Get the embedding dimensions

View file

@ -13,6 +13,7 @@ mod code;
mod hybrid;
mod local;
pub(crate) use local::get_cache_dir;
pub use local::{
cosine_similarity, dot_product, euclidean_distance, matryoshka_truncate, Embedding,
EmbeddingError, EmbeddingService, BATCH_SIZE, EMBEDDING_DIMENSIONS, MAX_TEXT_LENGTH,

View file

@ -10,6 +10,8 @@
//! Falls back to BM25-like term overlap scoring when the cross-encoder
//! model is unavailable.
#[cfg(feature = "embeddings")]
use crate::embeddings::get_cache_dir;
#[cfg(feature = "embeddings")]
use fastembed::{RerankInitOptions, RerankerModel, TextRerank};
@ -127,6 +129,7 @@ impl Reranker {
}
let options = RerankInitOptions::new(RerankerModel::JINARerankerV1TurboEn)
.with_cache_dir(get_cache_dir())
.with_show_download_progress(true);
match TextRerank::try_new(options) {
@ -163,7 +166,9 @@ impl Reranker {
top_k: Option<usize>,
) -> Result<Vec<RerankedResult<T>>, RerankerError> {
if query.is_empty() {
return Err(RerankerError::InvalidInput("Query cannot be empty".to_string()));
return Err(RerankerError::InvalidInput(
"Query cannot be empty".to_string(),
));
}
if candidates.is_empty() {
@ -190,7 +195,9 @@ impl Reranker {
.collect();
results.sort_by(|a, b| {
b.score.partial_cmp(&a.score).unwrap_or(std::cmp::Ordering::Equal)
b.score
.partial_cmp(&a.score)
.unwrap_or(std::cmp::Ordering::Equal)
});
if let Some(min_score) = self.config.min_score {
@ -217,7 +224,11 @@ impl Reranker {
})
.collect();
results.sort_by(|a, b| b.score.partial_cmp(&a.score).unwrap_or(std::cmp::Ordering::Equal));
results.sort_by(|a, b| {
b.score
.partial_cmp(&a.score)
.unwrap_or(std::cmp::Ordering::Equal)
});
if let Some(min_score) = self.config.min_score {
results.retain(|r| r.score >= min_score);

View file

@ -180,7 +180,7 @@ async fn main() {
if let Err(e) = s.init_embeddings() {
error!("Failed to initialize embedding service: {}", e);
error!("Smart ingest will fall back to regular ingest without deduplication");
error!("Hint: Check FASTEMBED_CACHE_PATH or ensure ~/.fastembed_cache exists");
error!("Hint: Check FASTEMBED_CACHE_PATH or ensure ~/.cache/vestige/fastembed is writable");
} else {
info!("Embedding service initialized successfully");
}