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

import (
4
	"archive/zip"
Michael Yang's avatar
Michael Yang committed
5
	"bytes"
Jeffrey Morgan's avatar
Jeffrey Morgan committed
6
	"context"
7
8
	"crypto/ed25519"
	"crypto/rand"
Michael Yang's avatar
Michael Yang committed
9
	"crypto/sha256"
10
	"encoding/pem"
Michael Yang's avatar
Michael Yang committed
11
	"errors"
Bruce MacDonald's avatar
Bruce MacDonald committed
12
	"fmt"
Michael Yang's avatar
Michael Yang committed
13
	"io"
Jeffrey Morgan's avatar
Jeffrey Morgan committed
14
15
	"log"
	"net"
16
	"net/http"
Jeffrey Morgan's avatar
Jeffrey Morgan committed
17
	"os"
18
	"os/signal"
19
	"path/filepath"
20
	"regexp"
21
	"runtime"
Michael Yang's avatar
Michael Yang committed
22
	"strings"
23
	"syscall"
Michael Yang's avatar
Michael Yang committed
24
	"time"
Jeffrey Morgan's avatar
Jeffrey Morgan committed
25

26
27
	"github.com/containerd/console"

Patrick Devine's avatar
Patrick Devine committed
28
	"github.com/olekukonko/tablewriter"
Michael Yang's avatar
Michael Yang committed
29
	"github.com/spf13/cobra"
30
	"golang.org/x/crypto/ssh"
31
	"golang.org/x/exp/slices"
32
	"golang.org/x/term"
Michael Yang's avatar
Michael Yang committed
33

34
	"github.com/ollama/ollama/api"
35
	"github.com/ollama/ollama/auth"
36
37
38
39
	"github.com/ollama/ollama/format"
	"github.com/ollama/ollama/parser"
	"github.com/ollama/ollama/progress"
	"github.com/ollama/ollama/server"
40
41
	"github.com/ollama/ollama/types/errtypes"
	"github.com/ollama/ollama/types/model"
42
	"github.com/ollama/ollama/version"
Jeffrey Morgan's avatar
Jeffrey Morgan committed
43
44
)

45
func CreateHandler(cmd *cobra.Command, args []string) error {
46
	filename, _ := cmd.Flags().GetString("file")
47
48
49
50
51
	filename, err := filepath.Abs(filename)
	if err != nil {
		return err
	}

Michael Yang's avatar
Michael Yang committed
52
	client, err := api.ClientFromEnvironment()
53
54
55
	if err != nil {
		return err
	}
56

Michael Yang's avatar
Michael Yang committed
57
58
59
	p := progress.NewProgress(os.Stderr)
	defer p.Stop()

Michael Yang's avatar
Michael Yang committed
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
	modelfile, err := os.ReadFile(filename)
	if err != nil {
		return err
	}

	commands, err := parser.Parse(bytes.NewReader(modelfile))
	if err != nil {
		return err
	}

	home, err := os.UserHomeDir()
	if err != nil {
		return err
	}

75
76
	status := "transferring model data"
	spinner := progress.NewSpinner(status)
77
78
	p.Add(status, spinner)

Michael Yang's avatar
Michael Yang committed
79
80
81
82
83
84
85
86
87
88
	for _, c := range commands {
		switch c.Name {
		case "model", "adapter":
			path := c.Args
			if path == "~" {
				path = home
			} else if strings.HasPrefix(path, "~/") {
				path = filepath.Join(home, path[2:])
			}

89
90
91
92
			if !filepath.IsAbs(path) {
				path = filepath.Join(filepath.Dir(filename), path)
			}

93
			fi, err := os.Stat(path)
Michael Yang's avatar
Michael Yang committed
94
			if errors.Is(err, os.ErrNotExist) && c.Name == "model" {
Michael Yang's avatar
Michael Yang committed
95
				continue
Michael Yang's avatar
Michael Yang committed
96
97
98
99
			} else if err != nil {
				return err
			}

100
			if fi.IsDir() {
Michael Yang's avatar
Michael Yang committed
101
102
103
				// this is likely a safetensors or pytorch directory
				// TODO make this work w/ adapters
				tempfile, err := tempZipFiles(path)
104
105
106
				if err != nil {
					return err
				}
Michael Yang's avatar
Michael Yang committed
107
				defer os.RemoveAll(tempfile)
108

Michael Yang's avatar
Michael Yang committed
109
				path = tempfile
Michael Yang's avatar
Michael Yang committed
110
111
			}

112
113
			digest, err := createBlob(cmd, client, path)
			if err != nil {
Michael Yang's avatar
Michael Yang committed
114
115
116
				return err
			}

117
118
119
120
121
122
123
			name := c.Name
			if c.Name == "model" {
				name = "from"
			}

			re := regexp.MustCompile(fmt.Sprintf(`(?im)^(%s)\s+%s\s*$`, name, c.Args))
			modelfile = re.ReplaceAll(modelfile, []byte("$1 @"+digest))
Michael Yang's avatar
Michael Yang committed
124
125
		}
	}
Michael Yang's avatar
Michael Yang committed
126

Michael Yang's avatar
Michael Yang committed
127
	bars := make(map[string]*progress.Bar)
128
	fn := func(resp api.ProgressResponse) error {
Michael Yang's avatar
Michael Yang committed
129
130
131
132
133
		if resp.Digest != "" {
			spinner.Stop()

			bar, ok := bars[resp.Digest]
			if !ok {
134
				bar = progress.NewBar(fmt.Sprintf("pulling %s...", resp.Digest[7:19]), resp.Total, resp.Completed)
Michael Yang's avatar
Michael Yang committed
135
136
137
138
139
140
141
142
143
144
145
146
147
				bars[resp.Digest] = bar
				p.Add(resp.Digest, bar)
			}

			bar.Set(resp.Completed)
		} else if status != resp.Status {
			spinner.Stop()

			status = resp.Status
			spinner = progress.NewSpinner(status)
			p.Add(status, spinner)
		}

148
149
150
		return nil
	}

Michael Yang's avatar
Michael Yang committed
151
152
153
	quantization, _ := cmd.Flags().GetString("quantization")

	request := api.CreateRequest{Name: args[0], Modelfile: string(modelfile), Quantization: quantization}
Michael Yang's avatar
Michael Yang committed
154
	if err := client.Create(cmd.Context(), &request, fn); err != nil {
155
156
157
158
159
160
		return err
	}

	return nil
}

