use std::path::{Path, PathBuf};
use anyhow::{Context, Result};
use llama_gguf::HfClient;
const MODEL_REPO: &str = "unsloth/embeddinggemma-300m-GGUF";
const MODEL_FILE: &str = "embeddinggemma-300m-Q4_K_M.gguf";
pub const MODEL_SIZE_MB: u64 = 329;
pub const MODEL_DISPLAY_NAME: &str = "EmbeddingGemma-300m";
pub struct GgufModelLoader;
impl GgufModelLoader {
pub fn ensure_model(model_dir: &Path) -> Result<PathBuf> {
let gguf_path = model_dir.join(MODEL_FILE);
if gguf_path.exists() {
tracing::debug!(path = %gguf_path.display(), "Model already cached");
return Ok(gguf_path);
}
std::fs::create_dir_all(model_dir)
.with_context(|| format!("Failed to create model dir: {}", model_dir.display()))?;
tracing::info!(
repo = MODEL_REPO,
file = MODEL_FILE,
"Downloading EmbeddingGemma GGUF model (~329MB)..."
);
let hf = HfClient::with_cache_dir(model_dir.to_path_buf());
let downloaded = hf
.download_file(MODEL_REPO, MODEL_FILE, true)
.with_context(|| format!("Failed to download {} from {}", MODEL_FILE, MODEL_REPO))?;
if downloaded != gguf_path {
std::fs::copy(&downloaded, &gguf_path).with_context(|| {
format!(
"Failed to copy {} to {}",
downloaded.display(),
gguf_path.display()
)
})?;
}
tracing::info!(path = %gguf_path.display(), "Model download complete");
Ok(gguf_path)
}
pub fn is_model_cached(model_dir: &Path) -> bool {
model_dir.join(MODEL_FILE).exists()
}
pub fn spawn_prefetch(model_dir: PathBuf) {
if Self::is_model_cached(&model_dir) {
tracing::debug!("Embedding model already cached, skipping prefetch");
return;
}
tracing::info!(dir = %model_dir.display(), "Spawning background model prefetch (~329MB)");
tokio::task::spawn_blocking(move || match Self::ensure_model(&model_dir) {
Ok(path) => {
tracing::info!(path = %path.display(), "Model prefetch complete");
}
Err(e) => {
tracing::warn!(
error = %e,
"Model prefetch failed — will retry on first search"
);
}
});
}
pub fn model_dir_for_workspace(workspace: &Path) -> PathBuf {
workspace.join("models").join("embeddinggemma-300m")
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_model_dir_path() {
let dir =
GgufModelLoader::model_dir_for_workspace(Path::new("/home/user/.oxios/workspace"));
assert_eq!(
dir,
PathBuf::from("/home/user/.oxios/workspace/models/embeddinggemma-300m")
);
}
#[test]
fn test_is_model_cached_false() {
let dir = PathBuf::from("/nonexistent/path");
assert!(!GgufModelLoader::is_model_cached(&dir));
}
#[test]
fn test_is_model_cached_true() {
let dir = tempfile::tempdir().unwrap();
let model_path = dir.path().join("embeddinggemma-300m-Q4_K_M.gguf");
std::fs::write(&model_path, b"fake model").unwrap();
assert!(GgufModelLoader::is_model_cached(dir.path()));
}
}