cmd.go 9.29 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/progressbar"
Jeffrey Morgan's avatar
Jeffrey Morgan committed
25
	"github.com/jmorganca/ollama/server"
Jeffrey Morgan's avatar
Jeffrey Morgan committed
26
27
)

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

35
36
	client := api.NewClient()

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

39
40
	request := api.CreateRequest{Name: args[0], Path: filename}
	fn := func(resp api.CreateProgress) error {
Michael Yang's avatar
Michael Yang committed
41
42
43
44
45
46
47
		if spinner != nil {
			spinner.Stop()
		}

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

48
49
50
51
52
53
54
		return nil
	}

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

Michael Yang's avatar
Michael Yang committed
55
56
57
58
	if spinner != nil {
		spinner.Stop()
	}

59
60
61
	return nil
}

62
func RunHandler(cmd *cobra.Command, args []string) error {
Patrick Devine's avatar
Patrick Devine committed
63
64
65
66
67
68
69
	mp := server.ParseModelPath(args[0])
	fp, err := mp.GetManifestPath(false)
	if err != nil {
		return err
	}

	_, err = os.Stat(fp)
Michael Yang's avatar
Michael Yang committed
70
71
72
	switch {
	case errors.Is(err, os.ErrNotExist):
		if err := pull(args[0]); err != nil {
Michael Yang's avatar
Michael Yang committed
73
74
75
76
77
78
79
80
			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
81
82
		}
	case err != nil:
Michael Yang's avatar
Michael Yang committed
83
84
85
86
		return err
	}

	return RunGenerate(cmd, args)
Bruce MacDonald's avatar
Bruce MacDonald committed
87
88
}

89
func PushHandler(cmd *cobra.Command, args []string) error {
90
91
92
	client := api.NewClient()

	request := api.PushRequest{Name: args[0]}
93
	fn := func(resp api.ProgressResponse) error {
94
95
96
97
98
99
100
101
102
103
		fmt.Println(resp.Status)
		return nil
	}

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

104
func ListHandler(cmd *cobra.Command, args []string) error {
Patrick Devine's avatar
Patrick Devine committed
105
106
107
108
109
110
111
112
113
114
	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
115
116
117
		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
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
	}

	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
}

