From ea45ea68d16c05934a11b24db0aeb68a8e3e4f22 Mon Sep 17 00:00:00 2001 From: Manish Biswal Date: Wed, 4 Mar 2026 14:32:44 +0530 Subject: [PATCH 1/2] feat: auto download backends Signed-off-by: Manish Biswal --- cmd/cli/commands/run.go | 15 ++++++ cmd/cli/commands/utils.go | 103 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 118 insertions(+) diff --git a/cmd/cli/commands/run.go b/cmd/cli/commands/run.go index 58e929131..4c3a1aa4a 100644 --- a/cmd/cli/commands/run.go +++ b/cmd/cli/commands/run.go @@ -820,6 +820,21 @@ func newRunCmd() *cobra.Command { } } + modelInfo, err := desktopClient.Inspect(model, true) + backend := "" + if err == nil { + backend, _ = GetRequiredBackendFromModelInfo(&modelInfo) + } + + if backend != "" { + if err := EnsureBackendAvailable(backend, cmd); err != nil { + if err.Error() == "backend installation cancelled" { + return nil + } + return err + } + } + // Handle --detach flag: just load the model without interaction if detach { if err := desktopClient.Preload(cmd.Context(), model); err != nil { diff --git a/cmd/cli/commands/utils.go b/cmd/cli/commands/utils.go index c77f472b7..76bed532a 100644 --- a/cmd/cli/commands/utils.go +++ b/cmd/cli/commands/utils.go @@ -1,7 +1,9 @@ package commands import ( + "bufio" "bytes" + "encoding/json" "errors" "fmt" "io" @@ -12,7 +14,10 @@ import ( "github.com/docker/model-runner/cmd/cli/desktop" "github.com/docker/model-runner/cmd/cli/pkg/standalone" "github.com/docker/model-runner/pkg/distribution/oci/reference" + "github.com/docker/model-runner/pkg/distribution/types" + "github.com/docker/model-runner/pkg/inference/backends/llamacpp" "github.com/docker/model-runner/pkg/inference/backends/vllm" + dmrm "github.com/docker/model-runner/pkg/inference/models" "github.com/moby/term" "github.com/olekukonko/tablewriter" "github.com/olekukonko/tablewriter/renderer" @@ -270,3 +275,101 @@ func newTable(w io.Writer) *tablewriter.Table { }), ) } + +func CheckBackendInstalled(backend string) (bool, error) { + status := desktopClient.Status() + if status.Error != nil { + return false, fmt.Errorf("failed to get backend status: %w", status.Error) + } + + var backendStatus map[string]string + if err := json.Unmarshal(status.Status, &backendStatus); err != nil { + return false, fmt.Errorf("failed to parse backend status: %w", err) + } + + backendState, exists := backendStatus[backend] + if !exists { + return false, nil + } + + return backendState == "installed" || backendState == "running", nil +} + +func PromptInstallBackend(backend string, cmd *cobra.Command) (bool, error) { + fmt.Fprintf(cmd.OutOrStdout(), "Backend %q is not installed. Download and install it now? [Y/n]: ", backend) + + reader := bufio.NewReader(os.Stdin) + input, err := reader.ReadString('\n') + if err != nil { + return false, fmt.Errorf("failed to read input: %w", err) + } + + input = strings.TrimSpace(strings.ToLower(input)) + return input == "" || input == "y" || input == "yes", nil +} + +func InstallBackend(backend string, cmd *cobra.Command) error { + installCmd := newInstallRunner() + installCmd.SetArgs([]string{"--backend", backend}) + + if err := installCmd.Execute(); err != nil { + return fmt.Errorf("failed to install backend %s: %w", backend, err) + } + + return nil +} + +func EnsureBackendAvailable(backend string, cmd *cobra.Command) error { + installed, err := CheckBackendInstalled(backend) + if err != nil { + return err + } + + if installed { + return nil + } + + confirm, err := PromptInstallBackend(backend, cmd) + if err != nil { + return err + } + + if !confirm { + cmd.Printf("Run 'docker model install-runner --backend %s' to install it manually.\n", backend) + return fmt.Errorf("backend installation cancelled") + } + + if err := InstallBackend(backend, cmd); err != nil { + return err + } + + cmd.Printf("Backend %q installed successfully.\n", backend) + return nil +} + +func GetRequiredBackend(model string) (string, error) { + modelInfo, err := desktopClient.Inspect(model, false) + if err != nil { + return "", err + } + + return GetRequiredBackendFromModelInfo(&modelInfo) +} + +func GetRequiredBackendFromModelInfo(modelInfo *dmrm.Model) (string, error) { + config, ok := modelInfo.Config.(*types.Config) + if !ok { + return llamacpp.Name, nil + } + + switch config.Format { + case types.FormatSafetensors: + return vllm.Name, nil + case types.FormatGGUF: + return llamacpp.Name, nil + case types.FormatDiffusers: + return "diffusers", nil + default: + return llamacpp.Name, nil + } +} From fcca922754b456c2f1e62a54a9cfb60e30205a8c Mon Sep 17 00:00:00 2001 From: Manish Biswal Date: Sat, 7 Mar 2026 21:45:33 +0530 Subject: [PATCH 2/2] feat(cli): prompt and install required backend during run --- cmd/cli/commands/run.go | 28 ++++++++++++++++++---------- cmd/cli/commands/utils.go | 15 ++++++++++++++- 2 files changed, 32 insertions(+), 11 deletions(-) diff --git a/cmd/cli/commands/run.go b/cmd/cli/commands/run.go index 4c3a1aa4a..f3d7beb4e 100644 --- a/cmd/cli/commands/run.go +++ b/cmd/cli/commands/run.go @@ -809,20 +809,21 @@ func newRunCmd() *cobra.Command { return nil } - _, err := desktopClient.Inspect(model, false) - if err != nil { - if !errors.Is(err, desktop.ErrNotFound) { - return handleClientError(err, "Failed to inspect model") - } - cmd.Println("Unable to find model '" + model + "' locally. Pulling from the server.") - if err := pullModel(cmd, desktopClient, model); err != nil { - return err + modelInfo, err := desktopClient.Inspect(model, false) + modelFoundLocally := err == nil + if err != nil && !errors.Is(err, desktop.ErrNotFound) { + return handleClientError(err, "Failed to inspect model") + } + + if !modelFoundLocally { + remoteInfo, remoteErr := desktopClient.Inspect(model, true) + if remoteErr == nil { + modelInfo = remoteInfo } } - modelInfo, err := desktopClient.Inspect(model, true) backend := "" - if err == nil { + if modelInfo.ID != "" { backend, _ = GetRequiredBackendFromModelInfo(&modelInfo) } @@ -835,6 +836,13 @@ func newRunCmd() *cobra.Command { } } + if !modelFoundLocally { + cmd.Println("Unable to find model '" + model + "' locally. Pulling from the server.") + if err := pullModel(cmd, desktopClient, model); err != nil { + return err + } + } + // Handle --detach flag: just load the model without interaction if detach { if err := desktopClient.Preload(cmd.Context(), model); err != nil { diff --git a/cmd/cli/commands/utils.go b/cmd/cli/commands/utils.go index 76bed532a..27d504823 100644 --- a/cmd/cli/commands/utils.go +++ b/cmd/cli/commands/utils.go @@ -292,7 +292,12 @@ func CheckBackendInstalled(backend string) (bool, error) { return false, nil } - return backendState == "installed" || backendState == "running", nil + state := strings.TrimSpace(strings.ToLower(backendState)) + if strings.HasPrefix(state, "not ") || strings.HasPrefix(state, "error") { + return false, nil + } + + return strings.HasPrefix(state, "installed") || strings.HasPrefix(state, "running"), nil } func PromptInstallBackend(backend string, cmd *cobra.Command) (bool, error) { @@ -343,6 +348,14 @@ func EnsureBackendAvailable(backend string, cmd *cobra.Command) error { return err } + installed, err = CheckBackendInstalled(backend) + if err != nil { + return err + } + if !installed { + return fmt.Errorf("backend %q is still not installed; run 'docker model install-runner --backend %s'", backend, backend) + } + cmd.Printf("Backend %q installed successfully.\n", backend) return nil }