cmd.go 6.79 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"
Jeffrey Morgan's avatar
Jeffrey Morgan committed
8
9
	"log"
	"net"
Michael Yang's avatar
Michael Yang committed
10
	"net/http"
Jeffrey Morgan's avatar
Jeffrey Morgan committed
11
	"os"
12
	"path/filepath"
Michael Yang's avatar
Michael Yang committed
13
	"strings"
Michael Yang's avatar
Michael Yang committed
14
	"time"
Jeffrey Morgan's avatar
Jeffrey Morgan committed
15

Patrick Devine's avatar
Patrick Devine committed
16
17
	"github.com/dustin/go-humanize"
	"github.com/olekukonko/tablewriter"
Michael Yang's avatar
Michael Yang committed
18
	"github.com/schollz/progressbar/v3"
Michael Yang's avatar
Michael Yang committed
19
20
21
	"github.com/spf13/cobra"
	"golang.org/x/term"

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

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

34
35
	client := api.NewClient()

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

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

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

47
48
49
50
51
52
53
		return nil
	}

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

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

58
59
60
	return nil
}

Michael Yang's avatar
Michael Yang committed
61
func RunRun(cmd *cobra.Command, args []string) error {
Patrick Devine's avatar
Patrick Devine committed
62
63
64
65
66
67
68
	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
69
70
71
	switch {
	case errors.Is(err, os.ErrNotExist):
		if err := pull(args[0]); err != nil {
Michael Yang's avatar
Michael Yang committed
72
73
74
75
76
77
78
79
			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
80
81
		}
	case err != nil:
Michael Yang's avatar
Michael Yang committed
82
83
84
85
		return err
	}

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

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

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

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

Patrick Devine's avatar
Patrick Devine committed
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
func list(cmd *cobra.Command, args []string) error {
	client := api.NewClient()

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

	var data [][]string

	for _, m := range models.Models {
		data = append(data, []string{m.Name, humanize.Bytes(uint64(m.Size)), format.HumanTime(m.ModifiedAt, "Never")})
	}

	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
}

131
132
133
134
func RunPull(cmd *cobra.Command, args []string) error {
	return pull(args[0])
}

Michael Yang's avatar
Michael Yang committed
135
func pull(model string) error {
Bruce MacDonald's avatar
Bruce MacDonald committed
136
	client := api.NewClient()
137

138
	var currentDigest string
Bruce MacDonald's avatar
Bruce MacDonald committed
139
	var bar *progressbar.ProgressBar
Michael Yang's avatar
Michael Yang committed
140

141
	request := api.PullRequest{Name: model}
142
143
144
	fn := func(resp api.ProgressResponse) error {
		if resp.Digest != currentDigest && resp.Digest != "" {
			currentDigest = resp.Digest
145
146
			bar = progressbar.DefaultBytes(
				int64(resp.Total),
147
				fmt.Sprintf("pulling %s...", resp.Digest[7:19]),
148
			)
149
150
151

			bar.Set(resp.Completed)
		} else if resp.Digest == currentDigest && resp.Digest != "" {
152
153
			bar.Set(resp.Completed)
		} else {
154
			currentDigest = ""
155
156
157
158
			fmt.Println(resp.Status)
		}
		return nil
	}
159

160
161
162
163
	if err := client.Pull(context.Background(), &request, fn); err != nil {
		return err
	}
	return nil
Michael Yang's avatar
Michael Yang committed
164
165
}

166
func RunGenerate(cmd *cobra.Command, args []string) error {
Michael Yang's avatar
Michael Yang committed
167
	if len(args) > 1 {
Michael Yang's avatar
Michael Yang committed
168
		// join all args into a single prompt
169
		return generate(cmd, args[0], strings.Join(args[1:], " "))
Michael Yang's avatar
Michael Yang committed
170
171
172
	}

	if term.IsTerminal(int(os.Stdin.Fd())) {
173
		return generateInteractive(cmd, args[0])
Michael Yang's avatar
Michael Yang committed
174
175
	}

176
	return generateBatch(cmd, args[0])
Michael Yang's avatar
Michael Yang committed
177
178
}

Michael Yang's avatar
Michael Yang committed
179
180
var generateContextKey struct{}

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

Michael Yang's avatar
Michael Yang committed
185
186
		spinner := NewSpinner("")
		go spinner.Spin(60 * time.Millisecond)
Michael Yang's avatar
Michael Yang committed
187

188
189
		var latest api.GenerateResponse

Michael Yang's avatar
Michael Yang committed
190
191
192
193
194
195
		generateContext, ok := cmd.Context().Value(generateContextKey).([]int)
		if !ok {
			generateContext = []int{}
		}

		request := api.GenerateRequest{Model: model, Prompt: prompt, Context: generateContext}
196
		fn := func(resp api.GenerateResponse) error {
Michael Yang's avatar
Michael Yang committed
197
198
199
			if !spinner.IsFinished() {
				spinner.Finish()
			}
Michael Yang's avatar
Michael Yang committed
200

201
202
			latest = resp

Michael Yang's avatar
Michael Yang committed
203
			fmt.Print(resp.Response)
Michael Yang's avatar
Michael Yang committed
204
205

			cmd.SetContext(context.WithValue(cmd.Context(), generateContextKey, resp.Context))
Michael Yang's avatar
Michael Yang committed
206
			return nil
207
208
209
210
211
		}

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

Michael Yang's avatar
Michael Yang committed
213
214
		fmt.Println()
		fmt.Println()
215
216
217
218
219
220
221
222
223

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

		if verbose {
			latest.Summary()
		}
Michael Yang's avatar
Michael Yang committed
224
	}
