mirror of
https://github.com/mudler/LocalAI.git
synced 2025-06-17 08:15:00 +00:00

* feat: Add backend gallery This PR add support to manage backends as similar to models. There is now available a backend gallery which can be used to install and remove extra backends. The backend gallery can be configured similarly as a model gallery, and API calls allows to install and remove new backends in runtime, and as well during the startup phase of LocalAI. Signed-off-by: Ettore Di Giacinto <mudler@localai.io> * Add backends docs Signed-off-by: Ettore Di Giacinto <mudler@localai.io> * wip: Backend Dockerfile for python backends Signed-off-by: Ettore Di Giacinto <mudler@localai.io> * feat: drop extras images, build python backends separately Signed-off-by: Ettore Di Giacinto <mudler@localai.io> * fixup on all backends Signed-off-by: Ettore Di Giacinto <mudler@localai.io> * test CI Signed-off-by: Ettore Di Giacinto <mudler@localai.io> * Tweaks Signed-off-by: Ettore Di Giacinto <mudler@localai.io> * Drop old backends leftovers Signed-off-by: Ettore Di Giacinto <mudler@localai.io> * Fixup CI Signed-off-by: Ettore Di Giacinto <mudler@localai.io> * Move dockerfile upper Signed-off-by: Ettore Di Giacinto <mudler@localai.io> * Fix proto Signed-off-by: Ettore Di Giacinto <mudler@localai.io> * Feature dropped for consistency - we prefer model galleries Signed-off-by: Ettore Di Giacinto <mudler@localai.io> * Add missing packages in the build image Signed-off-by: Ettore Di Giacinto <mudler@localai.io> * exllama is ponly available on cublas Signed-off-by: Ettore Di Giacinto <mudler@localai.io> * pin torch on chatterbox Signed-off-by: Ettore Di Giacinto <mudler@localai.io> * Fixups to index Signed-off-by: Ettore Di Giacinto <mudler@localai.io> * CI Signed-off-by: Ettore Di Giacinto <mudler@localai.io> * Debug CI * Install accellerators deps Signed-off-by: Ettore Di Giacinto <mudler@localai.io> * Add target arch * Add cuda minor version Signed-off-by: Ettore Di Giacinto <mudler@localai.io> * Use self-hosted runners Signed-off-by: Ettore Di Giacinto <mudler@localai.io> * ci: use quay for test images Signed-off-by: Ettore Di Giacinto <mudler@localai.io> * fixups for vllm and chatterbox Signed-off-by: Ettore Di Giacinto <mudler@localai.io> * Small fixups on CI Signed-off-by: Ettore Di Giacinto <mudler@localai.io> * chatterbox is only available for nvidia Signed-off-by: Ettore Di Giacinto <mudler@localai.io> * Simplify CI builds Signed-off-by: Ettore Di Giacinto <mudler@localai.io> * Adapt test, use qwen3 Signed-off-by: Ettore Di Giacinto <mudler@localai.io> * chore(model gallery): add jina-reranker-v1-tiny-en-gguf Signed-off-by: Ettore Di Giacinto <mudler@localai.io> * fix(gguf-parser): recover from potential panics that can happen while reading ggufs with gguf-parser Signed-off-by: Ettore Di Giacinto <mudler@localai.io> * Use reranker from llama.cpp in AIO images Signed-off-by: Ettore Di Giacinto <mudler@localai.io> * Limit concurrent jobs Signed-off-by: Ettore Di Giacinto <mudler@localai.io> --------- Signed-off-by: Ettore Di Giacinto <mudler@localai.io> Signed-off-by: Ettore Di Giacinto <mudler@users.noreply.github.com>
165 lines
5.5 KiB
Go
165 lines
5.5 KiB
Go
package gallery_test
|
|
|
|
import (
|
|
"errors"
|
|
"os"
|
|
"path/filepath"
|
|
|
|
"github.com/mudler/LocalAI/core/config"
|
|
. "github.com/mudler/LocalAI/core/gallery"
|
|
. "github.com/onsi/ginkgo/v2"
|
|
. "github.com/onsi/gomega"
|
|
"gopkg.in/yaml.v3"
|
|
)
|
|
|
|
const bertEmbeddingsURL = `https://gist.githubusercontent.com/mudler/0a080b166b87640e8644b09c2aee6e3b/raw/f0e8c26bb72edc16d9fbafbfd6638072126ff225/bert-embeddings-gallery.yaml`
|
|
|
|
var _ = Describe("Model test", func() {
|
|
|
|
BeforeEach(func() {
|
|
if os.Getenv("FIXTURES") == "" {
|
|
Skip("FIXTURES env var not set, skipping model tests")
|
|
}
|
|
})
|
|
|
|
Context("Downloading", func() {
|
|
It("applies model correctly", func() {
|
|
tempdir, err := os.MkdirTemp("", "test")
|
|
Expect(err).ToNot(HaveOccurred())
|
|
defer os.RemoveAll(tempdir)
|
|
c, err := ReadConfigFile[ModelConfig](filepath.Join(os.Getenv("FIXTURES"), "gallery_simple.yaml"))
|
|
Expect(err).ToNot(HaveOccurred())
|
|
err = InstallModel(tempdir, "", c, map[string]interface{}{}, func(string, string, string, float64) {}, true)
|
|
Expect(err).ToNot(HaveOccurred())
|
|
|
|
for _, f := range []string{"cerebras", "cerebras-completion.tmpl", "cerebras-chat.tmpl", "cerebras.yaml"} {
|
|
_, err = os.Stat(filepath.Join(tempdir, f))
|
|
Expect(err).ToNot(HaveOccurred())
|
|
}
|
|
|
|
content := map[string]interface{}{}
|
|
|
|
dat, err := os.ReadFile(filepath.Join(tempdir, "cerebras.yaml"))
|
|
Expect(err).ToNot(HaveOccurred())
|
|
|
|
err = yaml.Unmarshal(dat, content)
|
|
Expect(err).ToNot(HaveOccurred())
|
|
|
|
Expect(content["context_size"]).To(Equal(1024))
|
|
})
|
|
|
|
It("applies model from gallery correctly", func() {
|
|
tempdir, err := os.MkdirTemp("", "test")
|
|
Expect(err).ToNot(HaveOccurred())
|
|
defer os.RemoveAll(tempdir)
|
|
|
|
gallery := []GalleryModel{{
|
|
Metadata: Metadata{
|
|
Name: "bert",
|
|
URL: bertEmbeddingsURL,
|
|
},
|
|
}}
|
|
out, err := yaml.Marshal(gallery)
|
|
Expect(err).ToNot(HaveOccurred())
|
|
galleryFilePath := filepath.Join(tempdir, "gallery_simple.yaml")
|
|
err = os.WriteFile(galleryFilePath, out, 0600)
|
|
Expect(err).ToNot(HaveOccurred())
|
|
Expect(filepath.IsAbs(galleryFilePath)).To(BeTrue(), galleryFilePath)
|
|
galleries := []config.Gallery{
|
|
{
|
|
Name: "test",
|
|
URL: "file://" + galleryFilePath,
|
|
},
|
|
}
|
|
|
|
models, err := AvailableGalleryModels(galleries, tempdir)
|
|
Expect(err).ToNot(HaveOccurred())
|
|
Expect(len(models)).To(Equal(1))
|
|
Expect(models[0].Name).To(Equal("bert"))
|
|
Expect(models[0].URL).To(Equal(bertEmbeddingsURL))
|
|
Expect(models[0].Installed).To(BeFalse())
|
|
|
|
err = InstallModelFromGallery(galleries, "test@bert", tempdir, GalleryModel{}, func(s1, s2, s3 string, f float64) {}, true)
|
|
Expect(err).ToNot(HaveOccurred())
|
|
|
|
dat, err := os.ReadFile(filepath.Join(tempdir, "bert.yaml"))
|
|
Expect(err).ToNot(HaveOccurred())
|
|
|
|
content := map[string]interface{}{}
|
|
err = yaml.Unmarshal(dat, &content)
|
|
Expect(err).ToNot(HaveOccurred())
|
|
Expect(content["usage"]).To(ContainSubstring("You can test this model with curl like this"))
|
|
|
|
models, err = AvailableGalleryModels(galleries, tempdir)
|
|
Expect(err).ToNot(HaveOccurred())
|
|
Expect(len(models)).To(Equal(1))
|
|
Expect(models[0].Installed).To(BeTrue())
|
|
|
|
// delete
|
|
err = DeleteModelFromSystem(tempdir, "bert", []string{})
|
|
Expect(err).ToNot(HaveOccurred())
|
|
|
|
models, err = AvailableGalleryModels(galleries, tempdir)
|
|
Expect(err).ToNot(HaveOccurred())
|
|
Expect(len(models)).To(Equal(1))
|
|
Expect(models[0].Installed).To(BeFalse())
|
|
|
|
_, err = os.Stat(filepath.Join(tempdir, "bert.yaml"))
|
|
Expect(err).To(HaveOccurred())
|
|
Expect(errors.Is(err, os.ErrNotExist)).To(BeTrue())
|
|
})
|
|
|
|
It("renames model correctly", func() {
|
|
tempdir, err := os.MkdirTemp("", "test")
|
|
Expect(err).ToNot(HaveOccurred())
|
|
defer os.RemoveAll(tempdir)
|
|
c, err := ReadConfigFile[ModelConfig](filepath.Join(os.Getenv("FIXTURES"), "gallery_simple.yaml"))
|
|
Expect(err).ToNot(HaveOccurred())
|
|
|
|
err = InstallModel(tempdir, "foo", c, map[string]interface{}{}, func(string, string, string, float64) {}, true)
|
|
Expect(err).ToNot(HaveOccurred())
|
|
|
|
for _, f := range []string{"cerebras", "cerebras-completion.tmpl", "cerebras-chat.tmpl", "foo.yaml"} {
|
|
_, err = os.Stat(filepath.Join(tempdir, f))
|
|
Expect(err).ToNot(HaveOccurred())
|
|
}
|
|
})
|
|
|
|
It("overrides parameters", func() {
|
|
tempdir, err := os.MkdirTemp("", "test")
|
|
Expect(err).ToNot(HaveOccurred())
|
|
defer os.RemoveAll(tempdir)
|
|
c, err := ReadConfigFile[ModelConfig](filepath.Join(os.Getenv("FIXTURES"), "gallery_simple.yaml"))
|
|
Expect(err).ToNot(HaveOccurred())
|
|
|
|
err = InstallModel(tempdir, "foo", c, map[string]interface{}{"backend": "foo"}, func(string, string, string, float64) {}, true)
|
|
Expect(err).ToNot(HaveOccurred())
|
|
|
|
for _, f := range []string{"cerebras", "cerebras-completion.tmpl", "cerebras-chat.tmpl", "foo.yaml"} {
|
|
_, err = os.Stat(filepath.Join(tempdir, f))
|
|
Expect(err).ToNot(HaveOccurred())
|
|
}
|
|
|
|
content := map[string]interface{}{}
|
|
|
|
dat, err := os.ReadFile(filepath.Join(tempdir, "foo.yaml"))
|
|
Expect(err).ToNot(HaveOccurred())
|
|
|
|
err = yaml.Unmarshal(dat, content)
|
|
Expect(err).ToNot(HaveOccurred())
|
|
|
|
Expect(content["backend"]).To(Equal("foo"))
|
|
})
|
|
|
|
It("catches path traversals", func() {
|
|
tempdir, err := os.MkdirTemp("", "test")
|
|
Expect(err).ToNot(HaveOccurred())
|
|
defer os.RemoveAll(tempdir)
|
|
c, err := ReadConfigFile[ModelConfig](filepath.Join(os.Getenv("FIXTURES"), "gallery_simple.yaml"))
|
|
Expect(err).ToNot(HaveOccurred())
|
|
|
|
err = InstallModel(tempdir, "../../../foo", c, map[string]interface{}{}, func(string, string, string, float64) {}, true)
|
|
Expect(err).To(HaveOccurred())
|
|
})
|
|
})
|
|
})
|