Michael Yang's avatar
Michael Yang committed
161
162
163
164
165
166
167
168
169
170
func tempZipFiles(path string) (string, error) {
	tempfile, err := os.CreateTemp("", "ollama-tf")
	if err != nil {
		return "", err
	}
	defer tempfile.Close()

	zipfile := zip.NewWriter(tempfile)
	defer zipfile.Close()

Michael Yang's avatar
Michael Yang committed
171
172
	detectContentType := func(path string) (string, error) {
		f, err := os.Open(path)
Michael Yang's avatar
Michael Yang committed
173
174
175
		if err != nil {
			return "", err
		}
Michael Yang's avatar
Michael Yang committed
176
		defer f.Close()
Michael Yang's avatar
Michael Yang committed
177

Michael Yang's avatar
Michael Yang committed
178
179
		var b bytes.Buffer
		b.Grow(512)
Michael Yang's avatar
Michael Yang committed
180

Michael Yang's avatar
Michael Yang committed
181
182
183
184
185
186
		if _, err := io.CopyN(&b, f, 512); err != nil && !errors.Is(err, io.EOF) {
			return "", err
		}

		contentType, _, _ := strings.Cut(http.DetectContentType(b.Bytes()), ";")
		return contentType, nil
Michael Yang's avatar
Michael Yang committed
187
188
	}

Michael Yang's avatar
Michael Yang committed
189
190
191
192
193
194
195
196
197
198
199
	glob := func(pattern, contentType string) ([]string, error) {
		matches, err := filepath.Glob(pattern)
		if err != nil {
			return nil, err
		}

		for _, safetensor := range matches {
			if ct, err := detectContentType(safetensor); err != nil {
				return nil, err
			} else if ct != contentType {
				return nil, fmt.Errorf("invalid content type: expected %s for %s", ct, safetensor)
Michael Yang's avatar
Michael Yang committed
200
			}
Michael Yang's avatar
Michael Yang committed
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
		}

		return matches, nil
	}

	var files []string
	if st, _ := glob(filepath.Join(path, "model*.safetensors"), "application/octet-stream"); len(st) > 0 {
		// safetensors files might be unresolved git lfs references; skip if they are
		// covers model-x-of-y.safetensors, model.fp32-x-of-y.safetensors, model.safetensors
		files = append(files, st...)
	} else if pt, _ := glob(filepath.Join(path, "pytorch_model*.bin"), "application/zip"); len(pt) > 0 {
		// pytorch files might also be unresolved git lfs references; skip if they are
		// covers pytorch_model-x-of-y.bin, pytorch_model.fp32-x-of-y.bin, pytorch_model.bin
		files = append(files, pt...)
	} else if pt, _ := glob(filepath.Join(path, "consolidated*.pth"), "application/octet-stream"); len(pt) > 0 {
		// pytorch files might also be unresolved git lfs references; skip if they are
		// covers consolidated.x.pth, consolidated.pth
		files = append(files, pt...)
	} else {
		return "", errors.New("no safetensors or torch files found")
	}

	// add configuration files, json files are detected as text/plain
	js, err := glob(filepath.Join(path, "*.json"), "text/plain")
	if err != nil {
		return "", err
	}
	files = append(files, js...)

	if tks, _ := glob(filepath.Join(path, "tokenizer.model"), "application/octet-stream"); len(tks) > 0 {
		// add tokenizer.model if it exists, tokenizer.json is automatically picked up by the previous glob
		// tokenizer.model might be a unresolved git lfs reference; error if it is
		files = append(files, tks...)
	} else if tks, _ := glob(filepath.Join(path, "**/tokenizer.model"), "text/plain"); len(tks) > 0 {
		// some times tokenizer.model is in a subdirectory (e.g. meta-llama/Meta-Llama-3-8B)
		files = append(files, tks...)
	}

	for _, file := range files {
		f, err := os.Open(file)
		if err != nil {
Michael Yang's avatar
Michael Yang committed
242
243
			return "", err
		}
Michael Yang's avatar
Michael Yang committed
244
		defer f.Close()
Michael Yang's avatar
Michael Yang committed
245
246
247
248
249
250

		fi, err := f.Stat()
		if err != nil {
			return "", err
		}

Michael Yang's avatar
Michael Yang committed
251
		zfi, err := zip.FileInfoHeader(fi)
Michael Yang's avatar
Michael Yang committed
252
253
254
255
		if err != nil {
			return "", err
		}

Michael Yang's avatar
Michael Yang committed
256
		zf, err := zipfile.CreateHeader(zfi)
Michael Yang's avatar
Michael Yang committed
257
258
259
260
		if err != nil {
			return "", err
		}

Michael Yang's avatar
Michael Yang committed
261
		if _, err := io.Copy(zf, f); err != nil {
Michael Yang's avatar
Michael Yang committed
262
263
264
265
266
267
268
			return "", err
		}
	}

	return tempfile.Name(), nil
}

