cmd.go 10.9 KB
Newer Older
Jeffrey Morgan's avatar
Jeffrey Morgan committed
1
2
3
package cmd

import (
Michael Yang's avatar
Michael Yang committed
4
	"bufio"
Jeffrey Morgan's avatar
Jeffrey Morgan committed
5
	"context"
Michael Yang's avatar
Michael Yang committed
6
	"errors"
Bruce MacDonald's avatar
Bruce MacDonald committed
7
	"fmt"
Michael Yang's avatar
Michael Yang committed
8
	"io"
Jeffrey Morgan's avatar
Jeffrey Morgan committed
9
10
	"log"
	"net"
Michael Yang's avatar
Michael Yang committed
11
	"net/http"
Jeffrey Morgan's avatar
Jeffrey Morgan committed
12
	"os"
13
	"path/filepath"
Michael Yang's avatar
Michael Yang committed
14
	"strings"
Michael Yang's avatar
Michael Yang committed
15
	"time"
Jeffrey Morgan's avatar
Jeffrey Morgan committed
16

Michael Yang's avatar
Michael Yang committed
17
	"github.com/chzyer/readline"
Patrick Devine's avatar
Patrick Devine committed
18
19
	"github.com/dustin/go-humanize"
	"github.com/olekukonko/tablewriter"
Michael Yang's avatar
Michael Yang committed
20
21
	"github.com/spf13/cobra"

Jeffrey Morgan's avatar
Jeffrey Morgan committed
22
	"github.com/jmorganca/ollama/api"
Patrick Devine's avatar
Patrick Devine committed
23
	"github.com/jmorganca/ollama/format"
24
	"github.com/jmorganca/ollama/parser"
25
	"github.com/jmorganca/ollama/progressbar"
Jeffrey Morgan's avatar
Jeffrey Morgan committed
26
	"github.com/jmorganca/ollama/server"
Jeffrey Morgan's avatar
Jeffrey Morgan committed
27
28
)

29
func CreateHandler(cmd *cobra.Command, args []string) error {
30
	filename, _ := cmd.Flags().GetString("file")
31
32
33
34
35
	filename, err := filepath.Abs(filename)
	if err != nil {
		return err
	}

36
37
	client := api.NewClient()

Michael Yang's avatar
Michael Yang committed
38
39
	var spinner *Spinner

40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
	// pull the model file if needed
	mf, err := os.Open(filename)
	defer mf.Close()
	cmds, err := parser.Parse(mf)
	if err != nil {
		return err
	}
	mf.Close()
	for _, c := range cmds {
		if c.Name == "model" {
			// check if the model file needs to be pulled
			checkPull(c.Args)
		}
	}
	if err != nil {
		return err
	}

58
59
	request := api.CreateRequest{Name: args[0], Path: filename}
	fn := func(resp api.CreateProgress) error {
Michael Yang's avatar
Michael Yang committed
60
61
62
63
64
65
66
		if spinner != nil {
			spinner.Stop()
		}

		spinner = NewSpinner(resp.Status)
		go spinner.Spin(100 * time.Millisecond)

67
68
69
70
71
72
73
		return nil
	}

	if err := client.Create(context.Background(), &request, fn); err != nil {
		return err
	}

Michael Yang's avatar
Michael Yang committed
74
75
76
77
	if spinner != nil {
		spinner.Stop()
	}

78
79
80
	return nil
}

81
82
func checkPull(model string) error {
	mp := server.ParseModelPath(model)
Patrick Devine's avatar
Patrick Devine committed
83
84
85
86
87
88
	fp, err := mp.GetManifestPath(false)
	if err != nil {
		return err
	}

	_, err = os.Stat(fp)
Michael Yang's avatar
Michael Yang committed
89
90
	switch {
	case errors.Is(err, os.ErrNotExist):
91
		if err := pull(model, false); err != nil {
Michael Yang's avatar
Michael Yang committed
92
93
94
95
96
97
98
99
			var apiStatusError api.StatusError
			if !errors.As(err, &apiStatusError) {
				return err
			}

			if apiStatusError.StatusCode != http.StatusBadGateway {
				return err
			}
Michael Yang's avatar
Michael Yang committed
100
101
		}
	case err != nil:
Michael Yang's avatar
Michael Yang committed
102
103
104
		return err
	}

105
106
107
108
109
110
111
	return nil
}

