Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 44 additions & 1 deletion core/gallery/importers/ds4.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package importers

import (
"encoding/json"
"fmt"
"path/filepath"
"strings"

Expand All @@ -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"
)

Expand Down Expand Up @@ -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),
Expand Down
67 changes: 67 additions & 0 deletions core/gallery/importers/ds4_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)
Expand Down Expand Up @@ -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())
})
})
})
Loading