Skip to content

Commit 24d7dad

Browse files
authored
feat: kong cli refactor fixes #1955 (#1974)
* feat: migrate to alecthomas/kong for CLI Signed-off-by: Chris Jowett <[email protected]> * feat: bring in new flag for granular log levels Signed-off-by: Chris Jowett <[email protected]> * chore: go mod tidy Signed-off-by: Chris Jowett <[email protected]> * feat: allow loading cli flag values from ["./localai.yaml", "~/.config/localai.yaml", "/etc/localai.yaml"] in that order Signed-off-by: Chris Jowett <[email protected]> * feat: load from .env file instead of a yaml file Signed-off-by: Chris Jowett <[email protected]> * feat: better loading for environment files Signed-off-by: Chris Jowett <[email protected]> * feat(doc): add initial documentation about configuration Signed-off-by: Chris Jowett <[email protected]> * fix: remove test log lines Signed-off-by: Chris Jowett <[email protected]> * feat: integrate new documentation into existing pages Signed-off-by: Chris Jowett <[email protected]> * feat: add documentation on .env files Signed-off-by: Chris Jowett <[email protected]> * fix: cleanup some documentation table errors Signed-off-by: Chris Jowett <[email protected]> * feat: refactor CLI logic out to it's own package under core/cli Signed-off-by: Chris Jowett <[email protected]> --------- Signed-off-by: Chris Jowett <[email protected]>
1 parent 92005b9 commit 24d7dad

File tree

10 files changed

+552
-623
lines changed

10 files changed

+552
-623
lines changed

.env

Lines changed: 19 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,33 @@
11
## Set number of threads.
22
## Note: prefer the number of physical cores. Overbooking the CPU degrades performance notably.
3-
# THREADS=14
3+
# LOCALAI_THREADS=14
44

55
## Specify a different bind address (defaults to ":8080")
6-
# ADDRESS=127.0.0.1:8080
6+
# LOCALAI_ADDRESS=127.0.0.1:8080
77

88
## Default models context size
9-
# CONTEXT_SIZE=512
9+
# LOCALAI_CONTEXT_SIZE=512
1010
#
1111
## Define galleries.
1212
## models will to install will be visible in `/models/available`
13-
# GALLERIES=[{"name":"model-gallery", "url":"github:go-skynet/model-gallery/index.yaml"}]
13+
# LOCALAI_GALLERIES=[{"name":"model-gallery", "url":"github:go-skynet/model-gallery/index.yaml"}]
1414

1515
## CORS settings
16-
# CORS=true
17-
# CORS_ALLOW_ORIGINS=*
16+
# LOCALAI_CORS=true
17+
# LOCALAI_CORS_ALLOW_ORIGINS=*
1818

1919
## Default path for models
2020
#
21-
# MODELS_PATH=/models
21+
# LOCALAI_MODELS_PATH=/models
2222

2323
## Enable debug mode
24-
# DEBUG=true
24+
# LOCALAI_LOG_LEVEL=debug
2525

2626
## Disables COMPEL (Diffusers)
2727
# COMPEL=0
2828

2929
## Enable/Disable single backend (useful if only one GPU is available)
30-
# SINGLE_ACTIVE_BACKEND=true
30+
# LOCALAI_SINGLE_ACTIVE_BACKEND=true
3131

3232
## Specify a build type. Available: cublas, openblas, clblas.
3333
## cuBLAS: This is a GPU-accelerated version of the complete standard BLAS (Basic Linear Algebra Subprograms) library. It's provided by Nvidia and is part of their CUDA toolkit.
@@ -46,13 +46,13 @@
4646
# GO_TAGS=stablediffusion
4747

4848
## Path where to store generated images
49-
# IMAGE_PATH=/tmp
49+
# LOCALAI_IMAGE_PATH=/tmp/generated/images
5050

5151
## Specify a default upload limit in MB (whisper)
52-
# UPLOAD_LIMIT
52+
# LOCALAI_UPLOAD_LIMIT=15
5353