Michael Yang's avatar
Michael Yang committed
225
226
227
228

	return nil
}

229
func generateInteractive(cmd *cobra.Command, model string) error {
Michael Yang's avatar
Michael Yang committed
230
231
232
	fmt.Print(">>> ")
	scanner := bufio.NewScanner(os.Stdin)
	for scanner.Scan() {
233
		if err := generate(cmd, model, scanner.Text()); err != nil {
Michael Yang's avatar
Michael Yang committed
234
235
236
237
238
239
			return err
		}

		fmt.Print(">>> ")
	}

Bruce MacDonald's avatar
Bruce MacDonald committed
240
	return nil
Bruce MacDonald's avatar
Bruce MacDonald committed
241
242
}

243
func generateBatch(cmd *cobra.Command, model string) error {
Michael Yang's avatar
Michael Yang committed
244
245
246
247
	scanner := bufio.NewScanner(os.Stdin)
	for scanner.Scan() {
		prompt := scanner.Text()
		fmt.Printf(">>> %s\n", prompt)
248
		if err := generate(cmd, model, prompt); err != nil {
Michael Yang's avatar
Michael Yang committed
249
250
251
252
253
254
255
256
			return err
		}
	}

	return nil
}

func RunServer(_ *cobra.Command, _ []string) error {
257
258
259
260
261
262
263
264
265
266
267
	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
268
269
270
271
272
273
274
	if err != nil {
		return err
	}

	return server.Serve(ln)
}

Jeffrey Morgan's avatar
Jeffrey Morgan committed
275
276
277
278
func NewCLI() *cobra.Command {
	log.SetFlags(log.LstdFlags | log.Lshortfile)

	rootCmd := &cobra.Command{
Michael Yang's avatar
Michael Yang committed
279
280
281
		Use:          "ollama",
		Short:        "Large language model runner",
		SilenceUsage: true,
Jeffrey Morgan's avatar
Jeffrey Morgan committed
282
283
284
285
286
287
288
		CompletionOptions: cobra.CompletionOptions{
			DisableDefaultCmd: true,
		},
	}

	cobra.EnableCommandSorting = false

289
290
291
292
293
294
295
296
297
	createCmd := &cobra.Command{
		Use:   "create MODEL",
		Short: "Create a model from a Modelfile",
		Args:  cobra.MinimumNArgs(1),
		RunE:  create,
	}

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

Jeffrey Morgan's avatar
Jeffrey Morgan committed
298
	runCmd := &cobra.Command{
Michael Yang's avatar
Michael Yang committed
299
		Use:   "run MODEL [PROMPT]",
Jeffrey Morgan's avatar
Jeffrey Morgan committed
300
		Short: "Run a model",
Michael Yang's avatar
Michael Yang committed
301
302
		Args:  cobra.MinimumNArgs(1),
		RunE:  RunRun,
Jeffrey Morgan's avatar
Jeffrey Morgan committed
303
304
	}

305
306
	runCmd.Flags().Bool("verbose", false, "Show timings for response")

Jeffrey Morgan's avatar
Jeffrey Morgan committed
307
308
309
310
	serveCmd := &cobra.Command{
		Use:     "serve",
		Aliases: []string{"start"},
		Short:   "Start ollama",
Michael Yang's avatar
Michael Yang committed
311
		RunE:    RunServer,
Jeffrey Morgan's avatar
Jeffrey Morgan committed
312
313
	}

314
315
316
317
318
319
320
321
322
323
324
325
326
327
	pullCmd := &cobra.Command{
		Use:   "pull MODEL",
		Short: "Pull a model from a registry",
		Args:  cobra.MinimumNArgs(1),
		RunE:  RunPull,
	}

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

Patrick Devine's avatar
Patrick Devine committed
328
329
330
331
332
333
	listCmd := &cobra.Command{
		Use:   "list",
		Short: "List models",
		RunE:  list,
	}

Jeffrey Morgan's avatar
Jeffrey Morgan committed
334
335
	rootCmd.AddCommand(
		serveCmd,
336
		createCmd,
337
		runCmd,
338
339
		pullCmd,
		pushCmd,
Patrick Devine's avatar
Patrick Devine committed
340
		listCmd,
Jeffrey Morgan's avatar
Jeffrey Morgan committed
341
342
343
344
	)

	return rootCmd
}