func RunHandler(cmd *cobra.Command, args []string) error {
	if err := checkPull(args[0]); err != nil {
		return err
	}
Michael Yang's avatar
Michael Yang committed
112
	return RunGenerate(cmd, args)
Bruce MacDonald's avatar
Bruce MacDonald committed
113
114
}

115
func PushHandler(cmd *cobra.Command, args []string) error {
116
117
	client := api.NewClient()

118
119
120
121
122
	insecure, err := cmd.Flags().GetBool("insecure")
	if err != nil {
		return err
	}

123
124
125
	var currentDigest string
	var bar *progressbar.ProgressBar

126
	request := api.PushRequest{Name: args[0], Insecure: insecure}
127
	fn := func(resp api.ProgressResponse) error {
128
129
130
131
132
133
134
135
136
137
138
139
140
141
		if resp.Digest != currentDigest && resp.Digest != "" {
			currentDigest = resp.Digest
			bar = progressbar.DefaultBytes(
				int64(resp.Total),
				fmt.Sprintf("pushing %s...", resp.Digest[7:19]),
			)

			bar.Set(resp.Completed)
		} else if resp.Digest == currentDigest && resp.Digest != "" {
			bar.Set(resp.Completed)
		} else {
			currentDigest = ""
			fmt.Println(resp.Status)
		}
142
143
144
145
146
147
148
149
150
		return nil
	}

	if err := client.Push(context.Background(), &request, fn); err != nil {
		return err
	}
	return nil
}

151
func ListHandler(cmd *cobra.Command, args []string) error {
Patrick Devine's avatar
Patrick Devine committed
152
153
154
155
156
157
158
159
160
161
	client := api.NewClient()

	models, err := client.List(context.Background())
	if err != nil {
		return err
	}

	var data [][]string

	for _, m := range models.Models {
Michael Yang's avatar
Michael Yang committed
162
163
164
		if len(args) == 0 || strings.HasPrefix(m.Name, args[0]) {
			data = append(data, []string{m.Name, humanize.Bytes(uint64(m.Size)), format.HumanTime(m.ModifiedAt, "Never")})
		}
Patrick Devine's avatar
Patrick Devine committed
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
	}

	table := tablewriter.NewWriter(os.Stdout)
	table.SetHeader([]string{"NAME", "SIZE", "MODIFIED"})
	table.SetHeaderAlignment(tablewriter.ALIGN_LEFT)
	table.SetAlignment(tablewriter.ALIGN_LEFT)
	table.SetHeaderLine(false)
	table.SetBorder(false)
	table.SetNoWhiteSpace(true)
	table.SetTablePadding("\t")
	table.AppendBulk(data)
	table.Render()

	return nil
}

181
182
183
func DeleteHandler(cmd *cobra.Command, args []string) error {
	client := api.NewClient()

Patrick Devine's avatar
Patrick Devine committed
184
185
	req := api.DeleteRequest{Name: args[0]}
	if err := client.Delete(context.Background(), &req); err != nil {
186
187
		return err
	}
188
	fmt.Printf("deleted '%s'\n", args[0])
189
190
191
	return nil
}

Patrick Devine's avatar
Patrick Devine committed
192
193
194
195
196
197
198
199
200
201
202
func CopyHandler(cmd *cobra.Command, args []string) error {
	client := api.NewClient()

	req := api.CopyRequest{Source: args[0], Destination: args[1]}
	if err := client.Copy(context.Background(), &req); err != nil {
		return err
	}
	fmt.Printf("copied '%s' to '%s'\n", args[0], args[1])
	return nil
}

203
func PullHandler(cmd *cobra.Command, args []string) error {
204
205
206
207
208
209
	insecure, err := cmd.Flags().GetBool("insecure")
	if err != nil {
		return err
	}

	return pull(args[0], insecure)
210
211
}