269
270
271
272
273
274
275
276
277
278
279
func createBlob(cmd *cobra.Command, client *api.Client, path string) (string, error) {
	bin, err := os.Open(path)
	if err != nil {
		return "", err
	}
	defer bin.Close()

	hash := sha256.New()
	if _, err := io.Copy(hash, bin); err != nil {
		return "", err
	}
280
281
282
283

	if _, err := bin.Seek(0, io.SeekStart); err != nil {
		return "", err
	}
284
285
286
287
288
289
290
291

	digest := fmt.Sprintf("sha256:%x", hash.Sum(nil))
	if err = client.CreateBlob(cmd.Context(), digest, bin); err != nil {
		return "", err
	}
	return digest, nil
}

292
func RunHandler(cmd *cobra.Command, args []string) error {
Michael Yang's avatar
Michael Yang committed
293
	client, err := api.ClientFromEnvironment()
294
295
296
297
	if err != nil {
		return err
	}

298
	name := args[0]
299

300
	// check if the model exists on the server
301
	show, err := client.Show(cmd.Context(), &api.ShowRequest{Name: name})
Michael Yang's avatar
Michael Yang committed
302
303
304
	var statusError api.StatusError
	switch {
	case errors.As(err, &statusError) && statusError.StatusCode == http.StatusNotFound:
305
		if err := PullHandler(cmd, []string{name}); err != nil {
306
			return err
Michael Yang's avatar
Michael Yang committed
307
		}
308
309
310
311
312

		show, err = client.Show(cmd.Context(), &api.ShowRequest{Name: name})
		if err != nil {
			return err
		}
Michael Yang's avatar
Michael Yang committed
313
314
	case err != nil:
		return err
315
316
	}

317
318
319
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
353
354
355
356
357
358
359
360
	interactive := true

	opts := runOptions{
		Model:       args[0],
		WordWrap:    os.Getenv("TERM") == "xterm-256color",
		Options:     map[string]interface{}{},
		MultiModal:  slices.Contains(show.Details.Families, "clip"),
		ParentModel: show.Details.ParentModel,
	}

	format, err := cmd.Flags().GetString("format")
	if err != nil {
		return err
	}
	opts.Format = format

	prompts := args[1:]
	// prepend stdin to the prompt if provided
	if !term.IsTerminal(int(os.Stdin.Fd())) {
		in, err := io.ReadAll(os.Stdin)
		if err != nil {
			return err
		}

		prompts = append([]string{string(in)}, prompts...)
		opts.WordWrap = false
		interactive = false
	}
	opts.Prompt = strings.Join(prompts, " ")
	if len(prompts) > 0 {
		interactive = false
	}

	nowrap, err := cmd.Flags().GetBool("nowordwrap")
	if err != nil {
		return err
	}
	opts.WordWrap = !nowrap

	if !interactive {
		return generate(cmd, opts)
	}

	return generateInteractive(cmd, opts)
Bruce MacDonald's avatar
Bruce MacDonald committed
361
362
}

363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
func errFromUnknownKey(unknownKeyErr error) error {
	// find SSH public key in the error message
	sshKeyPattern := `ssh-\w+ [^\s"]+`
	re := regexp.MustCompile(sshKeyPattern)
	matches := re.FindStringSubmatch(unknownKeyErr.Error())

	if len(matches) > 0 {
		serverPubKey := matches[0]

		localPubKey, err := auth.GetPublicKey()
		if err != nil {
			return unknownKeyErr
		}

		if runtime.GOOS == "linux" && serverPubKey != localPubKey {
			// try the ollama service public key
			svcPubKey, err := os.ReadFile("/usr/share/ollama/.ollama/id_ed25519.pub")
			if err != nil {
				return unknownKeyErr
			}
			localPubKey = strings.TrimSpace(string(svcPubKey))
		}

		// check if the returned public key matches the local public key, this prevents adding a remote key to the user's account
		if serverPubKey != localPubKey {
			return unknownKeyErr
		}

		var msg strings.Builder
		msg.WriteString(unknownKeyErr.Error())
		msg.WriteString("\n\nYour ollama key is:\n")
		msg.WriteString(localPubKey)
		msg.WriteString("\nAdd your key at:\n")
		msg.WriteString("https://ollama.com/settings/keys")

		return errors.New(msg.String())
	}

	return unknownKeyErr
}

