From 4d011df66a4e1099a97bcd106d16a6e8ac41ff89 Mon Sep 17 00:00:00 2001 From: EddieLin <147571178+Longado@users.noreply.github.com> Date: Sun, 13 Sep 2026 18:04:51 +0800 Subject: [PATCH] =?UTF-8?q?fix(model-store):=20=E6=8E=A5=E5=8F=97=20HF=20t?= =?UTF-8?q?ree=20API=20=E8=BF=94=E5=9B=9E=E7=9A=84=E8=A3=B8=20LFS=20oid?= =?UTF-8?q?=EF=BC=8C=E4=BF=AE=E5=A4=8D=E6=9C=AC=E5=9C=B0=E6=A8=A1=E5=9E=8B?= =?UTF-8?q?=E4=B8=8B=E8=BD=BD=E5=A4=B1=E8=B4=A5=20(#1072)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * test(model-store): 复现 HF tree API 返回裸 LFS oid 时模型下载被拒 huggingface.co 与 hf-mirror.com 的 /api/models/{repo}/tree 接口里 lfs.oid 是不带 sha256: 前缀的 64 位十六进制,现有解析只认前缀形式,导致本地模型 下载直接报 unsupported Hugging Face LFS oid。 * fix(model-store): 接受 HF tree API 返回的裸 LFS oid,修复本地模型下载失败 2.0 起 parse_lfs_sha256 只接受 sha256: 前缀,但 huggingface.co 与 hf-mirror.com 的 tree 接口返回的是裸 64 位十六进制,导致下载 Qwen3-ASR 等本地模型时直接报 unsupported Hugging Face LFS oid。改为前缀可选, 仍校验 64 位十六进制。 --- .../crates/openless-core/src/model_store.rs | 31 +++++++++++++++++-- 1 file changed, 28 insertions(+), 3 deletions(-) diff --git a/openless-all/app/crates/openless-core/src/model_store.rs b/openless-all/app/crates/openless-core/src/model_store.rs index a87aa0a4..ab61b181 100644 --- a/openless-all/app/crates/openless-core/src/model_store.rs +++ b/openless-all/app/crates/openless-core/src/model_store.rs @@ -2485,10 +2485,10 @@ fn parse_hf_tree_page_for_entry( Ok(files) } +/// The Hugging Face tree API returns `lfs.oid` as a bare SHA-256 hex digest; +/// the `sha256:`-prefixed form (as in Git LFS pointer files) is accepted too. fn parse_lfs_sha256(oid: &str) -> Result { - let digest = oid - .strip_prefix("sha256:") - .ok_or_else(|| invalid("unsupported Hugging Face LFS oid"))?; + let digest = oid.strip_prefix("sha256:").unwrap_or(oid); if digest.len() != 64 || !digest.bytes().all(|byte| byte.is_ascii_hexdigit()) { return Err(invalid("invalid Hugging Face LFS sha256 oid")); } @@ -2940,6 +2940,31 @@ mod tests { .is_err()); } + #[test] + fn hf_tree_accepts_bare_lfs_oid_from_live_api() { + // Shape returned by huggingface.co and hf-mirror.com for Qwen/Qwen3-ASR-0.6B + // (2026-09-13): `lfs.oid` is the bare hex digest, without a `sha256:` prefix. + let entries = vec![serde_json::json!({ + "type": "file", + "oid": "3d4b2a1f0e9c8b7a6d5e4f3a2b1c0d9e8f7a6b5c", + "size": 1876091704u64, + "path": "model.safetensors", + "lfs": { + "oid": "79d6cbd4c98c7bbffe9db2edac07f56cd6637d0d5944b27f6c2b8353840323ea", + "size": 1876091704u64, + "pointerSize": 135 + } + })]; + let files = parse_hf_tree_page("Qwen/Qwen3-ASR-0.6B", "qwen3-asr-0.6b", &entries).unwrap(); + assert_eq!(files.len(), 1); + assert_eq!(files[0].path, "model.safetensors"); + assert_eq!(files[0].size_bytes, 1_876_091_704); + assert_eq!( + files[0].sha256.as_deref(), + Some("79d6cbd4c98c7bbffe9db2edac07f56cd6637d0d5944b27f6c2b8353840323ea") + ); + } + #[test] fn hf_link_pagination_accepts_same_origin_and_rejects_redirected_origin() { let current = "https://huggingface.co/api/models/org/model/tree/main?limit=1000";