5454
## List of external GRPC backends (note on the container image this variable is already set to use extra backends available in extra/)
55-
# EXTERNAL_GRPC_BACKENDS=my-backend:127.0.0.1:9000,my-backend2:/usr/bin/backend.py
55+
# LOCALAI_EXTERNAL_GRPC_BACKENDS=my-backend:127.0.0.1:9000,my-backend2:/usr/bin/backend.py
5656

5757
### Advanced settings ###
5858
### Those are not really used by LocalAI, but from components in the stack ###
@@ -72,18 +72,18 @@
7272
# LLAMACPP_PARALLEL=1
7373

7474
### Enable to run parallel requests
75-
# PARALLEL_REQUESTS=true
75+
# LOCALAI_PARALLEL_REQUESTS=true
7676

7777
### Watchdog settings
7878
###
7979
# Enables watchdog to kill backends that are inactive for too much time
80-
# WATCHDOG_IDLE=true
81-
#
82-
# Enables watchdog to kill backends that are busy for too much time
83-
# WATCHDOG_BUSY=true
80+
# LOCALAI_WATCHDOG_IDLE=true
8481
#
8582
# Time in duration format (e.g. 1h30m) after which a backend is considered idle
86-
# WATCHDOG_IDLE_TIMEOUT=5m
83+
# LOCALAI_WATCHDOG_IDLE_TIMEOUT=5m
84+
#
85+
# Enables watchdog to kill backends that are busy for too much time
86+
# LOCALAI_WATCHDOG_BUSY=true
8787
#
8888
# Time in duration format (e.g. 1h30m) after which a backend is considered busy
89-
# WATCHDOG_BUSY_TIMEOUT=5m
89+
# LOCALAI_WATCHDOG_BUSY_TIMEOUT=5m

core/cli/cli.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package cli
2+
3+
import "embed"
4+
5+
type Context struct {
6+
Debug bool `env:"LOCALAI_DEBUG,DEBUG" default:"false" hidden:"" help:"DEPRECATED, use --log-level=debug instead. Enable debug logging"`
7+
LogLevel *string `env:"LOCALAI_LOG_LEVEL" enum:"error,warn,info,debug" help:"Set the level of logs to output [${enum}]"`
8+
9+
// This field is not a command line argument/flag, the struct tag excludes it from the parsed CLI
10+
BackendAssets embed.FS `kong:"-"`
11+
}
12+
13+
var CLI struct {
14+
Context `embed:""`
15+
16+
Run RunCMD `cmd:"" help:"Run LocalAI, this the default command if no other command is specified. Run 'local-ai run --help' for more information" default:"withargs"`
17+
Models ModelsCMD `cmd:"" help:"Manage LocalAI models and definitions"`
18+
TTS TTSCMD `cmd:"" help:"Convert text to speech"`
19+
Transcript TranscriptCMD `cmd:"" help:"Convert audio to text"`
20+
}

