Skip to content

Commit 1b60a62

Browse files
committed
fix: resolve embedding model load failure on Windows
Switch from ort-load-dynamic to ort-download-binaries to prevent loading an incompatible onnxruntime.dll from the system PATH (e.g. Calibre). Add HF_HOME and HUGGINGFACE_HUB_CACHE as implicit cache directory fallbacks so the model is correctly discovered when users already have HuggingFace models cached in a non-default location. Remove .with_show_download_progress(true) to avoid polluting the MCP stdio JSON-RPC stream with progress bar output.
1 parent 8676d9f commit 1b60a62

3 files changed

Lines changed: 92 additions & 17 deletions

File tree

Cargo.lock

Lines changed: 18 additions & 11 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,7 @@ zstd = "0.13.3"
143143
ureq = "3"
144144

145145
# Embeddings
146-
fastembed = { version = "5.13.3", default-features = false, features = ["ort-load-dynamic", "hf-hub-native-tls"] }
146+
fastembed = { version = "5.13.3", default-features = false, features = ["ort-download-binaries", "hf-hub-native-tls"] }
147147

148148
# TOON format
149149
toon-format = { version = "0.4.1", default-features = false }

crates/rpg-nav/src/embeddings.rs

Lines changed: 73 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -330,14 +330,25 @@ fn compute_fingerprint(features: &[String]) -> String {
330330

331331
fn resolve_shared_model_cache_dir_from_env(
332332
rpg_model_cache_dir: Option<&std::ffi::OsStr>,
333+
hf_hub_cache: Option<&std::ffi::OsStr>,
334+
hf_home: Option<&std::ffi::OsStr>,
333335
xdg_cache_home: Option<&std::ffi::OsStr>,
334336
home: Option<&std::ffi::OsStr>,
335337
userprofile: Option<&std::ffi::OsStr>,
336338
) -> Result<PathBuf> {
339+
// Priority 1: explicit RPG override
337340
if let Some(path) = rpg_model_cache_dir {
338341
return Ok(PathBuf::from(path));
339342
}
340-
343+
// Priority 2: HuggingFace Hub cache (points directly at hub cache root)
344+
if let Some(path) = hf_hub_cache {
345+
return Ok(PathBuf::from(path));
346+
}
347+
// Priority 3: HF_HOME (model repos live directly under this directory)
348+
if let Some(path) = hf_home {
349+
return Ok(PathBuf::from(path));
350+
}
351+
// Priority 4: default ~/.cache/rpg-encoder/models/fastembed
341352
let home_dir = home
342353
.map(PathBuf::from)
343354
.or_else(|| userprofile.map(PathBuf::from))
@@ -357,6 +368,8 @@ fn resolve_shared_model_cache_dir_from_env(
357368
fn shared_model_cache_dir() -> Result<PathBuf> {
358369
let cache_dir = resolve_shared_model_cache_dir_from_env(
359370
std::env::var_os("RPG_MODEL_CACHE_DIR").as_deref(),
371+
std::env::var_os("HUGGINGFACE_HUB_CACHE").as_deref(),
372+
std::env::var_os("HF_HOME").as_deref(),
360373
std::env::var_os("XDG_CACHE_HOME").as_deref(),
361374
std::env::var_os("HOME").as_deref(),
362375
std::env::var_os("USERPROFILE").as_deref(),
@@ -366,11 +379,15 @@ fn shared_model_cache_dir() -> Result<PathBuf> {
366379
}
367380

368381
/// Initialize the fastembed model with a shared machine-level cache.
382+
///
383+
/// IMPORTANT: Do NOT add `.with_show_download_progress(true)` — the MCP server
384+
/// uses stdio for JSON-RPC transport and any stdout/stderr noise from progress
385+
/// bars can corrupt the protocol stream, causing deadlocks and zombie processes.
369386
fn init_model() -> Result<TextEmbedding> {
370387
let cache_dir = shared_model_cache_dir()?;
371-
let options = fastembed::TextInitOptions::new(EmbeddingModel::BGESmallENV15)
372-
.with_show_download_progress(true)
373-
.with_cache_dir(cache_dir);
388+
389+
let options =
390+
fastembed::TextInitOptions::new(EmbeddingModel::BGESmallENV15).with_cache_dir(cache_dir);
374391

375392
let model = TextEmbedding::try_new(options)
376393
.context("failed to initialize embedding model (BGE-small-en-v1.5)")?;
@@ -569,6 +586,8 @@ mod tests {
569586
fn test_resolve_shared_model_cache_dir_prefers_explicit_override() {
570587
let path = resolve_shared_model_cache_dir_from_env(
571588
Some(std::ffi::OsStr::new("D:/cache/rpg-models")),
589+
Some(std::ffi::OsStr::new("C:/hf-hub-cache")),
590+
Some(std::ffi::OsStr::new("C:/hf-home")),
572591
Some(std::ffi::OsStr::new("/tmp/xdg-cache")),
573592
Some(std::ffi::OsStr::new("/home/tester")),
574593
Some(std::ffi::OsStr::new("C:/Users/tester")),
@@ -580,6 +599,8 @@ mod tests {
580599
#[test]
581600
fn test_resolve_shared_model_cache_dir_uses_xdg_on_linux() {
582601
let path = resolve_shared_model_cache_dir_from_env(
602+
None,
603+
None,
583604
None,
584605
Some(std::ffi::OsStr::new("/tmp/xdg-cache")),
585606
Some(std::ffi::OsStr::new("/home/tester")),
@@ -603,6 +624,8 @@ mod tests {
603624
#[test]
604625
fn test_resolve_shared_model_cache_dir_falls_back_to_home_cache() {
605626
let path = resolve_shared_model_cache_dir_from_env(
627+
None,
628+
None,
606629
None,
607630
None,
608631
Some(std::ffi::OsStr::new("/home/tester")),
@@ -618,6 +641,8 @@ mod tests {
618641
#[test]
619642
fn test_resolve_shared_model_cache_dir_uses_userprofile_when_home_missing() {
620643
let path = resolve_shared_model_cache_dir_from_env(
644+
None,
645+
None,
621646
None,
622647
None,
623648
None,
@@ -632,13 +657,56 @@ mod tests {
632657

633658
#[test]
634659
fn test_resolve_shared_model_cache_dir_errors_without_home_or_override() {
635-
let err = resolve_shared_model_cache_dir_from_env(None, None, None, None).unwrap_err();
660+
let err = resolve_shared_model_cache_dir_from_env(None, None, None, None, None, None)
661+
.unwrap_err();
636662
assert!(
637663
err.to_string()
638664
.contains("could not determine home directory")
639665
);
640666
}
641667

668+
#[test]
669+
fn test_resolve_prefers_hf_hub_cache_over_hf_home() {
670+
let path = resolve_shared_model_cache_dir_from_env(
671+
None,
672+
Some(std::ffi::OsStr::new("C:/hf-hub-cache")),
673+
Some(std::ffi::OsStr::new("C:/hf-home")),
674+
None,
675+
Some(std::ffi::OsStr::new("/home/tester")),
676+
None,
677+
)
678+
.unwrap();
679+
assert_eq!(path, PathBuf::from("C:/hf-hub-cache"));
680+
}
681+
682+
#[test]
683+
fn test_resolve_uses_hf_home_when_no_explicit_hub_cache() {
684+
let path = resolve_shared_model_cache_dir_from_env(
685+
None,
686+
None,
687+
Some(std::ffi::OsStr::new("C:/Models_LLM")),
688+
None,
689+
Some(std::ffi::OsStr::new("/home/tester")),
690+
None,
691+
)
692+
.unwrap();
693+
assert_eq!(path, PathBuf::from("C:/Models_LLM"));
694+
}
695+
696+
#[test]
697+
fn test_resolve_rpg_override_beats_hf_vars() {
698+
let path = resolve_shared_model_cache_dir_from_env(
699+
Some(std::ffi::OsStr::new("D:/rpg-models")),
700+
Some(std::ffi::OsStr::new("C:/hf-hub-cache")),
701+
Some(std::ffi::OsStr::new("C:/hf-home")),
702+
None,
703+
Some(std::ffi::OsStr::new("/home/tester")),
704+
None,
705+
)
706+
.unwrap();
707+
assert_eq!(path, PathBuf::from("D:/rpg-models"));
708+
}
709+
642710
#[test]
643711
fn test_cosine_similarity_identical() {
644712
let a = vec![1.0, 0.0, 0.0];

0 commit comments

Comments
 (0)