404
func PushHandler(cmd *cobra.Command, args []string) error {
Michael Yang's avatar
Michael Yang committed
405
	client, err := api.ClientFromEnvironment()
406
407
408
	if err != nil {
		return err
	}
409

410
411
412
413
414
	insecure, err := cmd.Flags().GetBool("insecure")
	if err != nil {
		return err
	}

Michael Yang's avatar
Michael Yang committed
415
416
417
418
	p := progress.NewProgress(os.Stderr)
	defer p.Stop()

	bars := make(map[string]*progress.Bar)
419
420
	var status string
	var spinner *progress.Spinner
Michael Yang's avatar
Michael Yang committed
421

422
	fn := func(resp api.ProgressResponse) error {
Michael Yang's avatar
Michael Yang committed
423
		if resp.Digest != "" {
424
425
426
			if spinner != nil {
				spinner.Stop()
			}
Michael Yang's avatar
Michael Yang committed
427
428
429

			bar, ok := bars[resp.Digest]
			if !ok {
430
				bar = progress.NewBar(fmt.Sprintf("pushing %s...", resp.Digest[7:19]), resp.Total, resp.Completed)
Michael Yang's avatar
Michael Yang committed
431
432
433
434
435
436
				bars[resp.Digest] = bar
				p.Add(resp.Digest, bar)
			}

			bar.Set(resp.Completed)
		} else if status != resp.Status {
437
438
439
			if spinner != nil {
				spinner.Stop()
			}
Michael Yang's avatar
Michael Yang committed
440
441
442
443
444
445

			status = resp.Status
			spinner = progress.NewSpinner(status)
			p.Add(status, spinner)
		}

446
447
448
		return nil
	}

Michael Yang's avatar
Michael Yang committed
449
	request := api.PushRequest{Name: args[0], Insecure: insecure}
Michael Yang's avatar
Michael Yang committed
450
	if err := client.Push(cmd.Context(), &request, fn); err != nil {
451
452
453
454
455
456
457
458
459
460
461
462
463
464
		if spinner != nil {
			spinner.Stop()
		}
		if strings.Contains(err.Error(), "access denied") {
			return errors.New("you are not authorized to push to this namespace, create the model under a namespace you own")
		}
		host := model.ParseName(args[0]).Host
		isOllamaHost := strings.HasSuffix(host, ".ollama.ai") || strings.HasSuffix(host, ".ollama.com")
		if strings.Contains(err.Error(), errtypes.UnknownOllamaKeyErrMsg) && isOllamaHost {
			// the user has not added their ollama key to ollama.com
			// re-throw an error with a more user-friendly message
			return errFromUnknownKey(err)
		}

Michael Yang's avatar
Michael Yang committed
465
466
467
		return err
	}

468
	spinner.Stop()
Michael Yang's avatar
Michael Yang committed
469
	return nil
470
471
}

472
func ListHandler(cmd *cobra.Command, args []string) error {
Michael Yang's avatar
Michael Yang committed
473
	client, err := api.ClientFromEnvironment()
474
475
476
	if err != nil {
		return err
	}
Patrick Devine's avatar
Patrick Devine committed
477

Michael Yang's avatar
Michael Yang committed
478
	models, err := client.List(cmd.Context())
Patrick Devine's avatar
Patrick Devine committed
479
480
481
482
483
484
485
	if err != nil {
		return err
	}

	var data [][]string

	for _, m := range models.Models {
Michael Yang's avatar
Michael Yang committed
486
		if len(args) == 0 || strings.HasPrefix(m.Name, args[0]) {
487
			data = append(data, []string{m.Name, m.Digest[:12], format.HumanBytes(m.Size), format.HumanTime(m.ModifiedAt, "Never")})
Michael Yang's avatar
Michael Yang committed
488
		}
Patrick Devine's avatar
Patrick Devine committed
489
490
491
	}

	table := tablewriter.NewWriter(os.Stdout)
Patrick Devine's avatar
Patrick Devine committed
492
	table.SetHeader([]string{"NAME", "ID", "SIZE", "MODIFIED"})
Patrick Devine's avatar
Patrick Devine committed
493
494
495
496
497
498
499
500
501
502
503
504
	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
}

505
func DeleteHandler(cmd *cobra.Command, args []string) error {
Michael Yang's avatar
Michael Yang committed
506
	client, err := api.ClientFromEnvironment()
507
508
509
	if err != nil {
		return err
	}
510

511
512
	for _, name := range args {
		req := api.DeleteRequest{Name: name}
Michael Yang's avatar
Michael Yang committed
513
		if err := client.Delete(cmd.Context(), &req); err != nil {
514
515
516
			return err
		}
		fmt.Printf("deleted '%s'\n", name)
517
518
519
520
	}
	return nil
}

Patrick Devine's avatar
Patrick Devine committed
521
func ShowHandler(cmd *cobra.Command, args []string) error {
Michael Yang's avatar
Michael Yang committed
522
	client, err := api.ClientFromEnvironment()
Patrick Devine's avatar
Patrick Devine committed
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
	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 {
572
		return errors.New("only one of '--license', '--modelfile', '--parameters', '--system', or '--template' can be specified")
Patrick Devine's avatar
Patrick Devine committed
573
	} else if flagsSet == 0 {
574
		return errors.New("one of '--license', '--modelfile', '--parameters', '--system', or '--template' must be specified")
Patrick Devine's avatar
Patrick Devine committed
575
576
	}

577
	req := api.ShowRequest{Name: args[0]}
Michael Yang's avatar
Michael Yang committed
578
	resp, err := client.Show(cmd.Context(), &req)
Patrick Devine's avatar
Patrick Devine committed
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
	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
}

Patrick Devine's avatar
Patrick Devine committed
599
func CopyHandler(cmd *cobra.Command, args []string) error {
Michael Yang's avatar
Michael Yang committed
600
	client, err := api.ClientFromEnvironment()
601
602
603
	if err != nil {
		return err
	}
Patrick Devine's avatar
Patrick Devine committed
604
605

	req := api.CopyRequest{Source: args[0], Destination: args[1]}
Michael Yang's avatar
Michael Yang committed
606
	if err := client.Copy(cmd.Context(), &req); err != nil {
Patrick Devine's avatar
Patrick Devine committed
607
608
609
610
611
612
		return err
	}
	fmt.Printf("copied '%s' to '%s'\n", args[0], args[1])
	return nil
}

