use hf_hub::api::sync::Api;
use std::path::PathBuf;
#[derive(Debug)]
pub struct DownloadedModel {
pub artifact: PathBuf,
pub kind: ArtifactKind,
pub siblings: Vec<PathBuf>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ArtifactKind {
Safetensors,
Gguf,
Onnx,
}
impl DownloadedModel {
pub fn snapshot_dir(&self) -> Option<&std::path::Path> {
self.artifact.parent()
}
}
const SIBLING_CANDIDATES: &[&str] = &[
"config.json",
"tokenizer.json",
"tokenizer.model",
"tokenizer_config.json",
"special_tokens_map.json",
"generation_config.json",
];
pub fn download_model(model_id: &str) -> Result<DownloadedModel, String> {
let api = Api::new().map_err(|e| format!("HF API init failed: {e}"))?;
let repo = api.model(model_id.to_string());
let info = repo
.info()
.map_err(|e| format!("repo info for {model_id} failed: {e}"))?;
let filenames: Vec<String> = info.siblings.iter().map(|s| s.rfilename.clone()).collect();
let (artifact_name, kind) = pick_artifact(&filenames).ok_or_else(|| {
format!("no recognized artifact in {model_id}; tried safetensors / gguf / onnx")
})?;
log::info!("Downloading artifact: {artifact_name}");
let artifact = repo
.get(&artifact_name)
.map_err(|e| format!("download {artifact_name} from {model_id} failed: {e}"))?;
let mut siblings: Vec<PathBuf> = Vec::new();
if artifact_name.ends_with(".safetensors.index.json") {
let shards: Vec<&String> = filenames
.iter()
.filter(|f| f.ends_with(".safetensors") && f.contains("of"))
.collect();
for shard in &shards {
log::info!("Downloading shard: {shard}");
let p = repo
.get(shard.as_str())
.map_err(|e| format!("download {shard} failed: {e}"))?;
siblings.push(p);
}
} else if artifact_name.ends_with(".onnx") {
let data_name = format!("{artifact_name}_data");
if filenames.iter().any(|f| f == &data_name) {
if let Ok(p) = repo.get(&data_name) {
log::info!("Downloaded ONNX external data: {}", p.display());
siblings.push(p);
}
}
}
for name in SIBLING_CANDIDATES {
if filenames.iter().any(|f| f == name) {
match repo.get(name) {
Ok(p) => {
log::info!("Downloaded sibling: {name}");
siblings.push(p);
}
Err(e) => log::warn!("sibling {name} listed but download failed: {e}"),
}
}
}
Ok(DownloadedModel {
artifact,
kind,
siblings,
})
}
fn pick_artifact(filenames: &[String]) -> Option<(String, ArtifactKind)> {
if filenames.iter().any(|f| f == "model.safetensors") {
return Some(("model.safetensors".to_string(), ArtifactKind::Safetensors));
}
if filenames
.iter()
.any(|f| f == "model.safetensors.index.json")
{
return Some((
"model.safetensors.index.json".to_string(),
ArtifactKind::Safetensors,
));
}
if let Some(name) = filenames.iter().find(|f| f.ends_with(".gguf")) {
return Some((name.clone(), ArtifactKind::Gguf));
}
if let Some(name) = filenames.iter().find(|f| f.ends_with(".onnx")) {
return Some((name.clone(), ArtifactKind::Onnx));
}
None
}