From b52bfaf1b360a489fd1bf57e759f920100c654b4 Mon Sep 17 00:00:00 2001 From: Ettore Di Giacinto Date: Thu, 14 Aug 2025 13:01:56 +0200 Subject: [PATCH] fix: do not show invalid backends (#6058) Signed-off-by: Ettore Di Giacinto --- core/cli/worker/worker_llamacpp.go | 9 ++- core/gallery/backends.go | 88 +++++++++++++++++++------- core/gallery/backends_test.go | 66 +++++++++++++------ core/gallery/gallery.go | 3 +- core/http/endpoints/localai/backend.go | 2 +- 5 files changed, 120 insertions(+), 48 deletions(-) diff --git a/core/cli/worker/worker_llamacpp.go b/core/cli/worker/worker_llamacpp.go index c742a4b08..95ca9c6bd 100644 --- a/core/cli/worker/worker_llamacpp.go +++ b/core/cli/worker/worker_llamacpp.go @@ -30,12 +30,11 @@ func findLLamaCPPBackend(backendSystemPath string) (string, error) { log.Debug().Msgf("System backends: %v", backends) backendPath := "" - for b, path := range backends { - if b == "llama-cpp" { - backendPath = filepath.Dir(path) - break - } + backend, ok := backends.Get("llama-cpp") + if !ok { + return "", errors.New("llama-cpp backend not found, install it first") } + backendPath = filepath.Dir(backend.RunFile) if backendPath == "" { return "", errors.New("llama-cpp backend not found, install it first") diff --git a/core/gallery/backends.go b/core/gallery/backends.go index cd635f8b9..2d7c98a6d 100644 --- a/core/gallery/backends.go +++ b/core/gallery/backends.go @@ -66,7 +66,7 @@ func InstallBackendFromGallery(galleries []config.Gallery, systemState *system.S if err != nil { return err } - if _, ok := backends[name]; ok { + if backends.Exists(name) { return nil } } @@ -239,55 +239,99 @@ func DeleteBackendFromSystem(basePath string, name string) error { return os.RemoveAll(backendDirectory) } -func ListSystemBackends(basePath string) (map[string]string, error) { - backends, err := os.ReadDir(basePath) +type SystemBackend struct { + Name string + RunFile string + IsMeta bool + Metadata *BackendMetadata +} + +type SystemBackends map[string]SystemBackend + +func (b SystemBackends) Exists(name string) bool { + _, ok := b[name] + return ok +} + +func (b SystemBackends) Get(name string) (SystemBackend, bool) { + backend, ok := b[name] + return backend, ok +} + +func (b SystemBackends) GetAll() []SystemBackend { + backends := make([]SystemBackend, 0) + for _, backend := range b { + backends = append(backends, backend) + } + return backends +} + +func ListSystemBackends(basePath string) (SystemBackends, error) { + potentialBackends, err := os.ReadDir(basePath) if err != nil { return nil, err } - backendsNames := make(map[string]string) + backends := make(SystemBackends) - for _, backend := range backends { - if backend.IsDir() { - runFile := filepath.Join(basePath, backend.Name(), runFile) + for _, potentialBackend := range potentialBackends { + if potentialBackend.IsDir() { + potentialBackendRunFile := filepath.Join(basePath, potentialBackend.Name(), runFile) var metadata *BackendMetadata // If metadata file does not exist, we just use the directory name // and we do not fill the other metadata (such as potential backend Aliases) - metadataFilePath := filepath.Join(basePath, backend.Name(), metadataFile) + metadataFilePath := filepath.Join(basePath, potentialBackend.Name(), metadataFile) if _, err := os.Stat(metadataFilePath); os.IsNotExist(err) { metadata = &BackendMetadata{ - Name: backend.Name(), + Name: potentialBackend.Name(), } } else { // Check for alias in metadata - metadata, err = readBackendMetadata(filepath.Join(basePath, backend.Name())) + metadata, err = readBackendMetadata(filepath.Join(basePath, potentialBackend.Name())) if err != nil { return nil, err } } + if !backends.Exists(potentialBackend.Name()) { + // We don't want to override aliases if already set, and if we are meta backend + if _, err := os.Stat(potentialBackendRunFile); err == nil { + backends[potentialBackend.Name()] = SystemBackend{ + Name: potentialBackend.Name(), + RunFile: potentialBackendRunFile, + IsMeta: false, + Metadata: metadata, + } + } + } + if metadata == nil { continue } - if _, exists := backendsNames[backend.Name()]; !exists { - // We don't want to override aliases if already set, and if we are meta backend - if _, err := os.Stat(runFile); err == nil { - backendsNames[backend.Name()] = runFile - } else { - backendsNames[backend.Name()] = "" + if metadata.Alias != "" { + backends[metadata.Alias] = SystemBackend{ + Name: metadata.Alias, + RunFile: potentialBackendRunFile, + IsMeta: false, + Metadata: metadata, } } - if metadata.Alias != "" { - backendsNames[metadata.Alias] = runFile + if metadata.MetaBackendFor != "" { + backends[metadata.Name] = SystemBackend{ + Name: metadata.Name, + RunFile: filepath.Join(basePath, metadata.MetaBackendFor, runFile), + IsMeta: true, + Metadata: metadata, + } } } } - return backendsNames, nil + return backends, nil } func RegisterBackends(basePath string, modelLoader *model.ModelLoader) error { @@ -296,9 +340,9 @@ func RegisterBackends(basePath string, modelLoader *model.ModelLoader) error { return err } - for name, runFile := range backends { - log.Debug().Str("name", name).Str("runFile", runFile).Msg("Registering backend") - modelLoader.SetExternalBackend(name, runFile) + for _, backend := range backends { + log.Debug().Str("name", backend.Name).Str("runFile", backend.RunFile).Msg("Registering backend") + modelLoader.SetExternalBackend(backend.Name, backend.RunFile) } return nil diff --git a/core/gallery/backends_test.go b/core/gallery/backends_test.go index 460677706..848840191 100644 --- a/core/gallery/backends_test.go +++ b/core/gallery/backends_test.go @@ -208,13 +208,16 @@ var _ = Describe("Gallery Backends", func() { metaBackendPath := filepath.Join(tempDir, "meta-backend") Expect(metaBackendPath).To(BeADirectory()) + metaBackendPath = filepath.Join(tempDir, "meta-backend", "metadata.json") + Expect(metaBackendPath).To(BeARegularFile()) + concreteBackendPath := filepath.Join(tempDir, "nvidia-backend") Expect(concreteBackendPath).To(BeADirectory()) allBackends, err := ListSystemBackends(tempDir) Expect(err).NotTo(HaveOccurred()) - Expect(allBackends).To(HaveKey("meta-backend")) - Expect(allBackends).To(HaveKey("nvidia-backend")) + Expect(allBackends.Exists("meta-backend")).To(BeTrue()) + Expect(allBackends.Exists("nvidia-backend")).To(BeTrue()) // Delete meta backend by name err = DeleteBackendFromSystem(tempDir, "meta-backend") @@ -284,9 +287,14 @@ var _ = Describe("Gallery Backends", func() { allBackends, err := ListSystemBackends(tempDir) Expect(err).NotTo(HaveOccurred()) - Expect(allBackends).To(HaveKey("meta-backend")) - Expect(allBackends).To(HaveKey("nvidia-backend")) - Expect(allBackends["meta-backend"]).To(BeEmpty()) + Expect(allBackends.Exists("meta-backend")).To(BeTrue()) + Expect(allBackends.Exists("nvidia-backend")).To(BeTrue()) + + backend, ok := allBackends.Get("meta-backend") + Expect(ok).To(BeTrue()) + Expect(backend.Metadata.MetaBackendFor).To(Equal("nvidia-backend")) + Expect(backend.RunFile).To(Equal(filepath.Join(tempDir, "nvidia-backend", "run.sh"))) + Expect(backend.IsMeta).To(BeTrue()) // Delete meta backend by name err = DeleteBackendFromSystem(tempDir, "meta-backend") @@ -356,9 +364,11 @@ var _ = Describe("Gallery Backends", func() { allBackends, err := ListSystemBackends(tempDir) Expect(err).NotTo(HaveOccurred()) - Expect(allBackends).To(HaveKey("meta-backend")) - Expect(allBackends).To(HaveKey("nvidia-backend")) - Expect(allBackends["meta-backend"]).To(Equal(filepath.Join(tempDir, "nvidia-backend", "run.sh"))) + Expect(allBackends.Exists("meta-backend")).To(BeTrue()) + Expect(allBackends.Exists("nvidia-backend")).To(BeTrue()) + backend, ok := allBackends.Get("meta-backend") + Expect(ok).To(BeTrue()) + Expect(backend.RunFile).To(Equal(filepath.Join(tempDir, "nvidia-backend", "run.sh"))) // Delete meta backend by name err = DeleteBackendFromSystem(tempDir, "meta-backend") @@ -402,13 +412,21 @@ var _ = Describe("Gallery Backends", func() { Expect(err).NotTo(HaveOccurred()) // Should include both the meta backend name and concrete backend name - Expect(backends).To(HaveKey("meta-backend")) - Expect(backends).To(HaveKey("concrete-backend")) + Expect(backends.Exists("meta-backend")).To(BeTrue()) + Expect(backends.Exists("concrete-backend")).To(BeTrue()) + + // meta-backend should point to concrete-backend + Expect(backends.Exists("meta-backend")).To(BeTrue()) + backend, ok := backends.Get("meta-backend") + Expect(ok).To(BeTrue()) + Expect(backend.Metadata.MetaBackendFor).To(Equal("concrete-backend")) + Expect(backend.RunFile).To(Equal(filepath.Join(tempDir, "concrete-backend", "run.sh"))) + Expect(backend.IsMeta).To(BeTrue()) - // meta-backend should be empty - Expect(backends["meta-backend"]).To(BeEmpty()) // concrete-backend should point to its own run.sh - Expect(backends["concrete-backend"]).To(Equal(filepath.Join(tempDir, "concrete-backend", "run.sh"))) + backend, ok = backends.Get("concrete-backend") + Expect(ok).To(BeTrue()) + Expect(backend.RunFile).To(Equal(filepath.Join(tempDir, "concrete-backend", "run.sh"))) }) }) @@ -457,8 +475,14 @@ var _ = Describe("Gallery Backends", func() { // Check that the alias was recognized backends, err := ListSystemBackends(tempDir) Expect(err).ToNot(HaveOccurred()) - Expect(backends).To(HaveKeyWithValue("test-alias", filepath.Join(tempDir, "test-backend", "run.sh"))) - Expect(backends).To(HaveKeyWithValue("test-backend", filepath.Join(tempDir, "test-backend", "run.sh"))) + Expect(backends.Exists("test-alias")).To(BeTrue()) + Expect(backends.Exists("test-backend")).To(BeTrue()) + b, ok := backends.Get("test-alias") + Expect(ok).To(BeTrue()) + Expect(b.RunFile).To(Equal(filepath.Join(tempDir, "test-backend", "run.sh"))) + b, ok = backends.Get("test-backend") + Expect(ok).To(BeTrue()) + Expect(b.RunFile).To(Equal(filepath.Join(tempDir, "test-backend", "run.sh"))) }) }) @@ -497,10 +521,13 @@ var _ = Describe("Gallery Backends", func() { backends, err := ListSystemBackends(tempDir) Expect(err).NotTo(HaveOccurred()) - Expect(backends).To(HaveLen(len(backendNames))) + Expect(backends.GetAll()).To(HaveLen(len(backendNames))) for _, name := range backendNames { - Expect(backends).To(HaveKeyWithValue(name, filepath.Join(tempDir, name, "run.sh"))) + Expect(backends.Exists(name)).To(BeTrue()) + backend, ok := backends.Get(name) + Expect(ok).To(BeTrue()) + Expect(backend.RunFile).To(Equal(filepath.Join(tempDir, name, "run.sh"))) } }) @@ -528,7 +555,10 @@ var _ = Describe("Gallery Backends", func() { backends, err := ListSystemBackends(tempDir) Expect(err).NotTo(HaveOccurred()) - Expect(backends).To(HaveKeyWithValue(alias, filepath.Join(tempDir, backendName, "run.sh"))) + Expect(backends.Exists(alias)).To(BeTrue()) + backend, ok := backends.Get(alias) + Expect(ok).To(BeTrue()) + Expect(backend.RunFile).To(Equal(filepath.Join(tempDir, backendName, "run.sh"))) }) It("should return error when base path doesn't exist", func() { diff --git a/core/gallery/gallery.go b/core/gallery/gallery.go index 2e007a53a..a46920f1a 100644 --- a/core/gallery/gallery.go +++ b/core/gallery/gallery.go @@ -147,8 +147,7 @@ func AvailableBackends(galleries []config.Gallery, basePath string) (GalleryElem if err != nil { return false } - _, exists := backends[backend.GetName()] - return exists + return backends.Exists(backend.GetName()) }) if err != nil { return nil, err diff --git a/core/http/endpoints/localai/backend.go b/core/http/endpoints/localai/backend.go index 0382b5741..2a8df50d3 100644 --- a/core/http/endpoints/localai/backend.go +++ b/core/http/endpoints/localai/backend.go @@ -117,7 +117,7 @@ func (mgs *BackendEndpointService) ListBackendsEndpoint() func(c *fiber.Ctx) err if err != nil { return err } - return c.JSON(backends) + return c.JSON(backends.GetAll()) } }