613
func PullHandler(cmd *cobra.Command, args []string) error {
614
615
616
617
618
	insecure, err := cmd.Flags().GetBool("insecure")
	if err != nil {
		return err
	}

Michael Yang's avatar
Michael Yang committed
619
	client, err := api.ClientFromEnvironment()
620
621
622
	if err != nil {
		return err
	}
623

Michael Yang's avatar
Michael Yang committed
624
625
626
627
628
	p := progress.NewProgress(os.Stderr)
	defer p.Stop()

	bars := make(map[string]*progress.Bar)

629
630
	var status string
	var spinner *progress.Spinner
Michael Yang's avatar
Michael Yang committed
631

632
	fn := func(resp api.ProgressResponse) error {
Michael Yang's avatar
Michael Yang committed
633
		if resp.Digest != "" {
634
635
636
			if spinner != nil {
				spinner.Stop()
			}
Michael Yang's avatar
Michael Yang committed
637
638
639

			bar, ok := bars[resp.Digest]
			if !ok {
640
				bar = progress.NewBar(fmt.Sprintf("pulling %s...", resp.Digest[7:19]), resp.Total, resp.Completed)
Michael Yang's avatar
Michael Yang committed
641
642
643
644
645
646
				bars[resp.Digest] = bar
				p.Add(resp.Digest, bar)
			}

			bar.Set(resp.Completed)
		} else if status != resp.Status {
647
648
649
			if spinner != nil {
				spinner.Stop()
			}
Michael Yang's avatar
Michael Yang committed
650
651
652
653
654
655

			status = resp.Status
			spinner = progress.NewSpinner(status)
			p.Add(status, spinner)
		}

656
657
		return nil
	}
658

Michael Yang's avatar
Michael Yang committed
659
	request := api.PullRequest{Name: args[0], Insecure: insecure}
Michael Yang's avatar
Michael Yang committed
660
	if err := client.Pull(cmd.Context(), &request, fn); err != nil {
Michael Yang's avatar
Michael Yang committed
661
662
663
664
		return err
	}

	return nil
Michael Yang's avatar
Michael Yang committed
665
666
}

667
668
type generateContextKey string

669
type runOptions struct {
670
671
672
673
674
675
676
677
678
679
680
	Model       string
	ParentModel string
	Prompt      string
	Messages    []api.Message
	WordWrap    bool
	Format      string
	System      string
	Template    string
	Images      []api.ImageData
	Options     map[string]interface{}
	MultiModal  bool
681
682
}

683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
type displayResponseState struct {
	lineLength int
	wordBuffer string
}

func displayResponse(content string, wordWrap bool, state *displayResponseState) {
	termWidth, _, _ := term.GetSize(int(os.Stdout.Fd()))
	if wordWrap && termWidth >= 10 {
		for _, ch := range content {
			if state.lineLength+1 > termWidth-5 {
				if len(state.wordBuffer) > termWidth-10 {
					fmt.Printf("%s%c", state.wordBuffer, ch)
					state.wordBuffer = ""
					state.lineLength = 0
					continue
				}

				// backtrack the length of the last word and clear to the end of the line
				fmt.Printf("\x1b[%dD\x1b[K\n", len(state.wordBuffer))
				fmt.Printf("%s%c", state.wordBuffer, ch)
				state.lineLength = len(state.wordBuffer) + 1
			} else {
				fmt.Print(string(ch))
				state.lineLength += 1

				switch ch {
				case ' ':
					state.wordBuffer = ""
				case '\n':
					state.lineLength = 0
				default:
					state.wordBuffer += string(ch)
				}
			}
		}
	} else {
		fmt.Printf("%s%s", state.wordBuffer, content)
		if len(state.wordBuffer) > 0 {
			state.wordBuffer = ""
		}
	}
}

func chat(cmd *cobra.Command, opts runOptions) (*api.Message, error) {
	client, err := api.ClientFromEnvironment()
	if err != nil {
		return nil, err
	}

	p := progress.NewProgress(os.Stderr)
	defer p.StopAndClear()

	spinner := progress.NewSpinner("")
	p.Add("", spinner)

	cancelCtx, cancel := context.WithCancel(cmd.Context())
	defer cancel()

	sigChan := make(chan os.Signal, 1)
	signal.Notify(sigChan, syscall.SIGINT)

	go func() {
		<-sigChan
		cancel()
	}()

	var state *displayResponseState = &displayResponseState{}
	var latest api.ChatResponse
	var fullResponse strings.Builder
	var role string

	fn := func(response api.ChatResponse) error {
		p.StopAndClear()

		latest = response

		role = response.Message.Role
		content := response.Message.Content
		fullResponse.WriteString(content)

		displayResponse(content, opts.WordWrap, state)

		return nil
	}

	req := &api.ChatRequest{
		Model:    opts.Model,
		Messages: opts.Messages,
		Format:   opts.Format,
		Options:  opts.Options,
	}

	if err := client.Chat(cancelCtx, req, fn); err != nil {
		if errors.Is(err, context.Canceled) {
			return nil, nil
		}
		return nil, err
	}

	if len(opts.Messages) > 0 {
		fmt.Println()
		fmt.Println()
	}

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

	if verbose {
		latest.Summary()
	}

	return &api.Message{Role: role, Content: fullResponse.String()}, nil
}

