cmd.go 6.7 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"
Michael Yang's avatar
Michael Yang committed
12
	"strings"
Michael Yang's avatar
Michael Yang committed
13
	"time"
Jeffrey Morgan's avatar
Jeffrey Morgan committed
14

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

Jeffrey Morgan's avatar
Jeffrey Morgan committed
21
	"github.com/jmorganca/ollama/api"
Patrick Devine's avatar
Patrick Devine committed
22
	"github.com/jmorganca/ollama/format"
Jeffrey Morgan's avatar
Jeffrey Morgan committed
23
	"github.com/jmorganca/ollama/server"
Jeffrey Morgan's avatar
Jeffrey Morgan committed
24
25
)

26
27
28
29
func create(cmd *cobra.Command, args []string) error {
	filename, _ := cmd.Flags().GetString("file")
	client := api.NewClient()

Michael Yang's avatar
Michael Yang committed
30
31
	var spinner *Spinner

32
33
	request := api.CreateRequest{Name: args[0], Path: filename}
	fn := func(resp api.CreateProgress) error {
Michael Yang's avatar
Michael Yang committed
34
35
36
37
38
39
40
		if spinner != nil {
			spinner.Stop()
		}

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

41
42
43
44
45
46
47
		return nil
	}

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

Michael Yang's avatar
Michael Yang committed
48
49
50
51
	if spinner != nil {
		spinner.Stop()
	}

52
53
54
	return nil
}

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

	return RunGenerate(cmd, args)
Bruce MacDonald's avatar
Bruce MacDonald committed
80
81
}

82
83
84
85
func push(cmd *cobra.Command, args []string) error {
	client := api.NewClient()

	request := api.PushRequest{Name: args[0]}
86
	fn := func(resp api.ProgressResponse) error {
87
88
89
90
91
92
93
94
95
96
		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
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
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
}

125
126
127
128
func RunPull(cmd *cobra.Command, args []string) error {
	return pull(args[0])
}

Michael Yang's avatar
Michael Yang committed
129
func pull(model string) error {
Bruce MacDonald's avatar
Bruce MacDonald committed
130
	client := api.NewClient()
131

132
	var currentDigest string
Bruce MacDonald's avatar
Bruce MacDonald committed
133
	var bar *progressbar.ProgressBar
Michael Yang's avatar
Michael Yang committed
134

135
	request := api.PullRequest{Name: model}
136
137
138
	fn := func(resp api.ProgressResponse) error {
		if resp.Digest != currentDigest && resp.Digest != "" {
			currentDigest = resp.Digest
139
140
			bar = progressbar.DefaultBytes(
				int64(resp.Total),
141
				fmt.Sprintf("pulling %s...", resp.Digest[7:19]),
142
			)
143
144
145

			bar.Set(resp.Completed)
		} else if resp.Digest == currentDigest && resp.Digest != "" {
146
147
			bar.Set(resp.Completed)
		} else {
148
			currentDigest = ""
149
150
151
152
			fmt.Println(resp.Status)
		}
		return nil
	}
153

154
155
156
157
	if err := client.Pull(context.Background(), &request, fn); err != nil {
		return err
	}
	return nil
Michael Yang's avatar
Michael Yang committed
158
159
}

160
func RunGenerate(cmd *cobra.Command, args []string) error {
Michael Yang's avatar
Michael Yang committed
161
	if len(args) > 1 {
Michael Yang's avatar
Michael Yang committed
162
		// join all args into a single prompt
163
		return generate(cmd, args[0], strings.Join(args[1:], " "))
Michael Yang's avatar
Michael Yang committed
164
165
166
	}

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

170
	return generateBatch(cmd, args[0])
Michael Yang's avatar
Michael Yang committed
171
172
}

Michael Yang's avatar
Michael Yang committed
173
174
var generateContextKey struct{}

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

Michael Yang's avatar
Michael Yang committed
179
180
		spinner := NewSpinner("")
		go spinner.Spin(60 * time.Millisecond)
Michael Yang's avatar
Michael Yang committed
181

182
183
		var latest api.GenerateResponse

Michael Yang's avatar
Michael Yang committed
184
185
186
187
188
189
		generateContext, ok := cmd.Context().Value(generateContextKey).([]int)
		if !ok {
			generateContext = []int{}
		}

		request := api.GenerateRequest{Model: model, Prompt: prompt, Context: generateContext}
190
		fn := func(resp api.GenerateResponse) error {
Michael Yang's avatar
Michael Yang committed
191
192
193
			if !spinner.IsFinished() {
				spinner.Finish()
			}
Michael Yang's avatar
Michael Yang committed
194

195
196
			latest = resp

Michael Yang's avatar
Michael Yang committed
197
			fmt.Print(resp.Response)
Michael Yang's avatar
Michael Yang committed
198
199

			cmd.SetContext(context.WithValue(cmd.Context(), generateContextKey, resp.Context))
Michael Yang's avatar
Michael Yang committed
200
			return nil
201
202
203
204
205
		}

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

Michael Yang's avatar
Michael Yang committed
207
208
		fmt.Println()
		fmt.Println()
209
210
211
212
213
214
215
216
217

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

		if verbose {
			latest.Summary()
		}
Michael Yang's avatar
Michael Yang committed
218
	}
