1
+ // Package gallery provides installation and registration utilities for LocalAI backends,
2
+ // including meta-backend resolution based on system capabilities.
1
3
package gallery
2
4
3
5
import (
4
6
"encoding/json"
5
7
"fmt"
6
8
"os"
7
9
"path/filepath"
10
+ "strings"
8
11
"time"
9
12
10
13
"github.com/mudler/LocalAI/core/config"
@@ -20,6 +23,12 @@ const (
20
23
runFile = "run.sh"
21
24
)
22
25
26
+ // backendCandidate represents an installed concrete backend option for a given alias
27
+ type backendCandidate struct {
28
+ name string
29
+ runFile string
30
+ }
31
+
23
32
// readBackendMetadata reads the metadata JSON file for a backend
24
33
func readBackendMetadata (backendPath string ) (* BackendMetadata , error ) {
25
34
metadataPath := filepath .Join (backendPath , metadataFile )
@@ -58,7 +67,7 @@ func writeBackendMetadata(backendPath string, metadata *BackendMetadata) error {
58
67
return nil
59
68
}
60
69
61
- // Installs a model from the gallery
70
+ // InstallBackendFromGallery installs a backend from the gallery.
62
71
func InstallBackendFromGallery (galleries []config.Gallery , systemState * system.SystemState , modelLoader * model.ModelLoader , name string , downloadStatus func (string , string , string , float64 ), force bool ) error {
63
72
if ! force {
64
73
// check if we already have the backend installed
@@ -282,23 +291,18 @@ func (b SystemBackends) GetAll() []SystemBackend {
282
291
}
283
292
284
293
func ListSystemBackends (systemState * system.SystemState ) (SystemBackends , error ) {
285
- potentialBackends , err := os .ReadDir (systemState .Backend .BackendsPath )
286
- if err != nil {
287
- return nil , err
288
- }
289
-
294
+ // Gather backends from system and user paths, then resolve alias conflicts by capability.
290
295
backends := make (SystemBackends )
291
296
292
- systemBackends , err := os .ReadDir (systemState .Backend .BackendsSystemPath )
293
- if err == nil {
294
- // system backends are special, they are provided by the system and not managed by LocalAI
297
+ // System-provided backends
298
+ if systemBackends , err := os .ReadDir (systemState .Backend .BackendsSystemPath ); err == nil {
295
299
for _ , systemBackend := range systemBackends {
296
300
if systemBackend .IsDir () {
297
- systemBackendRunFile := filepath .Join (systemState .Backend .BackendsSystemPath , systemBackend .Name (), runFile )
298
- if _ , err := os .Stat (systemBackendRunFile ); err == nil {
301
+ run := filepath .Join (systemState .Backend .BackendsSystemPath , systemBackend .Name (), runFile )
302
+ if _ , err := os .Stat (run ); err == nil {
299
303
backends [systemBackend .Name ()] = SystemBackend {
300
304
Name : systemBackend .Name (),
301
- RunFile : filepath . Join ( systemState . Backend . BackendsSystemPath , systemBackend . Name (), runFile ) ,
305
+ RunFile : run ,
302
306
IsMeta : false ,
303
307
IsSystem : true ,
304
308
Metadata : nil ,
@@ -307,64 +311,104 @@ func ListSystemBackends(systemState *system.SystemState) (SystemBackends, error)
307
311
}
308
312
}
309
313
} else {
310
- log .Warn ().Err (err ).Msg ("Failed to read system backends, but that's ok, we will just use the backends managed by LocalAI " )
314
+ log .Warn ().Err (err ).Msg ("Failed to read system backends, proceeding with user- managed backends " )
311
315
}
312
316
313
- for _ , potentialBackend := range potentialBackends {
314
- if potentialBackend .IsDir () {
315
- potentialBackendRunFile := filepath .Join (systemState .Backend .BackendsPath , potentialBackend .Name (), runFile )
317
+ // User-managed backends and alias collection
318
+ entries , err := os .ReadDir (systemState .Backend .BackendsPath )
319
+ if err != nil {
320
+ return nil , err
321
+ }
316
322
317
- var metadata * BackendMetadata
323
+ aliasGroups := make (map [string ][]backendCandidate )
324
+ metaMap := make (map [string ]* BackendMetadata )
318
325
319
- // If metadata file does not exist, we just use the directory name
320
- // and we do not fill the other metadata (such as potential backend Aliases)
321
- metadataFilePath := filepath .Join (systemState .Backend .BackendsPath , potentialBackend .Name (), metadataFile )
322
- if _ , err := os .Stat (metadataFilePath ); os .IsNotExist (err ) {
323
- metadata = & BackendMetadata {
324
- Name : potentialBackend .Name (),
325
- }
326
+ for _ , e := range entries {
327
+ if ! e .IsDir () {
328
+ continue
329
+ }
330
+ dir := e .Name ()
331
+ run := filepath .Join (systemState .Backend .BackendsPath , dir , runFile )
332
+
333
+ var metadata * BackendMetadata
334
+ metadataPath := filepath .Join (systemState .Backend .BackendsPath , dir , metadataFile )
335
+ if _ , err := os .Stat (metadataPath ); os .IsNotExist (err ) {
336
+ metadata = & BackendMetadata {Name : dir }
337
+ } else {
338
+ m , rerr := readBackendMetadata (filepath .Join (systemState .Backend .BackendsPath , dir ))
339
+ if rerr != nil {
340
+ return nil , rerr
341
+ }
342
+ if m == nil {
343
+ metadata = & BackendMetadata {Name : dir }
326
344
} else {
327
- // Check for alias in metadata
328
- metadata , err = readBackendMetadata (filepath .Join (systemState .Backend .BackendsPath , potentialBackend .Name ()))
329
- if err != nil {
330
- return nil , err
331
- }
345
+ metadata = m
332
346
}
347
+ }
333
348
334
- if ! backends .Exists (potentialBackend .Name ()) {
335
- // We don't want to override aliases if already set, and if we are meta backend
336
- if _ , err := os .Stat (potentialBackendRunFile ); err == nil {
337
- backends [potentialBackend .Name ()] = SystemBackend {
338
- Name : potentialBackend .Name (),
339
- RunFile : potentialBackendRunFile ,
340
- IsMeta : false ,
341
- Metadata : metadata ,
342
- }
343
- }
349
+ metaMap [dir ] = metadata
350
+
351
+ // Concrete backend entry
352
+ if _ , err := os .Stat (run ); err == nil {
353
+ backends [dir ] = SystemBackend {
354
+ Name : dir ,
355
+ RunFile : run ,
356
+ IsMeta : false ,
357
+ Metadata : metadata ,
344
358
}
359
+ }
345
360
346
- if metadata == nil {
347
- continue
361
+ // Alias candidates
362
+ if metadata .Alias != "" {
363
+ aliasGroups [metadata .Alias ] = append (aliasGroups [metadata .Alias ], backendCandidate {name : dir , runFile : run })
364
+ }
365
+
366
+ // Meta backends indirection
367
+ if metadata .MetaBackendFor != "" {
368
+ backends [metadata .Name ] = SystemBackend {
369
+ Name : metadata .Name ,
370
+ RunFile : filepath .Join (systemState .Backend .BackendsPath , metadata .MetaBackendFor , runFile ),
371
+ IsMeta : true ,
372
+ Metadata : metadata ,
348
373
}
374
+ }
375
+ }
349
376
350
- if metadata .Alias != "" {
351
- backends [metadata .Alias ] = SystemBackend {
352
- Name : metadata .Alias ,
353
- RunFile : potentialBackendRunFile ,
354
- IsMeta : false ,
355
- Metadata : metadata ,
377
+ // Resolve aliases using system capability preferences
378
+ tokens := systemState .BackendPreferenceTokens ()
379
+ for alias , cands := range aliasGroups {
380
+ chosen := backendCandidate {}
381
+ // Try preference tokens
382
+ for _ , t := range tokens {
383
+ for _ , c := range cands {
384
+ if strings .Contains (strings .ToLower (c .name ), t ) && c .runFile != "" {
385
+ chosen = c
386
+ break
356
387
}
357
388
}
358
-
359
- if metadata .MetaBackendFor != "" {
360
- backends [metadata .Name ] = SystemBackend {
361
- Name : metadata .Name ,
362
- RunFile : filepath .Join (systemState .Backend .BackendsPath , metadata .MetaBackendFor , runFile ),
363
- IsMeta : true ,
364
- Metadata : metadata ,
389
+ if chosen .runFile != "" {
390
+ break
391
+ }
392
+ }
393
+ // Fallback: first runnable
394
+ if chosen .runFile == "" {
395
+ for _ , c := range cands {
396
+ if c .runFile != "" {
397
+ chosen = c
398
+ break
365
399
}
366
400
}
367
401
}
402
+ if chosen .runFile == "" {
403
+ continue
404
+ }
405
+ md := metaMap [chosen .name ]
406
+ backends [alias ] = SystemBackend {
407
+ Name : alias ,
408
+ RunFile : chosen .runFile ,
409
+ IsMeta : false ,
410
+ Metadata : md ,
411
+ }
368
412
}
369
413
370
414
return backends , nil
0 commit comments