func generate(cmd *cobra.Command, opts runOptions) error {
Michael Yang's avatar
Michael Yang committed
800
	client, err := api.ClientFromEnvironment()
Patrick Devine's avatar
Patrick Devine committed
801
	if err != nil {
802
		return err
Patrick Devine's avatar
Patrick Devine committed
803
	}
Michael Yang's avatar
Michael Yang committed
804

Michael Yang's avatar
Michael Yang committed
805
	p := progress.NewProgress(os.Stderr)
806
	defer p.StopAndClear()
807

Michael Yang's avatar
Michael Yang committed
808
809
810
	spinner := progress.NewSpinner("")
	p.Add("", spinner)

811
812
813
814
815
816
817
	var latest api.GenerateResponse

	generateContext, ok := cmd.Context().Value(generateContextKey("context")).([]int)
	if !ok {
		generateContext = []int{}
	}

Michael Yang's avatar
Michael Yang committed
818
	ctx, cancel := context.WithCancel(cmd.Context())
819
820
821
822
823
824
825
826
827
828
	defer cancel()

	sigChan := make(chan os.Signal, 1)
	signal.Notify(sigChan, syscall.SIGINT)

	go func() {
		<-sigChan
		cancel()
	}()

829
	var state *displayResponseState = &displayResponseState{}
830

831
	fn := func(response api.GenerateResponse) error {
Michael Yang's avatar
Michael Yang committed
832
		p.StopAndClear()
833

Patrick Devine's avatar
Patrick Devine committed
834
		latest = response
835
		content := response.Response
836

837
		displayResponse(content, opts.WordWrap, state)
838

Patrick Devine's avatar
Patrick Devine committed
839
840
		return nil
	}
841

842
843
844
845
846
847
848
	if opts.MultiModal {
		opts.Prompt, opts.Images, err = extractFileData(opts.Prompt)
		if err != nil {
			return err
		}
	}

Michael Yang's avatar
Michael Yang committed
849
850
851
852
	request := api.GenerateRequest{
		Model:    opts.Model,
		Prompt:   opts.Prompt,
		Context:  generateContext,
853
		Images:   opts.Images,
Michael Yang's avatar
Michael Yang committed
854
855
856
857
858
859
860
		Format:   opts.Format,
		System:   opts.System,
		Template: opts.Template,
		Options:  opts.Options,
	}

	if err := client.Generate(ctx, &request, fn); err != nil {
861
		if errors.Is(err, context.Canceled) {
862
			return nil
863
		}
864
		return err
Patrick Devine's avatar
Patrick Devine committed
865
	}
866

867
	if opts.Prompt != "" {
Michael Yang's avatar
Michael Yang committed
868
869
		fmt.Println()
		fmt.Println()
Patrick Devine's avatar
Patrick Devine committed
870
	}
871

872
873
874
875
	if !latest.Done {
		return nil
	}

Patrick Devine's avatar
Patrick Devine committed
876
877
	verbose, err := cmd.Flags().GetBool("verbose")
	if err != nil {
878
		return err
Patrick Devine's avatar
Patrick Devine committed
879
	}
Michael Yang's avatar
Michael Yang committed
880

Patrick Devine's avatar
Patrick Devine committed
881
882
	if verbose {
		latest.Summary()
Michael Yang's avatar
Michael Yang committed
883
	}
Michael Yang's avatar
Michael Yang committed
884

Patrick Devine's avatar
Patrick Devine committed
885
886
887
	ctx = context.WithValue(cmd.Context(), generateContextKey("context"), latest.Context)
	cmd.SetContext(ctx)

888
	return nil
Michael Yang's avatar
Michael Yang committed
889
890
}

891
func RunServer(cmd *cobra.Command, _ []string) error {
892
893
	// retrieve the OLLAMA_HOST environment variable
	ollamaHost, err := api.GetOllamaHost()
Michael Yang's avatar
Michael Yang committed
894
	if err != nil {
895
		return err
Jeffrey Morgan's avatar
Jeffrey Morgan committed
896
	}
897

Michael Yang's avatar
Michael Yang committed
898
	if err := initializeKeypair(); err != nil {
899
900
901
		return err
	}

902
	ln, err := net.Listen("tcp", net.JoinHostPort(ollamaHost.Host, ollamaHost.Port))
903
904
905
	if err != nil {
		return err
	}
Jeffrey Morgan's avatar
Jeffrey Morgan committed
906

907
	return server.Serve(ln)
Jeffrey Morgan's avatar
Jeffrey Morgan committed
908
909
}

910
911
912
913
914
915
916
917
918
919
920
921
func initializeKeypair() error {
	home, err := os.UserHomeDir()
	if err != nil {
		return err
	}

	privKeyPath := filepath.Join(home, ".ollama", "id_ed25519")
	pubKeyPath := filepath.Join(home, ".ollama", "id_ed25519.pub")

	_, err = os.Stat(privKeyPath)
	if os.IsNotExist(err) {
		fmt.Printf("Couldn't find '%s'. Generating new private key.\n", privKeyPath)
Michael Yang's avatar
Michael Yang committed
922
		cryptoPublicKey, cryptoPrivateKey, err := ed25519.GenerateKey(rand.Reader)
923
924
925
926
		if err != nil {
			return err
		}

Michael Yang's avatar
Michael Yang committed
927
		privateKeyBytes, err := ssh.MarshalPrivateKey(cryptoPrivateKey, "")
928
929
930
931
		if err != nil {
			return err
		}

Michael Yang's avatar
Michael Yang committed
932
		if err := os.MkdirAll(filepath.Dir(privKeyPath), 0o755); err != nil {
933
934
935
			return fmt.Errorf("could not create directory %w", err)
		}

Michael Yang's avatar
Michael Yang committed
936
		if err := os.WriteFile(privKeyPath, pem.EncodeToMemory(privateKeyBytes), 0o600); err != nil {
937
938
939
			return err
		}

Michael Yang's avatar
Michael Yang committed
940
		sshPublicKey, err := ssh.NewPublicKey(cryptoPublicKey)
941
942
943
944
		if err != nil {
			return err
		}

Michael Yang's avatar
Michael Yang committed
945
		publicKeyBytes := ssh.MarshalAuthorizedKey(sshPublicKey)
946

Michael Yang's avatar
Michael Yang committed
947
		if err := os.WriteFile(pubKeyPath, publicKeyBytes, 0o644); err != nil {
948
949
950
			return err
		}

Michael Yang's avatar
Michael Yang committed
951
		fmt.Printf("Your new public key is: \n\n%s\n", publicKeyBytes)
952
953
954
955
	}
	return nil
}

