feat(backend gallery): add meta packages

So we can have meta packages such as "vllm" that automatically installs
the corresponding package depending on the GPU that is being currently
detected in the system.

Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
This commit is contained in:
Ettore Di Giacinto 2025-06-20 19:16:53 +02:00
parent be3ff482d0
commit 9a34fc8c66
4 changed files with 346 additions and 13 deletions

View file

@ -6,12 +6,31 @@ import (
"path/filepath"
"github.com/mudler/LocalAI/core/config"
"github.com/mudler/LocalAI/core/system"
"github.com/mudler/LocalAI/pkg/model"
"github.com/mudler/LocalAI/pkg/oci"
"github.com/rs/zerolog/log"
)
const (
aliasFile = "alias"
metaFile = "meta"
runFile = "run.sh"
)
func findBestBackendFromMeta(backend *GalleryBackend, systemState *system.SystemState, backends GalleryElements[*GalleryBackend]) *GalleryBackend {
realBackend := backend.CapabilitiesMap[systemState.GPUVendor]
if realBackend == "" {
return nil
}
return backends.FindByName(realBackend)
}
// Installs a model from the gallery
func InstallBackendFromGallery(galleries []config.Gallery, name string, basePath string, downloadStatus func(string, string, string, float64)) error {
func InstallBackendFromGallery(galleries []config.Gallery, systemState *system.SystemState, name string, basePath string, downloadStatus func(string, string, string, float64)) error {
log.Debug().Interface("galleries", galleries).Str("name", name).Msg("Installing backend from gallery")
backends, err := AvailableBackends(galleries, basePath)
if err != nil {
return err
@ -19,7 +38,38 @@ func InstallBackendFromGallery(galleries []config.Gallery, name string, basePath
backend := FindGalleryElement(backends, name, basePath)
if backend == nil {
return fmt.Errorf("no model found with name %q", name)
return fmt.Errorf("no backend found with name %q", name)
}
if backend.IsMeta() {
log.Debug().Interface("systemState", systemState).Str("name", name).Msg("Backend is a meta backend")
// Then, let's try to find the best backend based on the capabilities map
bestBackend := findBestBackendFromMeta(backend, systemState, backends)
if bestBackend == nil {
return fmt.Errorf("no backend found with capabilities %q", backend.CapabilitiesMap)
}
log.Debug().Str("name", name).Str("bestBackend", bestBackend.Name).Msg("Installing backend from meta backend")
// Then, let's install the best backend
if err := InstallBackend(basePath, bestBackend, downloadStatus); err != nil {
return err
}
// we need now to create a path for the meta backend, with the alias to the installed ones so it can be used to remove it
metaBackendPath := filepath.Join(basePath, name)
if err := os.MkdirAll(metaBackendPath, 0750); err != nil {
return fmt.Errorf("failed to create meta backend path %q: %v", metaBackendPath, err)
}
// Then, let's create an meta file to point to the best backend
metaFile := filepath.Join(metaBackendPath, metaFile)
if err := os.WriteFile(metaFile, []byte(bestBackend.Name), 0644); err != nil {
return fmt.Errorf("failed to write meta file %q: %v", metaFile, err)
}
return nil
}
return InstallBackend(basePath, backend, downloadStatus)
@ -32,6 +82,10 @@ func InstallBackend(basePath string, config *GalleryBackend, downloadStatus func
return fmt.Errorf("failed to create base path: %v", err)
}
if config.IsMeta() {
return fmt.Errorf("meta backends cannot be installed directly")
}
name := config.Name
img, err := oci.GetImage(config.URI, "", nil, nil)
@ -50,7 +104,7 @@ func InstallBackend(basePath string, config *GalleryBackend, downloadStatus func
if config.Alias != "" {
// Write an alias file inside
aliasFile := filepath.Join(backendPath, "alias")
aliasFile := filepath.Join(backendPath, aliasFile)
if err := os.WriteFile(aliasFile, []byte(config.Alias), 0644); err != nil {
return fmt.Errorf("failed to write alias file %q: %v", aliasFile, err)
}
@ -60,9 +114,52 @@ func InstallBackend(basePath string, config *GalleryBackend, downloadStatus func
}
func DeleteBackendFromSystem(basePath string, name string) error {
backendFile := filepath.Join(basePath, name)
backendDirectory := filepath.Join(basePath, name)
return os.RemoveAll(backendFile)
// check if the backend dir exists
if _, err := os.Stat(backendDirectory); os.IsNotExist(err) {
// if doesn't exist, it might be an alias, so we need to check if we have a matching alias in
// all the backends in the basePath
backends, err := os.ReadDir(basePath)
if err != nil {
return err
}
for _, backend := range backends {
if backend.IsDir() {
aliasFile := filepath.Join(basePath, backend.Name(), aliasFile)
alias, err := os.ReadFile(aliasFile)
if err != nil {
return err
}
if string(alias) == name {
backendDirectory = filepath.Join(basePath, backend.Name())
break
}
}
}
if backendDirectory == "" {
return fmt.Errorf("no backend found with name %q", name)
}
}
// If it's a meta, delete also associated backend
metaFile := filepath.Join(backendDirectory, metaFile)
if _, err := os.Stat(metaFile); err == nil {
meta, err := os.ReadFile(metaFile)
if err != nil {
return err
}
metaBackendDirectory := filepath.Join(basePath, string(meta))
log.Debug().Str("backendDirectory", metaBackendDirectory).Msg("Deleting meta backend")
if _, err := os.Stat(metaBackendDirectory); os.IsNotExist(err) {
return fmt.Errorf("meta backend %q not found", string(meta))
}
os.RemoveAll(metaBackendDirectory)
}
return os.RemoveAll(backendDirectory)
}
func ListSystemBackends(basePath string) (map[string]string, error) {
@ -75,10 +172,10 @@ func ListSystemBackends(basePath string) (map[string]string, error) {
for _, backend := range backends {
if backend.IsDir() {
runFile := filepath.Join(basePath, backend.Name(), "run.sh")
runFile := filepath.Join(basePath, backend.Name(), runFile)
backendsNames[backend.Name()] = runFile
aliasFile := filepath.Join(basePath, backend.Name(), "alias")
aliasFile := filepath.Join(basePath, backend.Name(), aliasFile)
if _, err := os.Stat(aliasFile); err == nil {
// read the alias file, and use it as key
alias, err := os.ReadFile(aliasFile)