diff --git a/parser/parser.go b/parser/parser.go index 22e07235..6c451e99 100644 --- a/parser/parser.go +++ b/parser/parser.go @@ -32,7 +32,7 @@ var ( ) func Format(cmds []Command) string { - var b bytes.Buffer + var sb strings.Builder for _, cmd := range cmds { name := cmd.Name args := cmd.Args @@ -43,19 +43,18 @@ func Format(cmds []Command) string { args = cmd.Args case "license", "template", "system", "adapter": args = quote(args) - // pass case "message": role, message, _ := strings.Cut(cmd.Args, ": ") args = role + " " + quote(message) default: name = "parameter" - args = cmd.Name + " " + cmd.Args + args = cmd.Name + " " + quote(cmd.Args) } - fmt.Fprintln(&b, strings.ToUpper(name), args) + fmt.Fprintln(&sb, strings.ToUpper(name), args) } - return b.String() + return sb.String() } func Parse(r io.Reader) (cmds []Command, err error) { @@ -225,12 +224,12 @@ func parseRuneForState(r rune, cs state) (state, rune, error) { } func quote(s string) string { - if strings.Contains(s, "\n") || strings.HasSuffix(s, " ") { + if strings.Contains(s, "\n") || strings.HasPrefix(s, " ") || strings.HasSuffix(s, " ") { if strings.Contains(s, "\"") { return `"""` + s + `"""` } - return strconv.Quote(s) + return `"` + s + `"` } return s diff --git a/server/images.go b/server/images.go index 4e4107f7..68840c1a 100644 --- a/server/images.go +++ b/server/images.go @@ -21,7 +21,6 @@ import ( "runtime" "strconv" "strings" - "text/template" "golang.org/x/exp/slices" @@ -64,6 +63,48 @@ func (m *Model) IsEmbedding() bool { return slices.Contains(m.Config.ModelFamilies, "bert") || slices.Contains(m.Config.ModelFamilies, "nomic-bert") } +func (m *Model) Commands() (cmds []parser.Command) { + cmds = append(cmds, parser.Command{Name: "model", Args: m.ModelPath}) + + if m.Template != "" { + cmds = append(cmds, parser.Command{Name: "template", Args: m.Template}) + } + + if m.System != "" { + cmds = append(cmds, parser.Command{Name: "system", Args: m.System}) + } + + for _, adapter := range m.AdapterPaths { + cmds = append(cmds, parser.Command{Name: "adapter", Args: adapter}) + } + + for _, projector := range m.ProjectorPaths { + cmds = append(cmds, parser.Command{Name: "projector", Args: projector}) + } + + for k, v := range m.Options { + switch v := v.(type) { + case []any: + for _, s := range v { + cmds = append(cmds, parser.Command{Name: k, Args: fmt.Sprintf("%v", s)}) + } + default: + cmds = append(cmds, parser.Command{Name: k, Args: fmt.Sprintf("%v", v)}) + } + } + + for _, license := range m.License { + cmds = append(cmds, parser.Command{Name: "license", Args: license}) + } + + for _, msg := range m.Messages { + cmds = append(cmds, parser.Command{Name: "message", Args: fmt.Sprintf("%s %s", msg.Role, msg.Content)}) + } + + return cmds + +} + type Message struct { Role string `json:"role"` Content string `json:"content"` @@ -901,67 +942,6 @@ func DeleteModel(name string) error { return nil } -func ShowModelfile(model *Model) (string, error) { - var mt struct { - *Model - From string - Parameters map[string][]any - } - - mt.Parameters = make(map[string][]any) - for k, v := range model.Options { - if s, ok := v.([]any); ok { - mt.Parameters[k] = s - continue - } - - mt.Parameters[k] = []any{v} - } - - mt.Model = model - mt.From = model.ModelPath - - if model.ParentModel != "" { - mt.From = model.ParentModel - } - - modelFile := `# Modelfile generated by "ollama show" -# To build a new Modelfile based on this one, replace the FROM line with: -# FROM {{ .ShortName }} - -FROM {{ .From }} -TEMPLATE """{{ .Template }}""" - -{{- if .System }} -SYSTEM """{{ .System }}""" -{{- end }} - -{{- range $adapter := .AdapterPaths }} -ADAPTER {{ $adapter }} -{{- end }} - -{{- range $k, $v := .Parameters }} -{{- range $parameter := $v }} -PARAMETER {{ $k }} {{ printf "%#v" $parameter }} -{{- end }} -{{- end }}` - - tmpl, err := template.New("").Parse(modelFile) - if err != nil { - slog.Info(fmt.Sprintf("error parsing template: %q", err)) - return "", err - } - - var buf bytes.Buffer - - if err = tmpl.Execute(&buf, mt); err != nil { - slog.Info(fmt.Sprintf("error executing template: %q", err)) - return "", err - } - - return buf.String(), nil -} - func PushModel(ctx context.Context, name string, regOpts *registryOptions, fn func(api.ProgressResponse)) error { mp := ParseModelPath(name) fn(api.ProgressResponse{Status: "retrieving manifest"}) diff --git a/server/routes.go b/server/routes.go index b1962d23..35b20f56 100644 --- a/server/routes.go +++ b/server/routes.go @@ -728,12 +728,12 @@ func GetModelInfo(req api.ShowRequest) (*api.ShowResponse, error) { } } - mf, err := ShowModelfile(model) - if err != nil { - return nil, err - } - - resp.Modelfile = mf + var sb strings.Builder + fmt.Fprintln(&sb, "# Modelfile generate by \"ollama show\"") + fmt.Fprintln(&sb, "# To build a new Modelfile based on this, replace FROM with:") + fmt.Fprintf(&sb, "# FROM %s\n\n", model.ShortName) + fmt.Fprint(&sb, parser.Format(model.Commands())) + resp.Modelfile = sb.String() return resp, nil }