Skip to content
Merged
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
8 changes: 8 additions & 0 deletions core/services/galleryop/list_models.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,14 @@ const (

func ListModels(bcl *config.ModelConfigLoader, ml *model.ModelLoader, filter config.ModelConfigFilterFn, looseFilePolicy LooseFilePolicy) ([]string, error) {

// Callers (e.g. the Ollama /api/tags handler) pass nil to mean "no
// filtering". Without this guard the loose-file loop below dereferences
// filter and panics, which Echo surfaces to clients as a dropped
// connection (see issue #9817).
if filter == nil {
filter = config.NoFilterFn
}

skipMap := map[string]struct{}{}

dataModels := []string{}
Expand Down
64 changes: 64 additions & 0 deletions core/services/galleryop/list_models_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package galleryop_test

import (
"os"
"path/filepath"

"github.com/mudler/LocalAI/core/config"
"github.com/mudler/LocalAI/core/services/galleryop"
"github.com/mudler/LocalAI/pkg/model"
"github.com/mudler/LocalAI/pkg/system"

. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
)

// Regression test for issue #9817: the Ollama /api/tags handler calls
// ListModels with a nil filter, which used to panic as soon as a loose file
// existed under ModelsPath. The panic surfaced to Ollama clients (e.g. Home
// Assistant) as "Server disconnected without sending a response".
var _ = Describe("ListModels", func() {
var (
tempDir string
bcl *config.ModelConfigLoader
ml *model.ModelLoader
systemState *system.SystemState
)

BeforeEach(func() {
var err error
tempDir, err = os.MkdirTemp("", "list-models-test-*")
Expect(err).NotTo(HaveOccurred())

systemState, err = system.GetSystemState(system.WithModelPath(tempDir))
Expect(err).NotTo(HaveOccurred())
ml = model.NewModelLoader(systemState)
bcl = config.NewModelConfigLoader(tempDir)
})

AfterEach(func() {
os.RemoveAll(tempDir)

Check failure on line 40 in core/services/galleryop/list_models_test.go

View workflow job for this annotation

GitHub Actions / golangci-lint

Error return value of `os.RemoveAll` is not checked (errcheck)
})

It("does not panic with a nil filter when loose files exist", func() {
// ListFilesInModelPath skips well-known weight-file extensions
// (.gguf, .bin, ...) so use an extension-less file to ensure the
// filter path is exercised.
Expect(os.WriteFile(filepath.Join(tempDir, "loose-model"), []byte("x"), 0o644)).To(Succeed())

var names []string
var err error
Expect(func() {
names, err = galleryop.ListModels(bcl, ml, nil, galleryop.SKIP_IF_CONFIGURED)
}).ToNot(Panic())
Expect(err).ToNot(HaveOccurred())
Expect(names).To(ContainElement("loose-model"))
})

It("does not panic with a nil filter when ModelsPath is empty", func() {
Expect(func() {
_, err := galleryop.ListModels(bcl, ml, nil, galleryop.SKIP_IF_CONFIGURED)
Expect(err).ToNot(HaveOccurred())
}).ToNot(Panic())
})
})
Loading