mirror of
https://github.com/lordmathis/llamactl.git
synced 2025-12-23 01:24:24 +00:00
Implement per instance command override on backend
This commit is contained in:
@@ -142,27 +142,49 @@ func (o *Options) getBackend() backend {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *Options) isDockerEnabled(backend *config.BackendSettings) bool {
|
// isDockerEnabled checks if Docker is enabled with an optional override
|
||||||
if backend.Docker != nil && backend.Docker.Enabled && o.BackendType != BackendTypeMlxLm {
|
func (o *Options) isDockerEnabled(backend *config.BackendSettings, dockerEnabledOverride *bool) bool {
|
||||||
return true
|
// Check if backend supports Docker
|
||||||
|
if backend.Docker == nil {
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
return false
|
|
||||||
|
// MLX doesn't support Docker
|
||||||
|
if o.BackendType == BackendTypeMlxLm {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for instance-level override
|
||||||
|
if dockerEnabledOverride != nil {
|
||||||
|
return *dockerEnabledOverride
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fall back to config value
|
||||||
|
return backend.Docker.Enabled
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *Options) IsDockerEnabled(backendConfig *config.BackendConfig) bool {
|
func (o *Options) IsDockerEnabled(backendConfig *config.BackendConfig) bool {
|
||||||
backendSettings := o.getBackendSettings(backendConfig)
|
backendSettings := o.getBackendSettings(backendConfig)
|
||||||
return o.isDockerEnabled(backendSettings)
|
return o.isDockerEnabled(backendSettings, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetCommand builds the command to run the backend
|
// GetCommand builds the command to run the backend
|
||||||
func (o *Options) GetCommand(backendConfig *config.BackendConfig) string {
|
func (o *Options) GetCommand(backendConfig *config.BackendConfig, dockerEnabled *bool, commandOverride string) string {
|
||||||
|
|
||||||
backendSettings := o.getBackendSettings(backendConfig)
|
backendSettings := o.getBackendSettings(backendConfig)
|
||||||
|
|
||||||
if o.isDockerEnabled(backendSettings) {
|
// Determine if Docker is enabled
|
||||||
|
useDocker := o.isDockerEnabled(backendSettings, dockerEnabled)
|
||||||
|
|
||||||
|
if useDocker {
|
||||||
return "docker"
|
return "docker"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check for command override (only applies when not in Docker mode)
|
||||||
|
if commandOverride != "" {
|
||||||
|
return commandOverride
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fall back to config command
|
||||||
return backendSettings.Command
|
return backendSettings.Command
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -177,7 +199,7 @@ func (o *Options) BuildCommandArgs(backendConfig *config.BackendConfig) []string
|
|||||||
return args
|
return args
|
||||||
}
|
}
|
||||||
|
|
||||||
if o.isDockerEnabled(backendSettings) {
|
if o.isDockerEnabled(backendSettings, nil) {
|
||||||
// For Docker, start with Docker args
|
// For Docker, start with Docker args
|
||||||
args = append(args, backendSettings.Docker.Args...)
|
args = append(args, backendSettings.Docker.Args...)
|
||||||
args = append(args, backendSettings.Docker.Image)
|
args = append(args, backendSettings.Docker.Image)
|
||||||
@@ -202,7 +224,7 @@ func (o *Options) BuildEnvironment(backendConfig *config.BackendConfig, environm
|
|||||||
maps.Copy(env, backendSettings.Environment)
|
maps.Copy(env, backendSettings.Environment)
|
||||||
}
|
}
|
||||||
|
|
||||||
if o.isDockerEnabled(backendSettings) {
|
if o.isDockerEnabled(backendSettings, nil) {
|
||||||
if backendSettings.Docker.Environment != nil {
|
if backendSettings.Docker.Environment != nil {
|
||||||
maps.Copy(env, backendSettings.Docker.Environment)
|
maps.Copy(env, backendSettings.Docker.Environment)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"llamactl/pkg/backends"
|
"llamactl/pkg/backends"
|
||||||
|
"llamactl/pkg/config"
|
||||||
"llamactl/pkg/testutil"
|
"llamactl/pkg/testutil"
|
||||||
"reflect"
|
"reflect"
|
||||||
"testing"
|
"testing"
|
||||||
@@ -549,3 +550,79 @@ func TestParseLlamaCommand_ExtraArgs(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
func TestLlamaCppGetCommand_WithOverrides(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
dockerInConfig bool
|
||||||
|
dockerEnabled *bool
|
||||||
|
commandOverride string
|
||||||
|
expected string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "no overrides - use config command",
|
||||||
|
dockerInConfig: false,
|
||||||
|
dockerEnabled: nil,
|
||||||
|
commandOverride: "",
|
||||||
|
expected: "/usr/bin/llama-server",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "override to enable docker",
|
||||||
|
dockerInConfig: false,
|
||||||
|
dockerEnabled: boolPtr(true),
|
||||||
|
commandOverride: "",
|
||||||
|
expected: "docker",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "override to disable docker",
|
||||||
|
dockerInConfig: true,
|
||||||
|
dockerEnabled: boolPtr(false),
|
||||||
|
commandOverride: "",
|
||||||
|
expected: "/usr/bin/llama-server",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "command override",
|
||||||
|
dockerInConfig: false,
|
||||||
|
dockerEnabled: nil,
|
||||||
|
commandOverride: "/custom/llama-server",
|
||||||
|
expected: "/custom/llama-server",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "docker takes precedence over command override",
|
||||||
|
dockerInConfig: false,
|
||||||
|
dockerEnabled: boolPtr(true),
|
||||||
|
commandOverride: "/custom/llama-server",
|
||||||
|
expected: "docker",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
backendConfig := &config.BackendConfig{
|
||||||
|
LlamaCpp: config.BackendSettings{
|
||||||
|
Command: "/usr/bin/llama-server",
|
||||||
|
Docker: &config.DockerSettings{
|
||||||
|
Enabled: tt.dockerInConfig,
|
||||||
|
Image: "test-image",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
opts := backends.Options{
|
||||||
|
BackendType: backends.BackendTypeLlamaCpp,
|
||||||
|
LlamaServerOptions: &backends.LlamaServerOptions{
|
||||||
|
Model: "test-model.gguf",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
result := opts.GetCommand(backendConfig, tt.dockerEnabled, tt.commandOverride)
|
||||||
|
if result != tt.expected {
|
||||||
|
t.Errorf("GetCommand() = %v, want %v", result, tt.expected)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper function to create bool pointer
|
||||||
|
func boolPtr(b bool) *bool {
|
||||||
|
return &b
|
||||||
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package backends_test
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"llamactl/pkg/backends"
|
"llamactl/pkg/backends"
|
||||||
|
"llamactl/pkg/config"
|
||||||
"llamactl/pkg/testutil"
|
"llamactl/pkg/testutil"
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
@@ -274,3 +275,57 @@ func TestParseMlxCommand_ExtraArgs(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
func TestMlxGetCommand_NoDocker(t *testing.T) {
|
||||||
|
// MLX backend should never use Docker
|
||||||
|
backendConfig := &config.BackendConfig{
|
||||||
|
MLX: config.BackendSettings{
|
||||||
|
Command: "/usr/bin/mlx-server",
|
||||||
|
Docker: &config.DockerSettings{
|
||||||
|
Enabled: true, // Even if enabled in config
|
||||||
|
Image: "test-image",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
opts := backends.Options{
|
||||||
|
BackendType: backends.BackendTypeMlxLm,
|
||||||
|
MlxServerOptions: &backends.MlxServerOptions{
|
||||||
|
Model: "test-model",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
dockerEnabled *bool
|
||||||
|
commandOverride string
|
||||||
|
expected string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "ignores docker in config",
|
||||||
|
dockerEnabled: nil,
|
||||||
|
commandOverride: "",
|
||||||
|
expected: "/usr/bin/mlx-server",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "ignores docker override",
|
||||||
|
dockerEnabled: boolPtr(true),
|
||||||
|
commandOverride: "",
|
||||||
|
expected: "/usr/bin/mlx-server",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "respects command override",
|
||||||
|
dockerEnabled: nil,
|
||||||
|
commandOverride: "/custom/mlx-server",
|
||||||
|
expected: "/custom/mlx-server",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
result := opts.GetCommand(backendConfig, tt.dockerEnabled, tt.commandOverride)
|
||||||
|
if result != tt.expected {
|
||||||
|
t.Errorf("GetCommand() = %v, want %v", result, tt.expected)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -255,7 +255,7 @@ func (i *Instance) getCommand() string {
|
|||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
return opts.BackendOptions.GetCommand(i.globalBackendSettings)
|
return opts.BackendOptions.GetCommand(i.globalBackendSettings, opts.DockerEnabled, opts.CommandOverride)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *Instance) buildCommandArgs() []string {
|
func (i *Instance) buildCommandArgs() []string {
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"llamactl/pkg/backends"
|
"llamactl/pkg/backends"
|
||||||
"llamactl/pkg/config"
|
"llamactl/pkg/config"
|
||||||
|
"llamactl/pkg/validation"
|
||||||
"log"
|
"log"
|
||||||
"slices"
|
"slices"
|
||||||
"sync"
|
"sync"
|
||||||
@@ -22,6 +23,11 @@ type Options struct {
|
|||||||
IdleTimeout *int `json:"idle_timeout,omitempty"` // minutes
|
IdleTimeout *int `json:"idle_timeout,omitempty"` // minutes
|
||||||
// Environment variables
|
// Environment variables
|
||||||
Environment map[string]string `json:"environment,omitempty"`
|
Environment map[string]string `json:"environment,omitempty"`
|
||||||
|
|
||||||
|
// Execution context overrides
|
||||||
|
DockerEnabled *bool `json:"docker_enabled,omitempty"`
|
||||||
|
CommandOverride string `json:"command_override,omitempty"`
|
||||||
|
|
||||||
// Assigned nodes
|
// Assigned nodes
|
||||||
Nodes map[string]struct{} `json:"-"`
|
Nodes map[string]struct{} `json:"-"`
|
||||||
// Backend options
|
// Backend options
|
||||||
@@ -200,6 +206,28 @@ func (c *Options) validateAndApplyDefaults(name string, globalSettings *config.I
|
|||||||
*c.IdleTimeout = 0
|
*c.IdleTimeout = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Validate docker_enabled and command_override relationship
|
||||||
|
if c.DockerEnabled != nil && *c.DockerEnabled && c.CommandOverride != "" {
|
||||||
|
log.Printf("Instance %s: command_override cannot be set when docker_enabled is true, ignoring command_override", name)
|
||||||
|
c.CommandOverride = "" // Clear invalid configuration
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate command_override if set
|
||||||
|
if c.CommandOverride != "" {
|
||||||
|
if err := validation.ValidateStringForInjection(c.CommandOverride); err != nil {
|
||||||
|
log.Printf("Instance %s: invalid command_override: %v, clearing value", name, err)
|
||||||
|
c.CommandOverride = "" // Clear invalid value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate docker_enabled for MLX backend
|
||||||
|
if c.BackendOptions.BackendType == backends.BackendTypeMlxLm {
|
||||||
|
if c.DockerEnabled != nil && *c.DockerEnabled {
|
||||||
|
log.Printf("Instance %s: docker_enabled is not supported for MLX backend, ignoring", name)
|
||||||
|
c.DockerEnabled = nil // Clear invalid configuration
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Apply defaults from global settings for nil fields
|
// Apply defaults from global settings for nil fields
|
||||||
if globalSettings != nil {
|
if globalSettings != nil {
|
||||||
if c.AutoRestart == nil {
|
if c.AutoRestart == nil {
|
||||||
|
|||||||
Reference in New Issue
Block a user