212
func pull(model string, insecure bool) error {
Bruce MacDonald's avatar
Bruce MacDonald committed
213
	client := api.NewClient()
214

215
	var currentDigest string
Bruce MacDonald's avatar
Bruce MacDonald committed
216
	var bar *progressbar.ProgressBar
Michael Yang's avatar
Michael Yang committed
217

218
	request := api.PullRequest{Name: model, Insecure: insecure}
219
220
221
	fn := func(resp api.ProgressResponse) error {
		if resp.Digest != currentDigest && resp.Digest != "" {
			currentDigest = resp.Digest
222
223
			bar = progressbar.DefaultBytes(
				int64(resp.Total),
224
				fmt.Sprintf("pulling %s...", resp.Digest[7:19]),
225
			)
226
227
228

			bar.Set(resp.Completed)
		} else if resp.Digest == currentDigest && resp.Digest != "" {
229
230
			bar.Set(resp.Completed)
		} else {
231
			currentDigest = ""
232
233
234
235
			fmt.Println(resp.Status)
		}
		return nil
	}
236

237
238
239
240
	if err := client.Pull(context.Background(), &request, fn); err != nil {
		return err
	}
	return nil
Michael Yang's avatar
Michael Yang committed
241
242
}

243
func RunGenerate(cmd *cobra.Command, args []string) error {
Michael Yang's avatar
Michael Yang committed
244
	if len(args) > 1 {
Michael Yang's avatar
Michael Yang committed
245
		// join all args into a single prompt
246
		return generate(cmd, args[0], strings.Join(args[1:], " "))
Michael Yang's avatar
Michael Yang committed
247
248
	}

Michael Yang's avatar
Michael Yang committed
249
	if readline.IsTerminal(int(os.Stdin.Fd())) {
250
		return generateInteractive(cmd, args[0])
Michael Yang's avatar
Michael Yang committed
251
252
	}

253
	return generateBatch(cmd, args[0])
Michael Yang's avatar
Michael Yang committed
254
255
}

Michael Yang's avatar
Michael Yang committed
256
257
var generateContextKey struct{}

258
func generate(cmd *cobra.Command, model, prompt string) error {
Michael Yang's avatar
Michael Yang committed
259
260
261
	if len(strings.TrimSpace(prompt)) > 0 {
		client := api.NewClient()

Michael Yang's avatar
Michael Yang committed
262
263
		spinner := NewSpinner("")
		go spinner.Spin(60 * time.Millisecond)
Michael Yang's avatar
Michael Yang committed
264

265
266
		var latest api.GenerateResponse

Michael Yang's avatar
Michael Yang committed
267
268
269
270
271
272
		generateContext, ok := cmd.Context().Value(generateContextKey).([]int)
		if !ok {
			generateContext = []int{}
		}

		request := api.GenerateRequest{Model: model, Prompt: prompt, Context: generateContext}
273
		fn := func(resp api.GenerateResponse) error {
Michael Yang's avatar
Michael Yang committed
274
275
276
			if !spinner.IsFinished() {
				spinner.Finish()
			}
Michael Yang's avatar
Michael Yang committed
277

278
279
			latest = resp

Michael Yang's avatar
Michael Yang committed
280
			fmt.Print(resp.Response)
Michael Yang's avatar
Michael Yang committed
281
282

			cmd.SetContext(context.WithValue(cmd.Context(), generateContextKey, resp.Context))
Michael Yang's avatar
Michael Yang committed
283
			return nil
284
285
286
287
288
		}

		if err := client.Generate(context.Background(), &request, fn); err != nil {
			return err
		}
Michael Yang's avatar
Michael Yang committed
289

Michael Yang's avatar
Michael Yang committed
290
291
		fmt.Println()
		fmt.Println()
292
293
294
295
296
297
298
299
300

		verbose, err := cmd.Flags().GetBool("verbose")
		if err != nil {
			return err
		}

		if verbose {
			latest.Summary()
		}
Michael Yang's avatar
Michael Yang committed
301
	}
Michael Yang's avatar
Michael Yang committed
302
303
304
305

	return nil
}