134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
func DeleteHandler(cmd *cobra.Command, args []string) error {
	client := api.NewClient()

	request := api.DeleteRequest{Name: args[0]}
	fn := func(resp api.ProgressResponse) error {
		fmt.Println(resp.Status)
		return nil
	}

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

func PullHandler(cmd *cobra.Command, args []string) error {
150
151
152
	return pull(args[0])
}

Michael Yang's avatar
Michael Yang committed
153
func pull(model string) error {
Bruce MacDonald's avatar
Bruce MacDonald committed
154
	client := api.NewClient()
155

156
	var currentDigest string
Bruce MacDonald's avatar
Bruce MacDonald committed
157
	var bar *progressbar.ProgressBar
Michael Yang's avatar
Michael Yang committed
158

159
	request := api.PullRequest{Name: model}
160
161
162
	fn := func(resp api.ProgressResponse) error {
		if resp.Digest != currentDigest && resp.Digest != "" {
			currentDigest = resp.Digest
163
164
			bar = progressbar.DefaultBytes(
				int64(resp.Total),
165
				fmt.Sprintf("pulling %s...", resp.Digest[7:19]),
166
			)
167
168
169

			bar.Set(resp.Completed)
		} else if resp.Digest == currentDigest && resp.Digest != "" {
170
171
			bar.Set(resp.Completed)
		} else {
172
			currentDigest = ""
173
174
175
176
			fmt.Println(resp.Status)
		}
		return nil
	}
177

178
179
180
181
	if err := client.Pull(context.Background(), &request, fn); err != nil {
		return err
	}
	return nil
Michael Yang's avatar
Michael Yang committed
182
183
}

184
func RunGenerate(cmd *cobra.Command, args []string) error {
Michael Yang's avatar
Michael Yang committed
185
	if len(args) > 1 {
Michael Yang's avatar
Michael Yang committed
186
		// join all args into a single prompt
187
		return generate(cmd, args[0], strings.Join(args[1:], " "))
Michael Yang's avatar
Michael Yang committed
188
189
	}

Michael Yang's avatar
Michael Yang committed
190
	if readline.IsTerminal(int(os.Stdin.Fd())) {
191
		return generateInteractive(cmd, args[0])
Michael Yang's avatar
Michael Yang committed
192
193
	}

194
	return generateBatch(cmd, args[0])
Michael Yang's avatar
Michael Yang committed
195
196
}

Michael Yang's avatar
Michael Yang committed
197
198
var generateContextKey struct{}

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

Michael Yang's avatar
Michael Yang committed
203
204
		spinner := NewSpinner("")
		go spinner.Spin(60 * time.Millisecond)
Michael Yang's avatar
Michael Yang committed
205

206
207
		var latest api.GenerateResponse

Michael Yang's avatar
Michael Yang committed
208
209
210
211
212
213
		generateContext, ok := cmd.Context().Value(generateContextKey).([]int)
		if !ok {
			generateContext = []int{}
		}

		request := api.GenerateRequest{Model: model, Prompt: prompt, Context: generateContext}
214
		fn := func(resp api.GenerateResponse) error {
Michael Yang's avatar
Michael Yang committed
215
216
217
			if !spinner.IsFinished() {
				spinner.Finish()
			}
Michael Yang's avatar
Michael Yang committed
218

219
220
			latest = resp

Michael Yang's avatar
Michael Yang committed
221
			fmt.Print(resp.Response)
Michael Yang's avatar
Michael Yang committed
222
223

			cmd.SetContext(context.WithValue(cmd.Context(), generateContextKey, resp.Context))
Michael Yang's avatar
Michael Yang committed
224
			return nil
225
226
227
228
229
		}

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

Michael Yang's avatar
Michael Yang committed
231
232
		fmt.Println()
		fmt.Println()
233
234
235
236
237
238
239
240
241

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

		if verbose {
			latest.Summary()
		}
Michael Yang's avatar
Michael Yang committed
242
	}
Michael Yang's avatar
Michael Yang committed
243
244
245
246

	return nil
}

247
func generateInteractive(cmd *cobra.Command, model string) error {
Michael Yang's avatar
Michael Yang committed
248
249
250
251
252
253
254
255
256
257
258
	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
259
260
			readline.PcItem("verbose"),
			readline.PcItem("quiet"),
Michael Yang's avatar
Michael Yang committed
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
			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):
294
295
296
297
			if line == "" {
				return nil
			}

Michael Yang's avatar
Michael Yang committed
298
299
			continue
		case err != nil:
Michael Yang's avatar
Michael Yang committed
300
301
302
			return err
		}

Michael Yang's avatar
Michael Yang committed
303
		line = strings.TrimSpace(line)
Michael Yang's avatar
Michael Yang committed
304