core/cli/models.go

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
package cli
2+
3+
import (
4+
"encoding/json"
5+
"fmt"
6+
7+
"github.com/go-skynet/LocalAI/pkg/gallery"
8+
"github.com/rs/zerolog/log"
9+
"github.com/schollz/progressbar/v3"
10+
)
11+
12+
type ModelsCMDFlags struct {
13+
Galleries string `env:"LOCALAI_GALLERIES,GALLERIES" help:"JSON list of galleries" group:"models"`
14+
ModelsPath string `env:"LOCALAI_MODELS_PATH,MODELS_PATH" type:"path" default:"${basepath}/models" help:"Path containing models used for inferencing" group:"storage"`
15+
}
16+
17+
type ModelsList struct {
18+
ModelsCMDFlags `embed:""`
19+
}
20+
21+
type ModelsInstall struct {
22+
ModelArgs []string `arg:"" optional:"" name:"models" help:"Model configuration URLs to load"`
23+
24+
ModelsCMDFlags `embed:""`
25+
}
26+
27+
type ModelsCMD struct {
28+
List ModelsList `cmd:"" help:"List the models avaiable in your galleries" default:"withargs"`
29+
Install ModelsInstall `cmd:"" help:"Install a model from the gallery"`
30+
}
31+
32+
func (ml *ModelsList) Run(ctx *Context) error {
33+
var galleries []gallery.Gallery
34+
if err := json.Unmarshal([]byte(ml.Galleries), &galleries); err != nil {
35+
log.Error().Err(err).Msg("unable to load galleries")
36+
}
37+
38+
models, err := gallery.AvailableGalleryModels(galleries, ml.ModelsPath)
39+
if err != nil {
40+
return err
41+
}
42+
for _, model := range models {
43+
if model.Installed {
44+
fmt.Printf(" * %s@%s (installed)\n", model.Gallery.Name, model.Name)
45+
} else {
46+
fmt.Printf(" - %s@%s\n", model.Gallery.Name, model.Name)
47+
}
48+
}
49+
return nil
50+
}
51+
52+
func (mi *ModelsInstall) Run(ctx *Context) error {
53+
modelName := mi.ModelArgs[0]
54+
55+
var galleries []gallery.Gallery
56+
if err := json.Unmarshal([]byte(mi.Galleries), &galleries); err != nil {
57+
log.Error().Err(err).Msg("unable to load galleries")
58+
}
59+
60+
progressBar := progressbar.NewOptions(
61+
1000,
62+
progressbar.OptionSetDescription(fmt.Sprintf("downloading model %s", modelName)),
63+
progressbar.OptionShowBytes(false),
64+
progressbar.OptionClearOnFinish(),
65+
)
66+
progressCallback := func(fileName string, current string, total string, percentage float64) {
67+
progressBar.Set(int(percentage * 10))
68+
}
69+
err := gallery.InstallModelFromGallery(galleries, modelName, mi.ModelsPath, gallery.GalleryModel{}, progressCallback)
70+
if err != nil {
71+
return err
72+
}
73+
return nil
74+
}

