modelfiles: fix /save command and add shortname for safetensors based models (#15413)

This change fixes two issues with Modelfiles:

  1. If a user uses `ollama show --modelfile` to show a safetensors based
     model, the Model would leave the "FROM" field blank which won't allow
     a user to recreate the model. This change adds the model's current
     canonical short name to the FROM field.
  2. If a user uses the `/save` command in the CLI any messages which were
     saved in a previous model wouldn't get saved (only the set of messages
     from the current session).
This commit is contained in:
Patrick Devine
2026-04-08 21:05:39 -07:00
committed by GitHub
parent 6b5db12aa2
commit eb97274e5c
5 changed files with 179 additions and 45 deletions

View File

@@ -1308,9 +1308,17 @@ func GetModelInfo(req api.ShowRequest) (*api.ShowResponse, error) {
var sb strings.Builder
fmt.Fprintln(&sb, "# Modelfile generated by \"ollama show\"")
fmt.Fprintln(&sb, "# To build a new Modelfile based on this, replace FROM with:")
fmt.Fprintf(&sb, "# FROM %s\n\n", m.ShortName)
fmt.Fprint(&sb, m.String())
modelfile := m.String()
if m.IsMLX() {
fmt.Fprintf(&sb, "FROM %s\n", m.ShortName)
if _, rest, ok := strings.Cut(modelfile, "\n"); ok {
fmt.Fprint(&sb, rest)
}
} else {
fmt.Fprintln(&sb, "# To build a new Modelfile based on this, replace FROM with:")
fmt.Fprintf(&sb, "# FROM %s\n\n", m.ShortName)
fmt.Fprint(&sb, modelfile)
}
resp.Modelfile = sb.String()
// skip loading tensor information if this is a remote model

View File

@@ -580,6 +580,41 @@ func TestGetModelInfo_SafetensorsUsesStoredFileType(t *testing.T) {
}
}
func TestGetModelInfo_SafetensorsModelfileUsesShortName(t *testing.T) {
t.Setenv("OLLAMA_MODELS", t.TempDir())
cfgData, err := json.Marshal(model.ConfigV2{
ModelFormat: "safetensors",
Capabilities: []string{"completion"},
})
if err != nil {
t.Fatalf("failed to marshal config: %v", err)
}
configLayer, err := manifest.NewLayer(bytes.NewReader(cfgData), "application/vnd.docker.container.image.v1+json")
if err != nil {
t.Fatalf("failed to create config layer: %v", err)
}
name := model.ParseName("show-safetensors")
if err := manifest.WriteManifest(name, configLayer, nil); err != nil {
t.Fatalf("failed to write manifest: %v", err)
}
resp, err := GetModelInfo(api.ShowRequest{Model: name.String()})
if err != nil {
t.Fatalf("GetModelInfo() error = %v", err)
}
if !strings.Contains(resp.Modelfile, "FROM show-safetensors:latest\n") {
t.Fatalf("Modelfile = %q, want FROM show-safetensors:latest", resp.Modelfile)
}
if strings.Contains(resp.Modelfile, "# To build a new Modelfile based on this, replace FROM with:") {
t.Fatalf("Modelfile should not include replacement hint: %q", resp.Modelfile)
}
}
func casingShuffle(s string) string {
rr := []rune(s)
for i := range rr {