Michael Yang's avatar
Michael Yang committed
219
220
221
222

	return nil
}

223
func generateInteractive(cmd *cobra.Command, model string) error {
Michael Yang's avatar
Michael Yang committed
224
225
226
	fmt.Print(">>> ")
	scanner := bufio.NewScanner(os.Stdin)
	for scanner.Scan() {
227
		if err := generate(cmd, model, scanner.Text()); err != nil {
Michael Yang's avatar
Michael Yang committed
228
229
230
231
232
233
			return err
		}

		fmt.Print(">>> ")
	}

Bruce MacDonald's avatar
Bruce MacDonald committed
234
	return nil
Bruce MacDonald's avatar
Bruce MacDonald committed
235
236
}

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

	return nil
}

func RunServer(_ *cobra.Command, _ []string) error {
251
252
253
254
255
256
257
258
259
260
261
	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
262
263
264
265
266
267
268
	if err != nil {
		return err
	}

	return server.Serve(ln)
}

Jeffrey Morgan's avatar
Jeffrey Morgan committed
269
270
271
272
func NewCLI() *cobra.Command {
	log.SetFlags(log.LstdFlags | log.Lshortfile)

	rootCmd := &cobra.Command{
Michael Yang's avatar
Michael Yang committed
273
274
275
		Use:          "ollama",
		Short:        "Large language model runner",
		SilenceUsage: true,
Jeffrey Morgan's avatar
Jeffrey Morgan committed
276
277
278
279
280
281
282
		CompletionOptions: cobra.CompletionOptions{
			DisableDefaultCmd: true,
		},
	}

	cobra.EnableCommandSorting = false

283
284
285
286
287
288
289
290
291
	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
292
	runCmd := &cobra.Command{
Michael Yang's avatar
Michael Yang committed
293
		Use:   "run MODEL [PROMPT]",
Jeffrey Morgan's avatar
Jeffrey Morgan committed
294
		Short: "Run a model",
Michael Yang's avatar
Michael Yang committed
295
296
		Args:  cobra.MinimumNArgs(1),
		RunE:  RunRun,
Jeffrey Morgan's avatar
Jeffrey Morgan committed
297
298
	}

299
300
	runCmd.Flags().Bool("verbose", false, "Show timings for response")

Jeffrey Morgan's avatar
Jeffrey Morgan committed
301
302
303
304
	serveCmd := &cobra.Command{
		Use:     "serve",
		Aliases: []string{"start"},
		Short:   "Start ollama",
Michael Yang's avatar
Michael Yang committed
305
		RunE:    RunServer,
Jeffrey Morgan's avatar
Jeffrey Morgan committed
306
307
	}

308
309
310
311
312
313
314
315
316
317
318
319
320
321
	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
322
323
324
325
326
327
	listCmd := &cobra.Command{
		Use:   "list",
		Short: "List models",
		RunE:  list,
	}

Jeffrey Morgan's avatar
Jeffrey Morgan committed
328
329
	rootCmd.AddCommand(
		serveCmd,
330
		createCmd,
331
		runCmd,
332
333
		pullCmd,
		pushCmd,
Patrick Devine's avatar
Patrick Devine committed
334
		listCmd,
Jeffrey Morgan's avatar
Jeffrey Morgan committed
335
336
337
338
	)

	return rootCmd
}