-
Notifications
You must be signed in to change notification settings - Fork 79
Add model name normalization and display stripping #240
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -15,3 +15,4 @@ vendor/ | |
| # model-distribution | ||
| pkg/distribution/bin/ | ||
| /parallelget | ||
| /cli | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -10,6 +10,11 @@ import ( | |
| "github.com/pkg/errors" | ||
| ) | ||
|
|
||
| const ( | ||
| defaultOrg = "ai" | ||
| defaultTag = "latest" | ||
| ) | ||
|
|
||
| const ( | ||
| enableViaCLI = "Enable Docker Model Runner via the CLI → docker desktop enable model-runner" | ||
| enableViaGUI = "Enable Docker Model Runner via the GUI → Go to Settings->AI->Enable Docker Model Runner" | ||
|
|
@@ -32,3 +37,25 @@ func handleNotRunningError(err error) error { | |
| } | ||
| return err | ||
| } | ||
|
|
||
| // stripDefaultsFromModelName removes the default "ai/" prefix and ":latest" tag for display. | ||
| // Examples: | ||
| // - "ai/gemma3:latest" -> "gemma3" | ||
| // - "ai/gemma3:v1" -> "ai/gemma3:v1" | ||
| // - "myorg/gemma3:latest" -> "myorg/gemma3" | ||
| // - "gemma3:latest" -> "gemma3" | ||
| // - "hf.co/bartowski/model:latest" -> "hf.co/bartowski/model" | ||
| func stripDefaultsFromModelName(model string) string { | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: Maybe this should be implemented in the model manager along with
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good consideration, I thought of that at one point, might do it in a follow on PR. The reason it's not is one has wider use than the other. But I get your point they are similar, one does the opposite of the other. I actually think it's a pity we didn't go lowercase for our dmr quantization sometimes, the :tag . We actually have code here that converts everything to lowercase for huggingface and it's generally what ollama does also. |
||
| // Check if model has ai/ prefix without tag (implicitly :latest) - strip just ai/ | ||
| if strings.HasPrefix(model, defaultOrg+"/") { | ||
| model = strings.TrimPrefix(model, defaultOrg+"/") | ||
| } | ||
|
|
||
| // Check if model has :latest but no slash (no org specified) - strip :latest | ||
| if strings.HasSuffix(model, ":"+defaultTag) { | ||
| model = strings.TrimSuffix(model, ":"+defaultTag) | ||
| } | ||
|
|
||
| // For other cases (ai/ with custom tag, custom org with :latest, etc.), keep as-is | ||
| return model | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,148 @@ | ||
| package commands | ||
|
|
||
| import ( | ||
| "testing" | ||
|
|
||
| "github.com/docker/model-runner/pkg/inference/models" | ||
| ) | ||
|
|
||
| func TestNormalizeModelName(t *testing.T) { | ||
| tests := []struct { | ||
| name string | ||
| input string | ||
| expected string | ||
| }{ | ||
| { | ||
| name: "simple model name", | ||
| input: "gemma3", | ||
| expected: "ai/gemma3:latest", | ||
| }, | ||
| { | ||
| name: "model name with tag", | ||
| input: "gemma3:v1", | ||
| expected: "ai/gemma3:v1", | ||
| }, | ||
| { | ||
| name: "model name with org", | ||
| input: "myorg/gemma3", | ||
| expected: "myorg/gemma3:latest", | ||
| }, | ||
| { | ||
| name: "model name with org and tag", | ||
| input: "myorg/gemma3:v1", | ||
| expected: "myorg/gemma3:v1", | ||
| }, | ||
| { | ||
| name: "fully qualified model name", | ||
| input: "ai/gemma3:latest", | ||
| expected: "ai/gemma3:latest", | ||
| }, | ||
| { | ||
| name: "huggingface model", | ||
| input: "hf.co/bartowski/model", | ||
| expected: "hf.co/bartowski/model:latest", | ||
| }, | ||
| { | ||
| name: "huggingface model with tag", | ||
| input: "hf.co/bartowski/model:Q4_K_S", | ||
| expected: "hf.co/bartowski/model:q4_k_s", | ||
| }, | ||
| { | ||
| name: "registry with model", | ||
| input: "docker.io/library/model", | ||
| expected: "docker.io/library/model:latest", | ||
| }, | ||
| { | ||
| name: "registry with model and tag", | ||
| input: "docker.io/library/model:v1", | ||
| expected: "docker.io/library/model:v1", | ||
| }, | ||
| { | ||
| name: "empty string", | ||
| input: "", | ||
| expected: "", | ||
| }, | ||
| { | ||
| name: "ai prefix already present", | ||
| input: "ai/gemma3", | ||
| expected: "ai/gemma3:latest", | ||
| }, | ||
| { | ||
| name: "model name with latest tag already", | ||
| input: "gemma3:latest", | ||
| expected: "ai/gemma3:latest", | ||
| }, | ||
| } | ||
|
|
||
| for _, tt := range tests { | ||
| t.Run(tt.name, func(t *testing.T) { | ||
| result := models.NormalizeModelName(tt.input) | ||
| if result != tt.expected { | ||
| t.Errorf("NormalizeModelName(%q) = %q, want %q", tt.input, result, tt.expected) | ||
| } | ||
| }) | ||
| } | ||
| } | ||
|
|
||
| func TestStripDefaultsFromModelName(t *testing.T) { | ||
| tests := []struct { | ||
| name string | ||
| input string | ||
| expected string | ||
| }{ | ||
| { | ||
| name: "ai prefix and latest tag", | ||
| input: "ai/gemma3:latest", | ||
| expected: "gemma3", | ||
| }, | ||
| { | ||
| name: "ai prefix with custom tag", | ||
| input: "ai/gemma3:v1", | ||
| expected: "gemma3:v1", | ||
| }, | ||
| { | ||
| name: "custom org with latest tag", | ||
| input: "myorg/gemma3:latest", | ||
| expected: "myorg/gemma3", | ||
| }, | ||
| { | ||
| name: "simple model name with latest", | ||
| input: "gemma3:latest", | ||
| expected: "gemma3", | ||
| }, | ||
| { | ||
| name: "simple model name without tag", | ||
| input: "gemma3", | ||
| expected: "gemma3", | ||
| }, | ||
| { | ||
| name: "ai prefix without tag", | ||
| input: "ai/gemma3", | ||
| expected: "gemma3", | ||
| }, | ||
| { | ||
| name: "huggingface model with latest", | ||
| input: "hf.co/bartowski/model:latest", | ||
| expected: "hf.co/bartowski/model", | ||
| }, | ||
| { | ||
| name: "huggingface model with custom tag", | ||
| input: "hf.co/bartowski/model:Q4_K_S", | ||
| expected: "hf.co/bartowski/model:Q4_K_S", | ||
| }, | ||
| { | ||
| name: "empty string", | ||
| input: "", | ||
| expected: "", | ||
| }, | ||
| } | ||
|
|
||
| for _, tt := range tests { | ||
| t.Run(tt.name, func(t *testing.T) { | ||
| result := stripDefaultsFromModelName(tt.input) | ||
| if result != tt.expected { | ||
| t.Errorf("stripDefaultsFromModelName(%q) = %q, want %q", tt.input, result, tt.expected) | ||
| } | ||
| }) | ||
| } | ||
| } |
Uh oh!
There was an error while loading. Please reload this page.