fix(ds4): resolve repository imports to GGUF

Select a concrete DeepSeek V4 Flash GGUF from Hugging Face metadata while honoring quantization preference order. Reject unresolved repository imports instead of emitting an invalid bare URI.

Assisted-by: Codex:gpt-5
This commit is contained in:
localai-org-maint-bot
2026-08-04 04:17:10 +00:00
parent 2e14511fe2
commit ac25d08760
2 changed files with 111 additions and 1 deletions

View File

@@ -2,6 +2,7 @@ package importers
import (
"encoding/json"
"fmt"
"path/filepath"
"strings"
@@ -10,6 +11,7 @@ import (
"github.com/mudler/LocalAI/core/schema"
"github.com/mudler/LocalAI/pkg/downloader"
"github.com/mudler/LocalAI/pkg/functions"
hfapi "github.com/mudler/LocalAI/pkg/huggingface-api"
"go.yaml.in/yaml/v2"
)
@@ -115,7 +117,48 @@ func (i *DS4Importer) Import(details Details) (gallery.ModelConfig, error) {
// filename to "ds4flash.gguf" to match ds4's own convention (its CLI
// defaults to that path), so users can run the model without extra
// config.
uri := downloader.URI(details.URI)
selectedURI := details.URI
uriFilename, _ := downloader.URI(details.URI).FilenameFromUrl()
if !strings.HasSuffix(strings.ToLower(uriFilename), ".gguf") {
if details.HuggingFace == nil {
return gallery.ModelConfig{}, fmt.Errorf("cannot select a compatible GGUF from repository %q without HuggingFace file metadata", details.URI)
}
quantizations, _ := preferencesMap["quantizations"].(string)
var compatible []hfapi.ModelFile
for _, file := range details.HuggingFace.Files {
base := strings.ToLower(filepath.Base(file.Path))
if strings.HasPrefix(base, "deepseek-v4-flash-") && strings.HasSuffix(base, ".gguf") {
compatible = append(compatible, file)
}
}
if len(compatible) == 0 {
return gallery.ModelConfig{}, fmt.Errorf("no compatible GGUF found in repository %q", details.URI)
}
selected := compatible[0].URL
if quantizations != "" {
selected = compatible[len(compatible)-1].URL
matched := false
for _, quantization := range strings.Split(quantizations, ",") {
quantization = strings.ToLower(strings.TrimSpace(quantization))
for _, file := range compatible {
base := strings.ToLower(filepath.Base(file.Path))
if quantTokenMatches(base, quantization) {
selected = file.URL
matched = true
break
}
}
if matched {
break
}
}
}
selectedURI = selected
}
uri := downloader.URI(selectedURI)
cfg.Files = append(cfg.Files, gallery.File{
Filename: "ds4flash.gguf",
URI: string(uri),

View File

@@ -5,6 +5,7 @@ import (
"strings"
. "github.com/mudler/LocalAI/core/gallery/importers"
hfapi "github.com/mudler/LocalAI/pkg/huggingface-api"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
)
@@ -65,5 +66,71 @@ var _ = Describe("DS4Importer", func() {
"ConfigFile must specify backend: ds4, got: %s", cfg.ConfigFile)
Expect(strings.Contains(cfg.ConfigFile, "use_tokenizer_template: true")).To(BeTrue())
})
It("preserves an explicit GGUF URI with a query string", func() {
uri := "https://huggingface.co/antirez/deepseek-v4-gguf/resolve/main/DeepSeek-V4-Flash-UD-IQ3_XXS.gguf?download=true"
cfg, err := importer.Import(Details{URI: uri})
Expect(err).NotTo(HaveOccurred())
Expect(cfg.Files).To(HaveLen(1))
Expect(cfg.Files[0].URI).To(Equal(uri))
})
It("selects the highest-priority requested quantization from a repository", func() {
details := Details{
URI: "huggingface://antirez/deepseek-v4-gguf",
Preferences: json.RawMessage(`{"quantizations":"UD-IQ3_XXS,IQ2_XXS"}`),
HuggingFace: &hfapi.ModelDetails{Files: []hfapi.ModelFile{
{Path: "DeepSeek-V4-Flash-IQ2_XXS.gguf", URL: "https://huggingface.co/antirez/deepseek-v4-gguf/resolve/main/DeepSeek-V4-Flash-IQ2_XXS.gguf"},
{Path: "DeepSeek-V4-Flash-UD-IQ3_XXS.gguf", URL: "https://huggingface.co/antirez/deepseek-v4-gguf/resolve/main/DeepSeek-V4-Flash-UD-IQ3_XXS.gguf"},
}},
}
cfg, err := importer.Import(details)
Expect(err).NotTo(HaveOccurred())
Expect(cfg.Files).To(HaveLen(1))
Expect(cfg.Files[0].URI).To(Equal("https://huggingface.co/antirez/deepseek-v4-gguf/resolve/main/DeepSeek-V4-Flash-UD-IQ3_XXS.gguf"))
})
It("falls back to the last compatible GGUF when requested quantizations do not match", func() {
details := Details{
URI: "huggingface://antirez/deepseek-v4-gguf",
Preferences: json.RawMessage(`{"quantizations":"F16"}`),
HuggingFace: &hfapi.ModelDetails{Files: []hfapi.ModelFile{
{Path: "DeepSeek-V4-Flash-BF16.gguf", URL: "https://huggingface.co/antirez/deepseek-v4-gguf/resolve/main/DeepSeek-V4-Flash-BF16.gguf"},
{Path: "README.md", URL: "https://huggingface.co/antirez/deepseek-v4-gguf/resolve/main/README.md"},
{Path: "DeepSeek-V4-Flash-Q8_0.gguf", URL: "https://huggingface.co/antirez/deepseek-v4-gguf/resolve/main/DeepSeek-V4-Flash-Q8_0.gguf"},
}},
}
cfg, err := importer.Import(details)
Expect(err).NotTo(HaveOccurred())
Expect(cfg.Files).To(HaveLen(1))
Expect(cfg.Files[0].URI).To(Equal("https://huggingface.co/antirez/deepseek-v4-gguf/resolve/main/DeepSeek-V4-Flash-Q8_0.gguf"))
})
It("rejects a repository URI when no HuggingFace file metadata is available", func() {
_, err := importer.Import(Details{URI: "huggingface://antirez/deepseek-v4-gguf"})
Expect(err).To(MatchError(ContainSubstring("compatible GGUF")))
})
It("rejects repository metadata without a compatible GGUF", func() {
details := Details{
URI: "huggingface://antirez/deepseek-v4-gguf",
HuggingFace: &hfapi.ModelDetails{Files: []hfapi.ModelFile{
{Path: "README.md", URL: "https://huggingface.co/antirez/deepseek-v4-gguf/resolve/main/README.md"},
{Path: "unrelated.gguf", URL: "https://huggingface.co/antirez/deepseek-v4-gguf/resolve/main/unrelated.gguf"},
}},
}
cfg, err := importer.Import(details)
Expect(err).To(MatchError(ContainSubstring("compatible GGUF")))
Expect(cfg.Files).To(BeEmpty())
})
})
})