956
957
//nolint:unused
func waitForServer(ctx context.Context, client *api.Client) error {
Bruce MacDonald's avatar
Bruce MacDonald committed
958
959
960
961
962
963
964
965
	// wait for the server to start
	timeout := time.After(5 * time.Second)
	tick := time.Tick(500 * time.Millisecond)
	for {
		select {
		case <-timeout:
			return errors.New("timed out waiting for server to start")
		case <-tick:
Michael Yang's avatar
Michael Yang committed
966
			if err := client.Heartbeat(ctx); err == nil {
Bruce MacDonald's avatar
Bruce MacDonald committed
967
968
969
970
				return nil // server has started
			}
		}
	}
971

Bruce MacDonald's avatar
Bruce MacDonald committed
972
973
}

Michael Yang's avatar
Michael Yang committed
974
func checkServerHeartbeat(cmd *cobra.Command, _ []string) error {
Michael Yang's avatar
Michael Yang committed
975
	client, err := api.ClientFromEnvironment()
976
977
978
	if err != nil {
		return err
	}
Michael Yang's avatar
Michael Yang committed
979
	if err := client.Heartbeat(cmd.Context()); err != nil {
980
		if !strings.Contains(err.Error(), " refused") {
Bruce MacDonald's avatar
Bruce MacDonald committed
981
982
			return err
		}
983
984
		if err := startApp(cmd.Context(), client); err != nil {
			return fmt.Errorf("could not connect to ollama app, is it running?")
985
986
987
988
989
		}
	}
	return nil
}

Michael Yang's avatar
Michael Yang committed
990
991
992
993
994
995
996
997
func versionHandler(cmd *cobra.Command, _ []string) {
	client, err := api.ClientFromEnvironment()
	if err != nil {
		return
	}

	serverVersion, err := client.Version(cmd.Context())
	if err != nil {
Michael Yang's avatar
Michael Yang committed
998
999
1000
1001
1002
		fmt.Println("Warning: could not connect to a running Ollama instance")
	}

	if serverVersion != "" {
		fmt.Printf("ollama version is %s\n", serverVersion)
Michael Yang's avatar
Michael Yang committed
1003
1004
	}

1005
	if serverVersion != version.Version {
Michael Yang's avatar
Michael Yang committed
1006
		fmt.Printf("Warning: client version is %s\n", version.Version)
1007
	}
Michael Yang's avatar
Michael Yang committed
1008
1009
}

1010
1011
1012
1013
1014
1015
1016
1017
func appendHostEnvDocs(cmd *cobra.Command) {
	const hostEnvDocs = `
Environment Variables:
      OLLAMA_HOST        The host:port or base URL of the Ollama server (e.g. http://localhost:11434)
`
	cmd.SetUsageTemplate(cmd.UsageTemplate() + hostEnvDocs)
}