core/cli/run.go

Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
package cli
2+
3+
import (
4+
"fmt"
5+
"os"
6+
"strings"
7+
"time"
8+
9+
"github.com/go-skynet/LocalAI/core/config"
10+
"github.com/go-skynet/LocalAI/core/http"
11+
"github.com/go-skynet/LocalAI/core/startup"
12+
"github.com/rs/zerolog/log"
13+
)
14+
15+
type RunCMD struct {
16+
ModelArgs []string `arg:"" optional:"" name:"models" help:"Model configuration URLs to load"`
17+
18+
ModelsPath string `env:"LOCALAI_MODELS_PATH,MODELS_PATH" type:"path" default:"${basepath}/models" help:"Path containing models used for inferencing" group:"storage"`
19+
BackendAssetsPath string `env:"LOCALAI_BACKEND_ASSETS_PATH,BACKEND_ASSETS_PATH" type:"path" default:"/tmp/localai/backend_data" help:"Path used to extract libraries that are required by some of the backends in runtime" group:"storage"`
20+
ImagePath string `env:"LOCALAI_IMAGE_PATH,IMAGE_PATH" type:"path" default:"/tmp/generated/images" help:"Location for images generated by backends (e.g. stablediffusion)" group:"storage"`
21+
AudioPath string `env:"LOCALAI_AUDIO_PATH,AUDIO_PATH" type:"path" default:"/tmp/generated/audio" help:"Location for audio generated by backends (e.g. piper)" group:"storage"`
22+
UploadPath string `env:"LOCALAI_UPLOAD_PATH,UPLOAD_PATH" type:"path" default:"/tmp/localai/upload" help:"Path to store uploads from files api" group:"storage"`
23+
ConfigPath string `env:"LOCALAI_CONFIG_PATH,CONFIG_PATH" default:"/tmp/localai/config" group:"storage"`
24+
LocalaiConfigDir string `env:"LOCALAI_CONFIG_DIR" type:"path" default:"${basepath}/configuration" help:"Directory for dynamic loading of certain configuration files (currently api_keys.json and external_backends.json)" group:"storage"`
25+
// The alias on this option is there to preserve functionality with the old `--config-file` parameter
26+
ModelsConfigFile string `env:"LOCALAI_MODELS_CONFIG_FILE,CONFIG_FILE" aliases:"config-file" help:"YAML file containing a list of model backend configs" group:"storage"`
27+
28+
Galleries string `env:"LOCALAI_GALLERIES,GALLERIES" help:"JSON list of galleries" group:"models"`
29+
AutoloadGalleries bool `env:"LOCALAI_AUTOLOAD_GALLERIES,AUTOLOAD_GALLERIES" group:"models"`
30+
RemoteLibrary string `env:"LOCALAI_REMOTE_LIBRARY,REMOTE_LIBRARY" default:"${remoteLibraryURL}" help:"A LocalAI remote library URL" group:"models"`
31+
PreloadModels string `env:"LOCALAI_PRELOAD_MODELS,PRELOAD_MODELS" help:"A List of models to apply in JSON at start" group:"models"`
32+
Models []string `env:"LOCALAI_MODELS,MODELS" help:"A List of model configuration URLs to load" group:"models"`
33+
PreloadModelsConfig string `env:"LOCALAI_PRELOAD_MODELS_CONFIG,PRELOAD_MODELS_CONFIG" help:"A List of models to apply at startup. Path to a YAML config file" group:"models"`
34+
35+
F16 bool `name:"f16" env:"LOCALAI_F16,F16" help:"Enable GPU acceleration" group:"performance"`
36+
Threads int `env:"LOCALAI_THREADS,THREADS" short:"t" default:"4" help:"Number of threads used for parallel computation. Usage of the number of physical cores in the system is suggested" group:"performance"`
37+
ContextSize int `env:"LOCALAI_CONTEXT_SIZE,CONTEXT_SIZE" default:"512" help:"Default context size for models" group:"performance"`
38+
39+
Address string `env:"LOCALAI_ADDRESS,ADDRESS" default:":8080" help:"Bind address for the API server" group:"api"`
40+
CORS bool `env:"LOCALAI_CORS,CORS" help:"" group:"api"`
41+
CORSAllowOrigins string `env:"LOCALAI_CORS_ALLOW_ORIGINS,CORS_ALLOW_ORIGINS" group:"api"`
42+
UploadLimit int `env:"LOCALAI_UPLOAD_LIMIT,UPLOAD_LIMIT" default:"15" help:"Default upload-limit in MB" group:"api"`
43+
APIKeys []string `env:"LOCALAI_API_KEY,API_KEY" help:"List of API Keys to enable API authentication. When this is set, all the requests must be authenticated with one of these API keys" group:"api"`
44+
DisableWelcome bool `env:"LOCALAI_DISABLE_WELCOME,DISABLE_WELCOME" default:"false" help:"Disable welcome pages" group:"api"`
45+
46+
ParallelRequests bool `env:"LOCALAI_PARALLEL_REQUESTS,PARALLEL_REQUESTS" help:"Enable backends to handle multiple requests in parallel if they support it (e.g.: llama.cpp or vllm)" group:"backends"`
47+
SingleActiveBackend bool `env:"LOCALAI_SINGLE_ACTIVE_BACKEND,SINGLE_ACTIVE_BACKEND" help:"Allow only one backend to be run at a time" group:"backends"`
48+
PreloadBackendOnly bool `env:"LOCALAI_PRELOAD_BACKEND_ONLY,PRELOAD_BACKEND_ONLY" default:"false" help:"Do not launch the API services, only the preloaded models / backends are started (useful for multi-node setups)" group:"backends"`
49+
ExternalGRPCBackends []string `env:"LOCALAI_EXTERNAL_GRPC_BACKENDS,EXTERNAL_GRPC_BACKENDS" help:"A list of external grpc backends" group:"backends"`
50+
EnableWatchdogIdle bool `env:"LOCALAI_WATCHDOG_IDLE,WATCHDOG_IDLE" default:"false" help:"Enable watchdog for stopping backends that are idle longer than the watchdog-idle-timeout" group:"backends"`
51+
WatchdogIdleTimeout string `env:"LOCALAI_WATCHDOG_IDLE_TIMEOUT,WATCHDOG_IDLE_TIMEOUT" default:"15m" help:"Threshold beyond which an idle backend should be stopped" group:"backends"`
52+
EnableWatchdogBusy bool `env:"LOCALAI_WATCHDOG_BUSY,WATCHDOG_BUSY" default:"false" help:"Enable watchdog for stopping backends that are busy longer than the watchdog-busy-timeout" group:"backends"`
53+
WatchdogBusyTimeout string `env:"LOCALAI_WATCHDOG_BUSY_TIMEOUT,WATCHDOG_BUSY_TIMEOUT" default:"5m" help:"Threshold beyond which a busy backend should be stopped" group:"backends"`
54+
}
55+
56+
func (r *RunCMD) Run(ctx *Context) error {
57+
opts := []config.AppOption{
58+
config.WithConfigFile(r.ModelsConfigFile),
59+
config.WithJSONStringPreload(r.PreloadModels),
60+
config.WithYAMLConfigPreload(r.PreloadModelsConfig),
61+
config.WithModelPath(r.ModelsPath),
62+
config.WithContextSize(r.ContextSize),
63+
config.WithDebug(ctx.Debug),
64+
config.WithImageDir(r.ImagePath),
65+
config.WithAudioDir(r.AudioPath),
66+
config.WithUploadDir(r.UploadPath),
67+
config.WithConfigsDir(r.ConfigPath),
68+
config.WithF16(r.F16),
69+
config.WithStringGalleries(r.Galleries),
70+
config.WithModelLibraryURL(r.RemoteLibrary),
71+
config.WithDisableMessage(false),
72+
config.WithCors(r.CORS),
73+
config.WithCorsAllowOrigins(r.CORSAllowOrigins),
74+
config.WithThreads(r.Threads),
75+
config.WithBackendAssets(ctx.BackendAssets),
76+
config.WithBackendAssetsOutput(r.BackendAssetsPath),
77+
config.WithUploadLimitMB(r.UploadLimit),
78+
config.WithApiKeys(r.APIKeys),
79+
config.WithModelsURL(append(r.Models, r.ModelArgs...)...),
80+
}
81+
82+
idleWatchDog := r.EnableWatchdogIdle
83+
busyWatchDog := r.EnableWatchdogBusy
84+
85+
if r.DisableWelcome {
86+
opts = append(opts, config.DisableWelcomePage)
87+
}
88+
89+
if idleWatchDog || busyWatchDog {
90+
opts = append(opts, config.EnableWatchDog)
91+
if idleWatchDog {
92+
opts = append(opts, config.EnableWatchDogIdleCheck)
93+
dur, err := time.ParseDuration(r.WatchdogIdleTimeout)
94+
if err != nil {
95+
return err
96+
}
97+
opts = append(opts, config.SetWatchDogIdleTimeout(dur))
98+
}
99+
if busyWatchDog {
100+
opts = append(opts, config.EnableWatchDogBusyCheck)
101+
dur, err := time.ParseDuration(r.WatchdogBusyTimeout)
102+
if err != nil {
103+
return err
104+
}
105+
opts = append(opts, config.SetWatchDogBusyTimeout(dur))
106+
}
107+
}
108+
if r.ParallelRequests {
109+
opts = append(opts, config.EnableParallelBackendRequests)
110+
}
111+
if r.SingleActiveBackend {
112+
opts = append(opts, config.EnableSingleBackend)
113+
}
114+
115+
// split ":" to get backend name and the uri
116+
for _, v := range r.ExternalGRPCBackends {
117+
backend := v[:strings.IndexByte(v, ':')]
118+
uri := v[strings.IndexByte(v, ':')+1:]
119+
opts = append(opts, config.WithExternalBackend(backend, uri))
120+
}
121+
122+
if r.AutoloadGalleries {
123+
opts = append(opts, config.EnableGalleriesAutoload)
124+
}
125+
126+
if r.PreloadBackendOnly {
127+
_, _, _, err := startup.Startup(opts...)
128+
return err
129+
}
130+
131+
cl, ml, options, err := startup.Startup(opts...)
132+
133+
if err != nil {
134+
return fmt.Errorf("failed basic startup tasks with error %s", err.Error())
135+
}
136+
137+
// Watch the configuration directory
138+
// If the directory does not exist, we don't watch it
139+
if _, err := os.Stat(r.LocalaiConfigDir); err == nil {
140+
closeConfigWatcherFn, err := startup.WatchConfigDirectory(r.LocalaiConfigDir, options)
141+
defer closeConfigWatcherFn()
142+
143+
if err != nil {
144+
return fmt.Errorf("failed while watching configuration directory %s", r.LocalaiConfigDir)
145+
}
146+
}
147+
148+
appHTTP, err := http.App(cl, ml, options)
149+
if err != nil {
150+
log.Error().Err(err).Msg("error during HTTP App construction")
151+
return err
152+
}
153+
154+
return appHTTP.Listen(r.Address)
155+
}

