Pass backend options to instances

This commit is contained in:
2025-09-16 21:37:48 +02:00
parent 988c4aca40
commit 468688cdbc
5 changed files with 72 additions and 35 deletions

View File

@@ -58,7 +58,7 @@ func main() {
} }
// Initialize the instance manager // Initialize the instance manager
instanceManager := manager.NewInstanceManager(cfg.Instances) instanceManager := manager.NewInstanceManager(cfg.Backends, cfg.Instances)
// Create a new handler with the instance manager // Create a new handler with the instance manager
handler := server.NewHandler(instanceManager, cfg) handler := server.NewHandler(instanceManager, cfg)

View File

@@ -17,9 +17,6 @@ type BackendConfig struct {
// Path to mlx_lm executable (MLX-LM backend) // Path to mlx_lm executable (MLX-LM backend)
MLXLMExecutable string `yaml:"mlx_lm_executable"` MLXLMExecutable string `yaml:"mlx_lm_executable"`
// Optional: Default Python virtual environment path for MLX backends
MLXPythonPath string `yaml:"mlx_python_path,omitempty"`
} }
// AppConfig represents the configuration for llamactl // AppConfig represents the configuration for llamactl
@@ -128,7 +125,6 @@ func LoadConfig(configPath string) (AppConfig, error) {
Backends: BackendConfig{ Backends: BackendConfig{
LlamaExecutable: "llama-server", LlamaExecutable: "llama-server",
MLXLMExecutable: "mlx_lm.server", MLXLMExecutable: "mlx_lm.server",
MLXPythonPath: "", // Empty means use system Python
}, },
Instances: InstancesConfig{ Instances: InstancesConfig{
PortRange: [2]int{8000, 9000}, PortRange: [2]int{8000, 9000},
@@ -250,14 +246,10 @@ func loadEnvVars(cfg *AppConfig) {
// Backend config // Backend config
if llamaExec := os.Getenv("LLAMACTL_LLAMA_EXECUTABLE"); llamaExec != "" { if llamaExec := os.Getenv("LLAMACTL_LLAMA_EXECUTABLE"); llamaExec != "" {
cfg.Backends.LlamaExecutable = llamaExec cfg.Backends.LlamaExecutable = llamaExec
cfg.Instances.LlamaExecutable = llamaExec // Keep for backward compatibility
} }
if mlxLMExec := os.Getenv("LLAMACTL_MLX_LM_EXECUTABLE"); mlxLMExec != "" { if mlxLMExec := os.Getenv("LLAMACTL_MLX_LM_EXECUTABLE"); mlxLMExec != "" {
cfg.Backends.MLXLMExecutable = mlxLMExec cfg.Backends.MLXLMExecutable = mlxLMExec
} }
if mlxPython := os.Getenv("LLAMACTL_MLX_PYTHON_PATH"); mlxPython != "" {
cfg.Backends.MLXPythonPath = mlxPython
}
if autoRestart := os.Getenv("LLAMACTL_DEFAULT_AUTO_RESTART"); autoRestart != "" { if autoRestart := os.Getenv("LLAMACTL_DEFAULT_AUTO_RESTART"); autoRestart != "" {
if b, err := strconv.ParseBool(autoRestart); err == nil { if b, err := strconv.ParseBool(autoRestart); err == nil {
cfg.Instances.DefaultAutoRestart = b cfg.Instances.DefaultAutoRestart = b

View File

@@ -31,9 +31,10 @@ func (realTimeProvider) Now() time.Time {
// Process represents a running instance of the llama server // Process represents a running instance of the llama server
type Process struct { type Process struct {
Name string `json:"name"` Name string `json:"name"`
options *CreateInstanceOptions `json:"-"` options *CreateInstanceOptions `json:"-"`
globalSettings *config.InstancesConfig globalInstanceSettings *config.InstancesConfig
globalBackendSettings *config.BackendConfig
// Status // Status
Status InstanceStatus `json:"status"` Status InstanceStatus `json:"status"`
@@ -65,22 +66,23 @@ type Process struct {
} }
// NewInstance creates a new instance with the given name, log path, and options // NewInstance creates a new instance with the given name, log path, and options
func NewInstance(name string, globalSettings *config.InstancesConfig, options *CreateInstanceOptions, onStatusChange func(oldStatus, newStatus InstanceStatus)) *Process { func NewInstance(name string, globalBackendSettings *config.BackendConfig, globalInstanceSettings *config.InstancesConfig, options *CreateInstanceOptions, onStatusChange func(oldStatus, newStatus InstanceStatus)) *Process {
// Validate and copy options // Validate and copy options
options.ValidateAndApplyDefaults(name, globalSettings) options.ValidateAndApplyDefaults(name, globalInstanceSettings)
// Create the instance logger // Create the instance logger
logger := NewInstanceLogger(name, globalSettings.LogsDir) logger := NewInstanceLogger(name, globalInstanceSettings.LogsDir)
return &Process{ return &Process{
Name: name, Name: name,
options: options, options: options,
globalSettings: globalSettings, globalInstanceSettings: globalInstanceSettings,
logger: logger, globalBackendSettings: globalBackendSettings,
timeProvider: realTimeProvider{}, logger: logger,
Created: time.Now().Unix(), timeProvider: realTimeProvider{},
Status: Stopped, Created: time.Now().Unix(),
onStatusChange: onStatusChange, Status: Stopped,
onStatusChange: onStatusChange,
} }
} }
@@ -96,7 +98,13 @@ func (i *Process) GetPort() int {
if i.options != nil { if i.options != nil {
switch i.options.BackendType { switch i.options.BackendType {
case backends.BackendTypeLlamaCpp: case backends.BackendTypeLlamaCpp:
return i.options.LlamaServerOptions.Port if i.options.LlamaServerOptions != nil {
return i.options.LlamaServerOptions.Port
}
case backends.BackendTypeMlxLm:
if i.options.MlxServerOptions != nil {
return i.options.MlxServerOptions.Port
}
} }
} }
return 0 return 0
@@ -108,7 +116,13 @@ func (i *Process) GetHost() string {
if i.options != nil { if i.options != nil {
switch i.options.BackendType { switch i.options.BackendType {
case backends.BackendTypeLlamaCpp: case backends.BackendTypeLlamaCpp:
return i.options.LlamaServerOptions.Host if i.options.LlamaServerOptions != nil {
return i.options.LlamaServerOptions.Host
}
case backends.BackendTypeMlxLm:
if i.options.MlxServerOptions != nil {
return i.options.MlxServerOptions.Host
}
} }
} }
return "" return ""
@@ -124,7 +138,7 @@ func (i *Process) SetOptions(options *CreateInstanceOptions) {
} }
// Validate and copy options // Validate and copy options
options.ValidateAndApplyDefaults(i.Name, i.globalSettings) options.ValidateAndApplyDefaults(i.Name, i.globalInstanceSettings)
i.options = options i.options = options
// Clear the proxy so it gets recreated with new options // Clear the proxy so it gets recreated with new options
@@ -153,8 +167,15 @@ func (i *Process) GetProxy() (*httputil.ReverseProxy, error) {
var port int var port int
switch i.options.BackendType { switch i.options.BackendType {
case backends.BackendTypeLlamaCpp: case backends.BackendTypeLlamaCpp:
host = i.options.LlamaServerOptions.Host if i.options.LlamaServerOptions != nil {
port = i.options.LlamaServerOptions.Port host = i.options.LlamaServerOptions.Host
port = i.options.LlamaServerOptions.Port
}
case backends.BackendTypeMlxLm:
if i.options.MlxServerOptions != nil {
host = i.options.MlxServerOptions.Host
port = i.options.MlxServerOptions.Port
}
} }
targetURL, err := url.Parse(fmt.Sprintf("http://%s:%d", host, port)) targetURL, err := url.Parse(fmt.Sprintf("http://%s:%d", host, port))
@@ -215,7 +236,7 @@ func (i *Process) UnmarshalJSON(data []byte) error {
// Handle options with validation and defaults // Handle options with validation and defaults
if aux.Options != nil { if aux.Options != nil {
aux.Options.ValidateAndApplyDefaults(i.Name, i.globalSettings) aux.Options.ValidateAndApplyDefaults(i.Name, i.globalInstanceSettings)
i.options = aux.Options i.options = aux.Options
} }

View File

@@ -9,6 +9,8 @@ import (
"runtime" "runtime"
"syscall" "syscall"
"time" "time"
"llamactl/pkg/backends"
) )
// Start starts the llama server instance and returns an error if it fails. // Start starts the llama server instance and returns an error if it fails.
@@ -41,7 +43,20 @@ func (i *Process) Start() error {
args := i.options.BuildCommandArgs() args := i.options.BuildCommandArgs()
i.ctx, i.cancel = context.WithCancel(context.Background()) i.ctx, i.cancel = context.WithCancel(context.Background())
i.cmd = exec.CommandContext(i.ctx, "llama-server", args...)
var executable string
// Get executable from global configuration
switch i.options.BackendType {
case backends.BackendTypeLlamaCpp:
executable = i.globalBackendSettings.LlamaExecutable
case backends.BackendTypeMlxLm:
executable = i.globalBackendSettings.MLXLMExecutable
default:
return fmt.Errorf("unsupported backend type: %s", i.options.BackendType)
}
i.cmd = exec.CommandContext(i.ctx, executable, args...)
if runtime.GOOS != "windows" { if runtime.GOOS != "windows" {
setProcAttrs(i.cmd) setProcAttrs(i.cmd)
@@ -175,9 +190,16 @@ func (i *Process) WaitForHealthy(timeout int) error {
var host string var host string
var port int var port int
switch opts.BackendType { switch opts.BackendType {
case "llama-cpp": case backends.BackendTypeLlamaCpp:
host = opts.LlamaServerOptions.Host if opts.LlamaServerOptions != nil {
port = opts.LlamaServerOptions.Port host = opts.LlamaServerOptions.Host
port = opts.LlamaServerOptions.Port
}
case backends.BackendTypeMlxLm:
if opts.MlxServerOptions != nil {
host = opts.MlxServerOptions.Host
port = opts.MlxServerOptions.Port
}
} }
if host == "" { if host == "" {
host = "localhost" host = "localhost"

View File

@@ -35,6 +35,7 @@ type instanceManager struct {
runningInstances map[string]struct{} runningInstances map[string]struct{}
ports map[int]bool ports map[int]bool
instancesConfig config.InstancesConfig instancesConfig config.InstancesConfig
backendsConfig config.BackendConfig
// Timeout checker // Timeout checker
timeoutChecker *time.Ticker timeoutChecker *time.Ticker
@@ -44,7 +45,7 @@ type instanceManager struct {
} }
// NewInstanceManager creates a new instance of InstanceManager. // NewInstanceManager creates a new instance of InstanceManager.
func NewInstanceManager(instancesConfig config.InstancesConfig) InstanceManager { func NewInstanceManager(backendsConfig config.BackendConfig, instancesConfig config.InstancesConfig) InstanceManager {
if instancesConfig.TimeoutCheckInterval <= 0 { if instancesConfig.TimeoutCheckInterval <= 0 {
instancesConfig.TimeoutCheckInterval = 5 // Default to 5 minutes if not set instancesConfig.TimeoutCheckInterval = 5 // Default to 5 minutes if not set
} }
@@ -53,6 +54,7 @@ func NewInstanceManager(instancesConfig config.InstancesConfig) InstanceManager
runningInstances: make(map[string]struct{}), runningInstances: make(map[string]struct{}),
ports: make(map[int]bool), ports: make(map[int]bool),
instancesConfig: instancesConfig, instancesConfig: instancesConfig,
backendsConfig: backendsConfig,
timeoutChecker: time.NewTicker(time.Duration(instancesConfig.TimeoutCheckInterval) * time.Minute), timeoutChecker: time.NewTicker(time.Duration(instancesConfig.TimeoutCheckInterval) * time.Minute),
shutdownChan: make(chan struct{}), shutdownChan: make(chan struct{}),
@@ -241,7 +243,7 @@ func (im *instanceManager) loadInstance(name, path string) error {
} }
// Create new inst using NewInstance (handles validation, defaults, setup) // Create new inst using NewInstance (handles validation, defaults, setup)
inst := instance.NewInstance(name, &im.instancesConfig, persistedInstance.GetOptions(), statusCallback) inst := instance.NewInstance(name, &im.backendsConfig, &im.instancesConfig, persistedInstance.GetOptions(), statusCallback)
// Restore persisted fields that NewInstance doesn't set // Restore persisted fields that NewInstance doesn't set
inst.Created = persistedInstance.Created inst.Created = persistedInstance.Created