mirror of
https://github.com/mudler/LocalAI.git
synced 2026-08-04 04:12:22 -04:00
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:
@@ -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),
|
||||
|
||||
@@ -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())
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user