cmd.go 6.27 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
	"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

Michael Yang's avatar
Michael Yang committed
16
	"github.com/schollz/progressbar/v3"
Michael Yang's avatar
Michael Yang committed
17
18
19
	"github.com/spf13/cobra"
	"golang.org/x/term"

Jeffrey Morgan's avatar
Jeffrey Morgan committed
20
21
	"github.com/jmorganca/ollama/api"
	"github.com/jmorganca/ollama/server"
Jeffrey Morgan's avatar
Jeffrey Morgan committed
22
23
)

Bruce MacDonald's avatar
Bruce MacDonald committed
24
func cacheDir() string {
Jeffrey Morgan's avatar
Jeffrey Morgan committed
25
26
27
28
29
	home, err := os.UserHomeDir()
	if err != nil {
		panic(err)
	}

Michael Yang's avatar
Michael Yang committed
30
	return filepath.Join(home, ".ollama")
Jeffrey Morgan's avatar
Jeffrey Morgan committed
31
32
}

33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
func create(cmd *cobra.Command, args []string) error {
	filename, _ := cmd.Flags().GetString("file")
	client := api.NewClient()

	request := api.CreateRequest{Name: args[0], Path: filename}
	fn := func(resp api.CreateProgress) error {
		fmt.Println(resp.Status)
		return nil
	}

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

	return nil
}

Michael Yang's avatar
Michael Yang committed
50
func RunRun(cmd *cobra.Command, args []string) error {
Michael Yang's avatar
Michael Yang committed
51
52
53
54
	_, err := os.Stat(args[0])
	switch {
	case errors.Is(err, os.ErrNotExist):
		if err := pull(args[0]); err != nil {
Michael Yang's avatar
Michael Yang committed
55
56
57
58
59
60
61
62
			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
63
64
		}
	case err != nil:
Michael Yang's avatar
Michael Yang committed
65
66
67
68
		return err
	}

	return RunGenerate(cmd, args)
Bruce MacDonald's avatar
Bruce MacDonald committed
69
70
}