core/cli/transcript.go

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
package cli
2+
3+
import (
4+
"context"
5+
"errors"
6+
"fmt"
7+
8+
"github.com/go-skynet/LocalAI/core/backend"
9+
"github.com/go-skynet/LocalAI/core/config"
10+
"github.com/go-skynet/LocalAI/pkg/model"
11+
)
12+
13+
type TranscriptCMD struct {
14+
Filename string `arg:""`
15+
16+
Backend string `short:"b" default:"whisper" help:"Backend to run the transcription model"`
17+
Model string `short:"m" required:"" help:"Model name to run the TTS"`
18+
Language string `short:"l" help:"Language of the audio file"`
19+
Threads int `short:"t" default:"1" help:"Number of threads used for parallel computation"`
20+
ModelsPath string `env:"LOCALAI_MODELS_PATH,MODELS_PATH" type:"path" default:"${basepath}/models" help:"Path containing models used for inferencing" group:"storage"`
21+
BackendAssetsPath string `env:"LOCALAI_BACKEND_ASSETS_PATH,BACKEND_ASSETS_PATH" type:"path" default:"/tmp/localai/backend_data" help:"Path used to extract libraries that are required by some of the backends in runtime" group:"storage"`
22+
}
23+
24+
func (t *TranscriptCMD) Run(ctx *Context) error {
25+
opts := &config.ApplicationConfig{
26+
ModelPath: t.ModelsPath,
27+
Context: context.Background(),
28+
AssetsDestination: t.BackendAssetsPath,
29+
}
30+
31+
cl := config.NewBackendConfigLoader()
32+
ml := model.NewModelLoader(opts.ModelPath)
33+
if err := cl.LoadBackendConfigsFromPath(t.ModelsPath); err != nil {
34+
return err
35+
}
36+
37+
c, exists := cl.GetBackendConfig(t.Model)
38+
if !exists {
39+
return errors.New("model not found")
40+
}
41+
42+
c.Threads = &t.Threads
43+
44+
defer ml.StopAllGRPC()
45+
46+
tr, err := backend.ModelTranscription(t.Filename, t.Language, ml, c, opts)
47+
if err != nil {
48+
return err
49+
}
50+
for _, segment := range tr.Segments {
51+
fmt.Println(segment.Start.String(), "-", segment.Text)
52+
}
53+
return nil
54+
}

0 commit comments

Comments
 (0)