Browse Source

use parser.Format instead of templating modelfile

Michael Yang 1 year ago
parent
commit
9cf0f2e973
3 changed files with 54 additions and 75 deletions
  1. 6 7
      parser/parser.go
  2. 42 62
      server/images.go
  3. 6 6
      server/routes.go

+ 6 - 7
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

+ 42 - 62
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"})

+ 6 - 6
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
 }