71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
func push(cmd *cobra.Command, args []string) error {
	client := api.NewClient()

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

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

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

Michael Yang's avatar
Michael Yang committed
90
func pull(model string) error {
Bruce MacDonald's avatar
Bruce MacDonald committed
91
	client := api.NewClient()
92

Bruce MacDonald's avatar
Bruce MacDonald committed
93
	var bar *progressbar.ProgressBar
Michael Yang's avatar
Michael Yang committed
94

95
96
97
98
99
100
	currentLayer := ""
	request := api.PullRequest{Name: model}
	fn := func(resp api.PullProgress) error {
		if resp.Digest != currentLayer && resp.Digest != "" {
			if currentLayer != "" {
				fmt.Println()
Bruce MacDonald's avatar
Bruce MacDonald committed
101
			}
102
103
104
105
106
107
108
109
110
111
112
113
114
115
			currentLayer = resp.Digest
			layerStr := resp.Digest[7:23] + "..."
			bar = progressbar.DefaultBytes(
				int64(resp.Total),
				"pulling "+layerStr,
			)
		} else if resp.Digest == currentLayer && resp.Digest != "" {
			bar.Set(resp.Completed)
		} else {
			currentLayer = ""
			fmt.Println(resp.Status)
		}
		return nil
	}
116

117
118
119
120
	if err := client.Pull(context.Background(), &request, fn); err != nil {
		return err
	}
	return nil
Michael Yang's avatar
Michael Yang committed
121
122
}

123
func RunGenerate(cmd *cobra.Command, args []string) error {
Michael Yang's avatar
Michael Yang committed
124
	if len(args) > 1 {
Michael Yang's avatar
Michael Yang committed
125
		// join all args into a single prompt
126
		return generate(cmd, args[0], strings.Join(args[1:], " "))
Michael Yang's avatar
Michael Yang committed
127
128
129
	}

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

133
	return generateBatch(cmd, args[0])
Michael Yang's avatar
Michael Yang committed
134
135
}

Michael Yang's avatar
Michael Yang committed
136
137
var generateContextKey struct{}

138
func generate(cmd *cobra.Command, model, prompt string) error {
Michael Yang's avatar
Michael Yang committed
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
	if len(strings.TrimSpace(prompt)) > 0 {
		client := api.NewClient()

		spinner := progressbar.NewOptions(-1,
			progressbar.OptionSetWriter(os.Stderr),
			progressbar.OptionThrottle(60*time.Millisecond),
			progressbar.OptionSpinnerType(14),
			progressbar.OptionSetRenderBlankState(true),
			progressbar.OptionSetElapsedTime(false),
			progressbar.OptionClearOnFinish(),
		)

		go func() {
			for range time.Tick(60 * time.Millisecond) {
				if spinner.IsFinished() {
					break
				}

				spinner.Add(1)
Michael Yang's avatar
Michael Yang committed
158
			}
Michael Yang's avatar
Michael Yang committed
159
		}()
Michael Yang's avatar
Michael Yang committed
160

161
162
		var latest api.GenerateResponse

Michael Yang's avatar
Michael Yang committed
163
164
165
166
167
168
		generateContext, ok := cmd.Context().Value(generateContextKey).([]int)
		if !ok {
			generateContext = []int{}
		}

		request := api.GenerateRequest{Model: model, Prompt: prompt, Context: generateContext}
169
		fn := func(resp api.GenerateResponse) error {
Michael Yang's avatar
Michael Yang committed
170
171
172
			if !spinner.IsFinished() {
				spinner.Finish()
			}
Michael Yang's avatar
Michael Yang committed
173

174
175
			latest = resp

Michael Yang's avatar
Michael Yang committed
176
			fmt.Print(resp.Response)
Michael Yang's avatar
Michael Yang committed
177
178

			cmd.SetContext(context.WithValue(cmd.Context(), generateContextKey, resp.Context))
Michael Yang's avatar
Michael Yang committed
179
			return nil
180
181
182
183
184
		}

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

Michael Yang's avatar
Michael Yang committed
186
187
		fmt.Println()
		fmt.Println()
188
189
190
191
192
193
194
195
196

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

		if verbose {
			latest.Summary()
		}
Michael Yang's avatar
Michael Yang committed
197
	}
Michael Yang's avatar
Michael Yang committed
198
199
200
201

	return nil
}

202
func generateInteractive(cmd *cobra.Command, model string) error {
Michael Yang's avatar
Michael Yang committed
203
204
205
	fmt.Print(">>> ")
	scanner := bufio.NewScanner(os.Stdin)
	for scanner.Scan() {
206
		if err := generate(cmd, model, scanner.Text()); err != nil {
Michael Yang's avatar
Michael Yang committed
207
208
209
210
211
212
			return err
		}

		fmt.Print(">>> ")
	}

Bruce MacDonald's avatar
Bruce MacDonald committed
213
	return nil
Bruce MacDonald's avatar
Bruce MacDonald committed
214
215
}

216
func generateBatch(cmd *cobra.Command, model string) error {
Michael Yang's avatar
Michael Yang committed
217
218
219
220
	scanner := bufio.NewScanner(os.Stdin)
	for scanner.Scan() {
		prompt := scanner.Text()
		fmt.Printf(">>> %s\n", prompt)
221
		if err := generate(cmd, model, prompt); err != nil {
Michael Yang's avatar
Michael Yang committed
222
223
224
225
226
227
228
229
			return err
		}
	}

	return nil
}

func RunServer(_ *cobra.Command, _ []string) error {
230
231
232
233
234
235
236
237
238
239
240
	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
241
242
243
244
245
246
247
	if err != nil {
		return err
	}

	return server.Serve(ln)
}

Jeffrey Morgan's avatar
Jeffrey Morgan committed
248
249
250
251
func NewCLI() *cobra.Command {
	log.SetFlags(log.LstdFlags | log.Lshortfile)

	rootCmd := &cobra.Command{
Michael Yang's avatar
Michael Yang committed
252
253
254
		Use:          "ollama",
		Short:        "Large language model runner",
		SilenceUsage: true,
Jeffrey Morgan's avatar
Jeffrey Morgan committed
255
256
257
		CompletionOptions: cobra.CompletionOptions{
			DisableDefaultCmd: true,
		},
Michael Yang's avatar
Michael Yang committed
258
		PersistentPreRunE: func(_ *cobra.Command, args []string) error {
Bruce MacDonald's avatar
Bruce MacDonald committed
259
			// create the models directory and it's parent
Michael Yang's avatar
Michael Yang committed
260
			return os.MkdirAll(filepath.Join(cacheDir(), "models"), 0o700)
Jeffrey Morgan's avatar
Jeffrey Morgan committed
261
262
263
264
265
		},
	}

	cobra.EnableCommandSorting = false

266
267
268
269
270
271
272
273
274
	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
275
	runCmd := &cobra.Command{
Michael Yang's avatar
Michael Yang committed
276
		Use:   "run MODEL [PROMPT]",
Jeffrey Morgan's avatar
Jeffrey Morgan committed
277
		Short: "Run a model",
Michael Yang's avatar
Michael Yang committed
278
279
		Args:  cobra.MinimumNArgs(1),
		RunE:  RunRun,
Jeffrey Morgan's avatar
Jeffrey Morgan committed
280
281
	}

282
283
	runCmd.Flags().Bool("verbose", false, "Show timings for response")

Jeffrey Morgan's avatar
Jeffrey Morgan committed
284
285
286
287
	serveCmd := &cobra.Command{
		Use:     "serve",
		Aliases: []string{"start"},
		Short:   "Start ollama",
Michael Yang's avatar
Michael Yang committed
288
		RunE:    RunServer,
Jeffrey Morgan's avatar
Jeffrey Morgan committed
289
290
	}

291
292
293
294
295
296
297
298
299
300
301
302
303
304
	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,
	}

Jeffrey Morgan's avatar
Jeffrey Morgan committed
305
306
	rootCmd.AddCommand(
		serveCmd,
307
		createCmd,
308
		runCmd,
309
310
		pullCmd,
		pushCmd,
Jeffrey Morgan's avatar
Jeffrey Morgan committed
311
312
313
314
	)

	return rootCmd
}