306
func generateInteractive(cmd *cobra.Command, model string) error {
Michael Yang's avatar
Michael Yang committed
307
308
309
310
311
312
313
314
315
316
317
	home, err := os.UserHomeDir()
	if err != nil {
		return err
	}

	completer := readline.NewPrefixCompleter(
		readline.PcItem("/help"),
		readline.PcItem("/list"),
		readline.PcItem("/set",
			readline.PcItem("history"),
			readline.PcItem("nohistory"),
Michael Yang's avatar
Michael Yang committed
318
319
			readline.PcItem("verbose"),
			readline.PcItem("quiet"),
Michael Yang's avatar
Michael Yang committed
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
			readline.PcItem("mode",
				readline.PcItem("vim"),
				readline.PcItem("emacs"),
				readline.PcItem("default"),
			),
		),
		readline.PcItem("/exit"),
		readline.PcItem("/bye"),
	)

	usage := func() {
		fmt.Fprintln(os.Stderr, "commands:")
		fmt.Fprintln(os.Stderr, completer.Tree("  "))
	}

	config := readline.Config{
		Prompt:       ">>> ",
		HistoryFile:  filepath.Join(home, ".ollama", "history"),
		AutoComplete: completer,
	}

	scanner, err := readline.NewEx(&config)
	if err != nil {
		return err
	}
	defer scanner.Close()

	for {
		line, err := scanner.Readline()
		switch {
		case errors.Is(err, io.EOF):
			return nil
		case errors.Is(err, readline.ErrInterrupt):
353
354
355
356
			if line == "" {
				return nil
			}

Michael Yang's avatar
Michael Yang committed
357
358
			continue
		case err != nil:
Michael Yang's avatar
Michael Yang committed
359
360
361
			return err
		}

Michael Yang's avatar
Michael Yang committed
362
		line = strings.TrimSpace(line)
Michael Yang's avatar
Michael Yang committed
363

Michael Yang's avatar
Michael Yang committed
364
365
366
		switch {
		case strings.HasPrefix(line, "/list"):
			args := strings.Fields(line)
367
			if err := ListHandler(cmd, args[1:]); err != nil {
Michael Yang's avatar
Michael Yang committed
368
369
370
371
372
373
374
375
376
377
378
379
380
381
				return err
			}

			continue
		case strings.HasPrefix(line, "/set"):
			args := strings.Fields(line)
			if len(args) > 1 {
				switch args[1] {
				case "history":
					scanner.HistoryEnable()
					continue
				case "nohistory":
					scanner.HistoryDisable()
					continue
Michael Yang's avatar
Michael Yang committed
382
383
384
385
386
387
				case "verbose":
					cmd.Flags().Set("verbose", "true")
					continue
				case "quiet":
					cmd.Flags().Set("verbose", "false")
					continue
Michael Yang's avatar
Michael Yang committed
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
				case "mode":
					if len(args) > 2 {
						switch args[2] {
						case "vim":
							scanner.SetVimMode(true)
							continue
						case "emacs", "default":
							scanner.SetVimMode(false)
							continue
						}
					}
				}
			}
		case line == "/help", line == "/?":
			usage()
			continue
		case line == "/exit", line == "/bye":
			return nil
		}

		if err := generate(cmd, model, line); err != nil {
			return err
		}
	}
Bruce MacDonald's avatar
Bruce MacDonald committed
412
413
}

414
func generateBatch(cmd *cobra.Command, model string) error {
Michael Yang's avatar
Michael Yang committed
415
416
417
418
	scanner := bufio.NewScanner(os.Stdin)
	for scanner.Scan() {
		prompt := scanner.Text()
		fmt.Printf(">>> %s\n", prompt)
419
		if err := generate(cmd, model, prompt); err != nil {
Michael Yang's avatar
Michael Yang committed
420
421
422
423
424
425
426
427
			return err
		}
	}

	return nil
}

func RunServer(_ *cobra.Command, _ []string) error {
428
429
430
431
432
433
434
435
436
437
438
	host := os.Getenv("OLLAMA_HOST")
	if host == "" {
		host = "127.0.0.1"
	}

	port := os.Getenv("OLLAMA_PORT")
	if port == "" {
		port = "11434"
	}

	ln, err := net.Listen("tcp", fmt.Sprintf("%s:%s", host, port))
Jeffrey Morgan's avatar
Jeffrey Morgan committed
439
440
441
442
443
444
445
	if err != nil {
		return err
	}

	return server.Serve(ln)
}