Jeffrey Morgan's avatar
Jeffrey Morgan committed
1018
1019
func NewCLI() *cobra.Command {
	log.SetFlags(log.LstdFlags | log.Lshortfile)
Michael Yang's avatar
Michael Yang committed
1020
	cobra.EnableCommandSorting = false
Jeffrey Morgan's avatar
Jeffrey Morgan committed
1021

1022
	if runtime.GOOS == "windows" {
1023
		console.ConsoleFromFile(os.Stdin) //nolint:errcheck
1024
1025
	}

Jeffrey Morgan's avatar
Jeffrey Morgan committed
1026
	rootCmd := &cobra.Command{
1027
1028
1029
1030
		Use:           "ollama",
		Short:         "Large language model runner",
		SilenceUsage:  true,
		SilenceErrors: true,
Jeffrey Morgan's avatar
Jeffrey Morgan committed
1031
1032
1033
		CompletionOptions: cobra.CompletionOptions{
			DisableDefaultCmd: true,
		},
Michael Yang's avatar
Michael Yang committed
1034
1035
1036
1037
1038
1039
1040
1041
		Run: func(cmd *cobra.Command, args []string) {
			if version, _ := cmd.Flags().GetBool("version"); version {
				versionHandler(cmd, args)
				return
			}

			cmd.Print(cmd.UsageString())
		},
Jeffrey Morgan's avatar
Jeffrey Morgan committed
1042
1043
	}

Michael Yang's avatar
Michael Yang committed
1044
	rootCmd.Flags().BoolP("version", "v", false, "Show version information")
Jeffrey Morgan's avatar
Jeffrey Morgan committed
1045

1046
	createCmd := &cobra.Command{
1047
1048
		Use:     "create MODEL",
		Short:   "Create a model from a Modelfile",
Michael Yang's avatar
Michael Yang committed
1049
		Args:    cobra.ExactArgs(1),
1050
1051
		PreRunE: checkServerHeartbeat,
		RunE:    CreateHandler,
1052
1053
1054
	}

	createCmd.Flags().StringP("file", "f", "Modelfile", "Name of the Modelfile (default \"Modelfile\")")
Michael Yang's avatar
Michael Yang committed
1055
	createCmd.Flags().StringP("quantization", "q", "", "Quantization level.")
1056

Patrick Devine's avatar
Patrick Devine committed
1057
1058
1059
	showCmd := &cobra.Command{
		Use:     "show MODEL",
		Short:   "Show information for a model",
Michael Yang's avatar
Michael Yang committed
1060
		Args:    cobra.ExactArgs(1),
Patrick Devine's avatar
Patrick Devine committed
1061
1062
1063
1064
1065
1066
1067
1068
		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")
1069
	showCmd.Flags().Bool("system", false, "Show system message of a model")
Patrick Devine's avatar
Patrick Devine committed
1070

Jeffrey Morgan's avatar
Jeffrey Morgan committed
1071
	runCmd := &cobra.Command{
1072
1073
1074
1075
1076
		Use:     "run MODEL [PROMPT]",
		Short:   "Run a model",
		Args:    cobra.MinimumNArgs(1),
		PreRunE: checkServerHeartbeat,
		RunE:    RunHandler,
Jeffrey Morgan's avatar
Jeffrey Morgan committed
1077
1078
	}

1079
	runCmd.Flags().Bool("verbose", false, "Show timings for response")
1080
	runCmd.Flags().Bool("insecure", false, "Use an insecure registry")
1081
	runCmd.Flags().Bool("nowordwrap", false, "Don't wrap words to the next line automatically")
Jeffrey Morgan's avatar
Jeffrey Morgan committed
1082
	runCmd.Flags().String("format", "", "Response format (e.g. json)")
Jeffrey Morgan's avatar
Jeffrey Morgan committed
1083
1084
1085
1086
	serveCmd := &cobra.Command{
		Use:     "serve",
		Aliases: []string{"start"},
		Short:   "Start ollama",
Michael Yang's avatar
Michael Yang committed
1087
		Args:    cobra.ExactArgs(0),
Michael Yang's avatar
Michael Yang committed
1088
		RunE:    RunServer,
Jeffrey Morgan's avatar
Jeffrey Morgan committed
1089
	}
1090
1091
1092
	serveCmd.SetUsageTemplate(serveCmd.UsageTemplate() + `
Environment Variables:

1093
1094
1095
1096
    OLLAMA_HOST         The host:port to bind to (default "127.0.0.1:11434")
    OLLAMA_ORIGINS      A comma separated list of allowed origins.
    OLLAMA_MODELS       The path to the models directory (default is "~/.ollama/models")
    OLLAMA_KEEP_ALIVE   The duration that models stay loaded in memory (default is "5m")
1097
    OLLAMA_DEBUG        Set to 1 to enable additional debug logging
1098
`)
Jeffrey Morgan's avatar
Jeffrey Morgan committed
1099

1100
	pullCmd := &cobra.Command{
1101
1102
		Use:     "pull MODEL",
		Short:   "Pull a model from a registry",
Michael Yang's avatar
Michael Yang committed
1103
		Args:    cobra.ExactArgs(1),
1104
1105
		PreRunE: checkServerHeartbeat,
		RunE:    PullHandler,
1106
1107
	}

1108
1109
	pullCmd.Flags().Bool("insecure", false, "Use an insecure registry")

1110
	pushCmd := &cobra.Command{
1111
1112
		Use:     "push MODEL",
		Short:   "Push a model to a registry",
Michael Yang's avatar
Michael Yang committed
1113
		Args:    cobra.ExactArgs(1),
1114
1115
		PreRunE: checkServerHeartbeat,
		RunE:    PushHandler,
1116
1117
	}

1118
1119
	pushCmd.Flags().Bool("insecure", false, "Use an insecure registry")

Patrick Devine's avatar
Patrick Devine committed
1120
	listCmd := &cobra.Command{
1121
		Use:     "list",
Patrick Devine's avatar
Patrick Devine committed
1122
		Aliases: []string{"ls"},
1123
		Short:   "List models",
1124
		PreRunE: checkServerHeartbeat,
1125
		RunE:    ListHandler,
1126
	}
Patrick Devine's avatar
Patrick Devine committed
1127
	copyCmd := &cobra.Command{
Michael Yang's avatar
Michael Yang committed
1128
		Use:     "cp SOURCE TARGET",
1129
		Short:   "Copy a model",
Michael Yang's avatar
Michael Yang committed
1130
		Args:    cobra.ExactArgs(2),
1131
1132
		PreRunE: checkServerHeartbeat,
		RunE:    CopyHandler,
Patrick Devine's avatar
Patrick Devine committed
1133
1134
	}

1135
	deleteCmd := &cobra.Command{
Michael Yang's avatar
Michael Yang committed
1136
		Use:     "rm MODEL [MODEL...]",
1137
1138
1139
1140
		Short:   "Remove a model",
		Args:    cobra.MinimumNArgs(1),
		PreRunE: checkServerHeartbeat,
		RunE:    DeleteHandler,
Patrick Devine's avatar
Patrick Devine committed
1141
1142
	}

1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
	for _, cmd := range []*cobra.Command{
		createCmd,
		showCmd,
		runCmd,
		pullCmd,
		pushCmd,
		listCmd,
		copyCmd,
		deleteCmd,
	} {
		appendHostEnvDocs(cmd)
	}

Jeffrey Morgan's avatar
Jeffrey Morgan committed
1156
1157
	rootCmd.AddCommand(
		serveCmd,
1158
		createCmd,
Patrick Devine's avatar
Patrick Devine committed
1159
		showCmd,
1160
		runCmd,
1161
1162
		pullCmd,
		pushCmd,
Patrick Devine's avatar
Patrick Devine committed
1163
		listCmd,
Patrick Devine's avatar
Patrick Devine committed
1164
		copyCmd,
1165
		deleteCmd,
Jeffrey Morgan's avatar
Jeffrey Morgan committed
1166
1167
1168
1169
	)

	return rootCmd
}