cmd.go 6.37 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 {
Patrick Devine's avatar
Patrick Devine committed
51
52
53
54
55
56
57
	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
58
59
60
	switch {
	case errors.Is(err, os.ErrNotExist):
		if err := pull(args[0]); err != nil {
Michael Yang's avatar
Michael Yang committed
61
62
63
64
65
66
67
68
			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
69
70
		}
	case err != nil:
Michael Yang's avatar
Michael Yang committed
71
72
73
74
		return err
	}

	return RunGenerate(cmd, args)
Bruce MacDonald's avatar
Bruce MacDonald committed
75
76
}

77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
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
96
func pull(model string) error {
Bruce MacDonald's avatar
Bruce MacDonald committed
97
	client := api.NewClient()
98

Bruce MacDonald's avatar
Bruce MacDonald committed
99
	var bar *progressbar.ProgressBar
Michael Yang's avatar
Michael Yang committed
100

101
102
103
104
105
106
	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
107
			}
108
109
110
111
112
113
114
115
116
117
118
119
120
121
			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
	}
122

123
124
125
126
	if err := client.Pull(context.Background(), &request, fn); err != nil {
		return err
	}
	return nil
Michael Yang's avatar
Michael Yang committed
127
128
}

129
func RunGenerate(cmd *cobra.Command, args []string) error {
Michael Yang's avatar
Michael Yang committed
130
	if len(args) > 1 {
Michael Yang's avatar
Michael Yang committed
131
		// join all args into a single prompt
132
		return generate(cmd, args[0], strings.Join(args[1:], " "))
Michael Yang's avatar
Michael Yang committed
133
134
135
	}

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

139
	return generateBatch(cmd, args[0])
Michael Yang's avatar
Michael Yang committed
140
141
}

Michael Yang's avatar
Michael Yang committed
142
143
var generateContextKey struct{}

144
func generate(cmd *cobra.Command, model, prompt string) error {
Michael Yang's avatar
Michael Yang committed
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
	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
164
			}
Michael Yang's avatar
Michael Yang committed
165
		}()
Michael Yang's avatar
Michael Yang committed
166

167
168
		var latest api.GenerateResponse

Michael Yang's avatar
Michael Yang committed
169
170
171
172
173
174
		generateContext, ok := cmd.Context().Value(generateContextKey).([]int)
		if !ok {
			generateContext = []int{}
		}

		request := api.GenerateRequest{Model: model, Prompt: prompt, Context: generateContext}
175
		fn := func(resp api.GenerateResponse) error {
Michael Yang's avatar
Michael Yang committed
176
177
178
			if !spinner.IsFinished() {
				spinner.Finish()
			}
Michael Yang's avatar
Michael Yang committed
179

180
181
			latest = resp

Michael Yang's avatar
Michael Yang committed
182
			fmt.Print(resp.Response)
Michael Yang's avatar
Michael Yang committed
183
184

			cmd.SetContext(context.WithValue(cmd.Context(), generateContextKey, resp.Context))
Michael Yang's avatar
Michael Yang committed
185
			return nil
186
187
188
189
190
		}

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

Michael Yang's avatar
Michael Yang committed
192
193
		fmt.Println()
		fmt.Println()
194
195
196
197
198
199
200
201
202

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

		if verbose {
			latest.Summary()
		}
Michael Yang's avatar
Michael Yang committed
203
	}
Michael Yang's avatar
Michael Yang committed
204
205
206
207

	return nil
}

208
func generateInteractive(cmd *cobra.Command, model string) error {
Michael Yang's avatar
Michael Yang committed
209
210
211
	fmt.Print(">>> ")
	scanner := bufio.NewScanner(os.Stdin)
	for scanner.Scan() {
212
		if err := generate(cmd, model, scanner.Text()); err != nil {
Michael Yang's avatar
Michael Yang committed
213
214
215
216
217
218
			return err
		}

		fmt.Print(">>> ")
	}

Bruce MacDonald's avatar
Bruce MacDonald committed
219
	return nil
Bruce MacDonald's avatar
Bruce MacDonald committed
220
221
}

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

	return nil
}

func RunServer(_ *cobra.Command, _ []string) error {
236
237
238
239
240
241
242
243
244
245
246
	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
247
248
249
250
251
252
253
	if err != nil {
		return err
	}

	return server.Serve(ln)
}

Jeffrey Morgan's avatar
Jeffrey Morgan committed
254
255
256
257
func NewCLI() *cobra.Command {
	log.SetFlags(log.LstdFlags | log.Lshortfile)

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

	cobra.EnableCommandSorting = false

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

288
289
	runCmd.Flags().Bool("verbose", false, "Show timings for response")

Jeffrey Morgan's avatar
Jeffrey Morgan committed
290
291
292
293
	serveCmd := &cobra.Command{
		Use:     "serve",
		Aliases: []string{"start"},
		Short:   "Start ollama",
Michael Yang's avatar
Michael Yang committed
294
		RunE:    RunServer,
Jeffrey Morgan's avatar
Jeffrey Morgan committed
295
296
	}

297
298
299
300
301
302
303
304
305
306
307
308
309
310
	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
311
312
	rootCmd.AddCommand(
		serveCmd,
313
		createCmd,
314
		runCmd,
315
316
		pullCmd,
		pushCmd,
Jeffrey Morgan's avatar
Jeffrey Morgan committed
317
318
319
320
	)

	return rootCmd
}