Michael Yang's avatar
Michael Yang committed
305
306
307
		switch {
		case strings.HasPrefix(line, "/list"):
			args := strings.Fields(line)
308
			if err := ListHandler(cmd, args[1:]); err != nil {
Michael Yang's avatar
Michael Yang committed
309
310
311
312
313
314
315
316
317
318
319
320
321
322
				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
323
324
325
326
327
328
				case "verbose":
					cmd.Flags().Set("verbose", "true")
					continue
				case "quiet":
					cmd.Flags().Set("verbose", "false")
					continue
Michael Yang's avatar
Michael Yang committed
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
				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
353
354
}

355
func generateBatch(cmd *cobra.Command, model string) error {
Michael Yang's avatar
Michael Yang committed
356
357
358
359
	scanner := bufio.NewScanner(os.Stdin)
	for scanner.Scan() {
		prompt := scanner.Text()
		fmt.Printf(">>> %s\n", prompt)
360
		if err := generate(cmd, model, prompt); err != nil {
Michael Yang's avatar
Michael Yang committed
361
362
363
364
365
366
367
368
			return err
		}
	}

	return nil
}

func RunServer(_ *cobra.Command, _ []string) error {
369
370
371
372
373
374
375
376
377
378
379
	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
380
381
382
383
384
385
386
	if err != nil {
		return err
	}

	return server.Serve(ln)
}

Jeffrey Morgan's avatar
Jeffrey Morgan committed
387
388
389
390
func NewCLI() *cobra.Command {
	log.SetFlags(log.LstdFlags | log.Lshortfile)

	rootCmd := &cobra.Command{
Michael Yang's avatar
Michael Yang committed
391
392
393
		Use:          "ollama",
		Short:        "Large language model runner",
		SilenceUsage: true,
Jeffrey Morgan's avatar
Jeffrey Morgan committed
394
395
396
397
398
399
400
		CompletionOptions: cobra.CompletionOptions{
			DisableDefaultCmd: true,
		},
	}

	cobra.EnableCommandSorting = false

401
402
403
404
	createCmd := &cobra.Command{
		Use:   "create MODEL",
		Short: "Create a model from a Modelfile",
		Args:  cobra.MinimumNArgs(1),
405
		RunE:  CreateHandler,
406
407
408
409
	}

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

Jeffrey Morgan's avatar
Jeffrey Morgan committed
410
	runCmd := &cobra.Command{
Michael Yang's avatar
Michael Yang committed
411
		Use:   "run MODEL [PROMPT]",
Jeffrey Morgan's avatar
Jeffrey Morgan committed
412
		Short: "Run a model",
Michael Yang's avatar
Michael Yang committed
413
		Args:  cobra.MinimumNArgs(1),
414
		RunE:  RunHandler,
Jeffrey Morgan's avatar
Jeffrey Morgan committed
415
416
	}

417
418
	runCmd.Flags().Bool("verbose", false, "Show timings for response")

Jeffrey Morgan's avatar
Jeffrey Morgan committed
419
420
421
422
	serveCmd := &cobra.Command{
		Use:     "serve",
		Aliases: []string{"start"},
		Short:   "Start ollama",
Michael Yang's avatar
Michael Yang committed
423
		RunE:    RunServer,
Jeffrey Morgan's avatar
Jeffrey Morgan committed
424
425
	}

426
427
428
429
	pullCmd := &cobra.Command{
		Use:   "pull MODEL",
		Short: "Pull a model from a registry",
		Args:  cobra.MinimumNArgs(1),
430
		RunE:  PullHandler,
431
432
433
434
435
436
	}

	pushCmd := &cobra.Command{
		Use:   "push MODEL",
		Short: "Push a model to a registry",
		Args:  cobra.MinimumNArgs(1),
437
		RunE:  PushHandler,
438
439
	}

Patrick Devine's avatar
Patrick Devine committed
440
441
	listCmd := &cobra.Command{
		Use:   "list",
Patrick Devine's avatar
Patrick Devine committed
442
		Aliases: []string{"ls"},
Patrick Devine's avatar
Patrick Devine committed
443
		Short: "List models",
444
445
446
447
448
449
450
451
		RunE:  ListHandler,
	}

	deleteCmd := &cobra.Command{
		Use:   "rm",
		Short: "Remove a model",
		Args:  cobra.MinimumNArgs(1),
		RunE:  DeleteHandler,
Patrick Devine's avatar
Patrick Devine committed
452
453
	}

Jeffrey Morgan's avatar
Jeffrey Morgan committed
454
455
	rootCmd.AddCommand(
		serveCmd,
456
		createCmd,
457
		runCmd,
458
459
		pullCmd,
		pushCmd,
Patrick Devine's avatar
Patrick Devine committed
460
		listCmd,
461
		deleteCmd,
Jeffrey Morgan's avatar
Jeffrey Morgan committed
462
463
464
465
	)

	return rootCmd
}