Jeffrey Morgan's avatar
Jeffrey Morgan committed
446
447
448
449
func NewCLI() *cobra.Command {
	log.SetFlags(log.LstdFlags | log.Lshortfile)

	rootCmd := &cobra.Command{
Michael Yang's avatar
Michael Yang committed
450
451
452
		Use:          "ollama",
		Short:        "Large language model runner",
		SilenceUsage: true,
Jeffrey Morgan's avatar
Jeffrey Morgan committed
453
454
455
456
457
458
459
		CompletionOptions: cobra.CompletionOptions{
			DisableDefaultCmd: true,
		},
	}

	cobra.EnableCommandSorting = false

460
461
462
463
	createCmd := &cobra.Command{
		Use:   "create MODEL",
		Short: "Create a model from a Modelfile",
		Args:  cobra.MinimumNArgs(1),
464
		RunE:  CreateHandler,
465
466
467
468
	}

	createCmd.Flags().StringP("file", "f", "Modelfile", "Name of the Modelfile (default \"Modelfile\")")

Jeffrey Morgan's avatar
Jeffrey Morgan committed
469
	runCmd := &cobra.Command{
Michael Yang's avatar
Michael Yang committed
470
		Use:   "run MODEL [PROMPT]",
Jeffrey Morgan's avatar
Jeffrey Morgan committed
471
		Short: "Run a model",
Michael Yang's avatar
Michael Yang committed
472
		Args:  cobra.MinimumNArgs(1),
473
		RunE:  RunHandler,
Jeffrey Morgan's avatar
Jeffrey Morgan committed
474
475
	}

476
477
	runCmd.Flags().Bool("verbose", false, "Show timings for response")

Jeffrey Morgan's avatar
Jeffrey Morgan committed
478
479
480
481
	serveCmd := &cobra.Command{
		Use:     "serve",
		Aliases: []string{"start"},
		Short:   "Start ollama",
Michael Yang's avatar
Michael Yang committed
482
		RunE:    RunServer,
Jeffrey Morgan's avatar
Jeffrey Morgan committed
483
484
	}

485
486
487
488
	pullCmd := &cobra.Command{
		Use:   "pull MODEL",
		Short: "Pull a model from a registry",
		Args:  cobra.MinimumNArgs(1),
489
		RunE:  PullHandler,
490
491
	}

492
493
	pullCmd.Flags().Bool("insecure", false, "Use an insecure registry")

494
495
496
497
	pushCmd := &cobra.Command{
		Use:   "push MODEL",
		Short: "Push a model to a registry",
		Args:  cobra.MinimumNArgs(1),
498
		RunE:  PushHandler,
499
500
	}

501
502
	pushCmd.Flags().Bool("insecure", false, "Use an insecure registry")

Patrick Devine's avatar
Patrick Devine committed
503
	listCmd := &cobra.Command{
504
		Use:     "list",
Patrick Devine's avatar
Patrick Devine committed
505
		Aliases: []string{"ls"},
506
507
		Short:   "List models",
		RunE:    ListHandler,
508
509
	}

Patrick Devine's avatar
Patrick Devine committed
510
511
512
513
514
515
516
	copyCmd := &cobra.Command{
		Use:   "cp",
		Short: "Copy a model",
		Args:  cobra.MinimumNArgs(2),
		RunE:  CopyHandler,
	}

517
518
519
520
521
	deleteCmd := &cobra.Command{
		Use:   "rm",
		Short: "Remove a model",
		Args:  cobra.MinimumNArgs(1),
		RunE:  DeleteHandler,
Patrick Devine's avatar
Patrick Devine committed
522
523
	}

Jeffrey Morgan's avatar
Jeffrey Morgan committed
524
525
	rootCmd.AddCommand(
		serveCmd,
526
		createCmd,
527
		runCmd,
528
529
		pullCmd,
		pushCmd,
Patrick Devine's avatar
Patrick Devine committed
530
		listCmd,
Patrick Devine's avatar
Patrick Devine committed
531
		copyCmd,
532
		deleteCmd,
Jeffrey Morgan's avatar
Jeffrey Morgan committed
533
534
535
536
	)

	return rootCmd
}