add show command (#474)
This commit is contained in:
parent
7de300856b
commit
790d24eb7b
5 changed files with 299 additions and 50 deletions
|
@ -255,6 +255,14 @@ func (c *Client) Delete(ctx context.Context, req *DeleteRequest) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *Client) Show(ctx context.Context, req *ShowRequest) (*ShowResponse, error) {
|
||||||
|
var resp ShowResponse
|
||||||
|
if err := c.do(ctx, http.MethodPost, "/api/show", req, &resp); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &resp, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (c *Client) Heartbeat(ctx context.Context) error {
|
func (c *Client) Heartbeat(ctx context.Context) error {
|
||||||
if err := c.do(ctx, http.MethodHead, "/", nil, nil); err != nil {
|
if err := c.do(ctx, http.MethodHead, "/", nil, nil); err != nil {
|
||||||
return err
|
return err
|
||||||
|
|
12
api/types.go
12
api/types.go
|
@ -61,6 +61,18 @@ type DeleteRequest struct {
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ShowRequest struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ShowResponse struct {
|
||||||
|
License string `json:"license,omitempty"`
|
||||||
|
Modelfile string `json:"modelfile,omitempty"`
|
||||||
|
Parameters string `json:"parameters,omitempty"`
|
||||||
|
Template string `json:"template,omitempty"`
|
||||||
|
System string `json:"system,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
type CopyRequest struct {
|
type CopyRequest struct {
|
||||||
Source string `json:"source"`
|
Source string `json:"source"`
|
||||||
Destination string `json:"destination"`
|
Destination string `json:"destination"`
|
||||||
|
|
149
cmd/cmd.go
149
cmd/cmd.go
|
@ -230,6 +230,84 @@ func DeleteHandler(cmd *cobra.Command, args []string) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func ShowHandler(cmd *cobra.Command, args []string) error {
|
||||||
|
client, err := api.FromEnv()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(args) != 1 {
|
||||||
|
return errors.New("missing model name")
|
||||||
|
}
|
||||||
|
|
||||||
|
license, errLicense := cmd.Flags().GetBool("license")
|
||||||
|
modelfile, errModelfile := cmd.Flags().GetBool("modelfile")
|
||||||
|
parameters, errParams := cmd.Flags().GetBool("parameters")
|
||||||
|
system, errSystem := cmd.Flags().GetBool("system")
|
||||||
|
template, errTemplate := cmd.Flags().GetBool("template")
|
||||||
|
|
||||||
|
for _, boolErr := range []error{errLicense, errModelfile, errParams, errSystem, errTemplate} {
|
||||||
|
if boolErr != nil {
|
||||||
|
return errors.New("error retrieving flags")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
flagsSet := 0
|
||||||
|
showType := ""
|
||||||
|
|
||||||
|
if license {
|
||||||
|
flagsSet++
|
||||||
|
showType = "license"
|
||||||
|
}
|
||||||
|
|
||||||
|
if modelfile {
|
||||||
|
flagsSet++
|
||||||
|
showType = "modelfile"
|
||||||
|
}
|
||||||
|
|
||||||
|
if parameters {
|
||||||
|
flagsSet++
|
||||||
|
showType = "parameters"
|
||||||
|
}
|
||||||
|
|
||||||
|
if system {
|
||||||
|
flagsSet++
|
||||||
|
showType = "system"
|
||||||
|
}
|
||||||
|
|
||||||
|
if template {
|
||||||
|
flagsSet++
|
||||||
|
showType = "template"
|
||||||
|
}
|
||||||
|
|
||||||
|
if flagsSet > 1 {
|
||||||
|
return errors.New("only one of 'license', 'modelfile', 'parameters', 'system', or 'template' can be set")
|
||||||
|
} else if flagsSet == 0 {
|
||||||
|
return errors.New("one of 'license', 'modelfile', 'parameters', 'system', or 'template' must be set")
|
||||||
|
}
|
||||||
|
|
||||||
|
req := api.ShowRequest{Name: args[0]}
|
||||||
|
resp, err := client.Show(context.Background(), &req)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
switch showType {
|
||||||
|
case "license":
|
||||||
|
fmt.Println(resp.License)
|
||||||
|
case "modelfile":
|
||||||
|
fmt.Println(resp.Modelfile)
|
||||||
|
case "parameters":
|
||||||
|
fmt.Println(resp.Parameters)
|
||||||
|
case "system":
|
||||||
|
fmt.Println(resp.System)
|
||||||
|
case "template":
|
||||||
|
fmt.Println(resp.Template)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func CopyHandler(cmd *cobra.Command, args []string) error {
|
func CopyHandler(cmd *cobra.Command, args []string) error {
|
||||||
client, err := api.FromEnv()
|
client, err := api.FromEnv()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -377,20 +455,6 @@ func generate(cmd *cobra.Command, model, prompt string) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func showLayer(l *server.Layer) {
|
|
||||||
filename, err := server.GetBlobsPath(l.Digest)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println("Couldn't get layer's path")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
bts, err := os.ReadFile(filename)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println("Couldn't read layer")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
fmt.Println(string(bts))
|
|
||||||
}
|
|
||||||
|
|
||||||
func generateInteractive(cmd *cobra.Command, model string) error {
|
func generateInteractive(cmd *cobra.Command, model string) error {
|
||||||
home, err := os.UserHomeDir()
|
home, err := os.UserHomeDir()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -413,6 +477,8 @@ func generateInteractive(cmd *cobra.Command, model string) error {
|
||||||
),
|
),
|
||||||
readline.PcItem("/show",
|
readline.PcItem("/show",
|
||||||
readline.PcItem("license"),
|
readline.PcItem("license"),
|
||||||
|
readline.PcItem("modelfile"),
|
||||||
|
readline.PcItem("parameters"),
|
||||||
readline.PcItem("system"),
|
readline.PcItem("system"),
|
||||||
readline.PcItem("template"),
|
readline.PcItem("template"),
|
||||||
),
|
),
|
||||||
|
@ -522,42 +588,28 @@ func generateInteractive(cmd *cobra.Command, model string) error {
|
||||||
case strings.HasPrefix(line, "/show"):
|
case strings.HasPrefix(line, "/show"):
|
||||||
args := strings.Fields(line)
|
args := strings.Fields(line)
|
||||||
if len(args) > 1 {
|
if len(args) > 1 {
|
||||||
mp := server.ParseModelPath(model)
|
resp, err := server.GetModelInfo(model)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
fmt.Println("error: couldn't get model")
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
manifest, _, err := server.GetManifest(mp)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println("error: couldn't get a manifest for this model")
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
switch args[1] {
|
switch args[1] {
|
||||||
case "license":
|
case "license":
|
||||||
for _, l := range manifest.Layers {
|
fmt.Println(resp.License)
|
||||||
if l.MediaType == "application/vnd.ollama.image.license" {
|
case "modelfile":
|
||||||
showLayer(l)
|
fmt.Println(resp.Modelfile)
|
||||||
}
|
case "parameters":
|
||||||
}
|
fmt.Println(resp.Parameters)
|
||||||
continue
|
|
||||||
case "system":
|
case "system":
|
||||||
for _, l := range manifest.Layers {
|
fmt.Println(resp.System)
|
||||||
if l.MediaType == "application/vnd.ollama.image.system" {
|
|
||||||
showLayer(l)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
case "template":
|
case "template":
|
||||||
for _, l := range manifest.Layers {
|
fmt.Println(resp.Template)
|
||||||
if l.MediaType == "application/vnd.ollama.image.template" {
|
|
||||||
showLayer(l)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
default:
|
default:
|
||||||
usage()
|
fmt.Println("error: unknown command")
|
||||||
continue
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
continue
|
||||||
} else {
|
} else {
|
||||||
usage()
|
usage()
|
||||||
continue
|
continue
|
||||||
|
@ -749,6 +801,20 @@ func NewCLI() *cobra.Command {
|
||||||
|
|
||||||
createCmd.Flags().StringP("file", "f", "Modelfile", "Name of the Modelfile (default \"Modelfile\")")
|
createCmd.Flags().StringP("file", "f", "Modelfile", "Name of the Modelfile (default \"Modelfile\")")
|
||||||
|
|
||||||
|
showCmd := &cobra.Command{
|
||||||
|
Use: "show MODEL",
|
||||||
|
Short: "Show information for a model",
|
||||||
|
Args: cobra.MinimumNArgs(1),
|
||||||
|
PreRunE: checkServerHeartbeat,
|
||||||
|
RunE: ShowHandler,
|
||||||
|
}
|
||||||
|
|
||||||
|
showCmd.Flags().Bool("license", false, "Show license of a model")
|
||||||
|
showCmd.Flags().Bool("modelfile", false, "Show Modelfile of a model")
|
||||||
|
showCmd.Flags().Bool("parameters", false, "Show parameters of a model")
|
||||||
|
showCmd.Flags().Bool("template", false, "Show template of a model")
|
||||||
|
showCmd.Flags().Bool("system", false, "Show system prompt of a model")
|
||||||
|
|
||||||
runCmd := &cobra.Command{
|
runCmd := &cobra.Command{
|
||||||
Use: "run MODEL [PROMPT]",
|
Use: "run MODEL [PROMPT]",
|
||||||
Short: "Run a model",
|
Short: "Run a model",
|
||||||
|
@ -814,6 +880,7 @@ func NewCLI() *cobra.Command {
|
||||||
rootCmd.AddCommand(
|
rootCmd.AddCommand(
|
||||||
serveCmd,
|
serveCmd,
|
||||||
createCmd,
|
createCmd,
|
||||||
|
showCmd,
|
||||||
runCmd,
|
runCmd,
|
||||||
pullCmd,
|
pullCmd,
|
||||||
pushCmd,
|
pushCmd,
|
||||||
|
|
107
server/images.go
107
server/images.go
|
@ -41,15 +41,18 @@ type RegistryOptions struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
type Model struct {
|
type Model struct {
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
ModelPath string
|
ShortName string
|
||||||
AdapterPaths []string
|
ModelPath string
|
||||||
Template string
|
OriginalModel string
|
||||||
System string
|
AdapterPaths []string
|
||||||
Digest string
|
Template string
|
||||||
ConfigDigest string
|
System string
|
||||||
Options map[string]interface{}
|
License []string
|
||||||
Embeddings []vector.Embedding
|
Digest string
|
||||||
|
ConfigDigest string
|
||||||
|
Options map[string]interface{}
|
||||||
|
Embeddings []vector.Embedding
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Model) Prompt(request api.GenerateRequest, embedding string) (string, error) {
|
func (m *Model) Prompt(request api.GenerateRequest, embedding string) (string, error) {
|
||||||
|
@ -171,9 +174,11 @@ func GetModel(name string) (*Model, error) {
|
||||||
|
|
||||||
model := &Model{
|
model := &Model{
|
||||||
Name: mp.GetFullTagname(),
|
Name: mp.GetFullTagname(),
|
||||||
|
ShortName: mp.GetShortTagname(),
|
||||||
Digest: digest,
|
Digest: digest,
|
||||||
ConfigDigest: manifest.Config.Digest,
|
ConfigDigest: manifest.Config.Digest,
|
||||||
Template: "{{ .Prompt }}",
|
Template: "{{ .Prompt }}",
|
||||||
|
License: []string{},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, layer := range manifest.Layers {
|
for _, layer := range manifest.Layers {
|
||||||
|
@ -185,6 +190,7 @@ func GetModel(name string) (*Model, error) {
|
||||||
switch layer.MediaType {
|
switch layer.MediaType {
|
||||||
case "application/vnd.ollama.image.model":
|
case "application/vnd.ollama.image.model":
|
||||||
model.ModelPath = filename
|
model.ModelPath = filename
|
||||||
|
model.OriginalModel = layer.From
|
||||||
case "application/vnd.ollama.image.embed":
|
case "application/vnd.ollama.image.embed":
|
||||||
file, err := os.Open(filename)
|
file, err := os.Open(filename)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -229,6 +235,12 @@ func GetModel(name string) (*Model, error) {
|
||||||
if err = json.NewDecoder(params).Decode(&model.Options); err != nil {
|
if err = json.NewDecoder(params).Decode(&model.Options); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
case "application/vnd.ollama.image.license":
|
||||||
|
bts, err := os.ReadFile(filename)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
model.License = append(model.License, string(bts))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -933,6 +945,83 @@ func DeleteModel(name string) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func ShowModelfile(model *Model) (string, error) {
|
||||||
|
type modelTemplate struct {
|
||||||
|
*Model
|
||||||
|
From string
|
||||||
|
Params string
|
||||||
|
}
|
||||||
|
|
||||||
|
var params []string
|
||||||
|
for k, v := range model.Options {
|
||||||
|
switch val := v.(type) {
|
||||||
|
case string:
|
||||||
|
params = append(params, fmt.Sprintf("PARAMETER %s %s", k, val))
|
||||||
|
case int:
|
||||||
|
params = append(params, fmt.Sprintf("PARAMETER %s %s", k, strconv.Itoa(val)))
|
||||||
|
case float64:
|
||||||
|
params = append(params, fmt.Sprintf("PARAMETER %s %s", k, strconv.FormatFloat(val, 'f', 0, 64)))
|
||||||
|
case bool:
|
||||||
|
params = append(params, fmt.Sprintf("PARAMETER %s %s", k, strconv.FormatBool(val)))
|
||||||
|
case []interface{}:
|
||||||
|
for _, nv := range val {
|
||||||
|
switch nval := nv.(type) {
|
||||||
|
case string:
|
||||||
|
params = append(params, fmt.Sprintf("PARAMETER %s %s", k, nval))
|
||||||
|
case int:
|
||||||
|
params = append(params, fmt.Sprintf("PARAMETER %s %s", k, strconv.Itoa(nval)))
|
||||||
|
case float64:
|
||||||
|
params = append(params, fmt.Sprintf("PARAMETER %s %s", k, strconv.FormatFloat(nval, 'f', 0, 64)))
|
||||||
|
case bool:
|
||||||
|
params = append(params, fmt.Sprintf("PARAMETER %s %s", k, strconv.FormatBool(nval)))
|
||||||
|
default:
|
||||||
|
log.Printf("unknown type: %s", reflect.TypeOf(nv).String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
log.Printf("unknown type: %s", reflect.TypeOf(v).String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mt := modelTemplate{
|
||||||
|
Model: model,
|
||||||
|
From: model.OriginalModel,
|
||||||
|
Params: strings.Join(params, "\n"),
|
||||||
|
}
|
||||||
|
|
||||||
|
if mt.From == "" {
|
||||||
|
mt.From = model.ModelPath
|
||||||
|
}
|
||||||
|
|
||||||
|
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 }}"""
|
||||||
|
SYSTEM """{{ .System }}"""
|
||||||
|
{{ .Params }}
|
||||||
|
`
|
||||||
|
for _, l := range mt.Model.AdapterPaths {
|
||||||
|
modelFile += fmt.Sprintf("ADAPTER %s\n", l)
|
||||||
|
}
|
||||||
|
|
||||||
|
tmpl, err := template.New("").Parse(modelFile)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("error parsing template: %q", err)
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
var buf bytes.Buffer
|
||||||
|
|
||||||
|
if err = tmpl.Execute(&buf, mt); err != nil {
|
||||||
|
log.Printf("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 {
|
func PushModel(ctx context.Context, name string, regOpts *RegistryOptions, fn func(api.ProgressResponse)) error {
|
||||||
mp := ParseModelPath(name)
|
mp := ParseModelPath(name)
|
||||||
fn(api.ProgressResponse{Status: "retrieving manifest"})
|
fn(api.ProgressResponse{Status: "retrieving manifest"})
|
||||||
|
|
|
@ -12,6 +12,7 @@ import (
|
||||||
"os/signal"
|
"os/signal"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
@ -364,6 +365,77 @@ func DeleteModelHandler(c *gin.Context) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func ShowModelHandler(c *gin.Context) {
|
||||||
|
var req api.ShowRequest
|
||||||
|
if err := c.ShouldBindJSON(&req); err != nil {
|
||||||
|
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := GetModelInfo(req.Name)
|
||||||
|
if err != nil {
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
c.JSON(http.StatusNotFound, gin.H{"error": fmt.Sprintf("model '%s' not found", req.Name)})
|
||||||
|
} else {
|
||||||
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, resp)
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetModelInfo(name string) (*api.ShowResponse, error) {
|
||||||
|
model, err := GetModel(name)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
resp := &api.ShowResponse{
|
||||||
|
License: strings.Join(model.License, "\n"),
|
||||||
|
System: model.System,
|
||||||
|
Template: model.Template,
|
||||||
|
}
|
||||||
|
|
||||||
|
mf, err := ShowModelfile(model)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
resp.Modelfile = mf
|
||||||
|
|
||||||
|
var params []string
|
||||||
|
cs := 30
|
||||||
|
for k, v := range model.Options {
|
||||||
|
switch val := v.(type) {
|
||||||
|
case string:
|
||||||
|
params = append(params, fmt.Sprintf("%-*s %s", cs, k, val))
|
||||||
|
case int:
|
||||||
|
params = append(params, fmt.Sprintf("%-*s %s", cs, k, strconv.Itoa(val)))
|
||||||
|
case float64:
|
||||||
|
params = append(params, fmt.Sprintf("%-*s %s", cs, k, strconv.FormatFloat(val, 'f', 0, 64)))
|
||||||
|
case bool:
|
||||||
|
params = append(params, fmt.Sprintf("%-*s %s", cs, k, strconv.FormatBool(val)))
|
||||||
|
case []interface{}:
|
||||||
|
for _, nv := range val {
|
||||||
|
switch nval := nv.(type) {
|
||||||
|
case string:
|
||||||
|
params = append(params, fmt.Sprintf("%-*s %s", cs, k, nval))
|
||||||
|
case int:
|
||||||
|
params = append(params, fmt.Sprintf("%-*s %s", cs, k, strconv.Itoa(nval)))
|
||||||
|
case float64:
|
||||||
|
params = append(params, fmt.Sprintf("%-*s %s", cs, k, strconv.FormatFloat(nval, 'f', 0, 64)))
|
||||||
|
case bool:
|
||||||
|
params = append(params, fmt.Sprintf("%-*s %s", cs, k, strconv.FormatBool(nval)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
resp.Parameters = strings.Join(params, "\n")
|
||||||
|
|
||||||
|
return resp, nil
|
||||||
|
}
|
||||||
|
|
||||||
func ListModelsHandler(c *gin.Context) {
|
func ListModelsHandler(c *gin.Context) {
|
||||||
var models []api.ModelResponse
|
var models []api.ModelResponse
|
||||||
fp, err := GetManifestPath()
|
fp, err := GetManifestPath()
|
||||||
|
@ -457,6 +529,7 @@ func Serve(ln net.Listener, origins []string) error {
|
||||||
r.POST("/api/copy", CopyModelHandler)
|
r.POST("/api/copy", CopyModelHandler)
|
||||||
r.GET("/api/tags", ListModelsHandler)
|
r.GET("/api/tags", ListModelsHandler)
|
||||||
r.DELETE("/api/delete", DeleteModelHandler)
|
r.DELETE("/api/delete", DeleteModelHandler)
|
||||||
|
r.POST("/api/show", ShowModelHandler)
|
||||||
|
|
||||||
log.Printf("Listening on %s", ln.Addr())
|
log.Printf("Listening on %s", ln.Addr())
|
||||||
s := &http.Server{
|
s := &http.Server{
|
||||||
|
|
Loading…
Reference in a new issue