interactive.go 18.1 KB
Newer Older
1
2
3
package cmd

import (
Michael Yang's avatar
Michael Yang committed
4
	"cmp"
5
6
7
8
9
	"errors"
	"fmt"
	"io"
	"net/http"
	"os"
10
	"path/filepath"
11
	"regexp"
12
	"slices"
13
14
15
	"strings"

	"github.com/spf13/cobra"
Michael Yang's avatar
Michael Yang committed
16
	"golang.org/x/exp/maps"
17

18
	"github.com/ollama/ollama/api"
19
	"github.com/ollama/ollama/envconfig"
Michael Yang's avatar
Michael Yang committed
20
	"github.com/ollama/ollama/parser"
21
22
	"github.com/ollama/ollama/progress"
	"github.com/ollama/ollama/readline"
23
	"github.com/ollama/ollama/types/errtypes"
24
25
26
27
28
29
30
31
32
33
)

type MultilineState int

const (
	MultilineNone MultilineState = iota
	MultilinePrompt
	MultilineSystem
)

34
35
36
37
38
39
40
func loadModel(cmd *cobra.Command, opts *runOptions) error {
	p := progress.NewProgress(os.Stderr)
	defer p.StopAndClear()

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

41
	client, err := api.ClientFromEnvironment()
42
	if err != nil {
43
		return err
44
45
	}

46
	chatReq := &api.ChatRequest{
47
48
		Model:     opts.Model,
		KeepAlive: opts.KeepAlive,
49
	}
50

51
	return client.Chat(cmd.Context(), chatReq, func(resp api.ChatResponse) error {
52
		p.StopAndClear()
53
54
55
56
57
58
59
60
61
		for _, msg := range opts.Messages {
			switch msg.Role {
			case "user":
				fmt.Printf(">>> %s\n", msg.Content)
			case "assistant":
				state := &displayResponseState{}
				displayResponse(msg.Content, opts.WordWrap, state)
				fmt.Println()
				fmt.Println()
62
63
64
65
66
67
68
69
70
			}
		}
		return nil
	})
}

func generateInteractive(cmd *cobra.Command, opts runOptions) error {
	err := loadModel(cmd, &opts)
	if err != nil {
71
72
73
74
75
		return err
	}

	usage := func() {
		fmt.Fprintln(os.Stderr, "Available Commands:")
76
77
78
79
		fmt.Fprintln(os.Stderr, "  /set            Set session variables")
		fmt.Fprintln(os.Stderr, "  /show           Show model information")
		fmt.Fprintln(os.Stderr, "  /load <model>   Load a session or model")
		fmt.Fprintln(os.Stderr, "  /save <model>   Save your current session")
Bryce Reitano's avatar
Bryce Reitano committed
80
		fmt.Fprintln(os.Stderr, "  /clear          Clear session context")
81
82
83
		fmt.Fprintln(os.Stderr, "  /bye            Exit")
		fmt.Fprintln(os.Stderr, "  /?, /help       Help for a command")
		fmt.Fprintln(os.Stderr, "  /? shortcuts    Help for keyboard shortcuts")
84
85
		fmt.Fprintln(os.Stderr, "")
		fmt.Fprintln(os.Stderr, "Use \"\"\" to begin a multi-line message.")
86
87
88
89
90

		if opts.MultiModal {
			fmt.Fprintf(os.Stderr, "Use %s to include .jpg or .png images.\n", filepath.FromSlash("/path/to/file"))
		}

91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
		fmt.Fprintln(os.Stderr, "")
	}

	usageSet := func() {
		fmt.Fprintln(os.Stderr, "Available Commands:")
		fmt.Fprintln(os.Stderr, "  /set parameter ...     Set a parameter")
		fmt.Fprintln(os.Stderr, "  /set system <string>   Set system message")
		fmt.Fprintln(os.Stderr, "  /set history           Enable history")
		fmt.Fprintln(os.Stderr, "  /set nohistory         Disable history")
		fmt.Fprintln(os.Stderr, "  /set wordwrap          Enable wordwrap")
		fmt.Fprintln(os.Stderr, "  /set nowordwrap        Disable wordwrap")
		fmt.Fprintln(os.Stderr, "  /set format json       Enable JSON mode")
		fmt.Fprintln(os.Stderr, "  /set noformat          Disable formatting")
		fmt.Fprintln(os.Stderr, "  /set verbose           Show LLM stats")
		fmt.Fprintln(os.Stderr, "  /set quiet             Disable LLM stats")
		fmt.Fprintln(os.Stderr, "")
	}

	usageShortcuts := func() {
		fmt.Fprintln(os.Stderr, "Available keyboard shortcuts:")
		fmt.Fprintln(os.Stderr, "  Ctrl + a            Move to the beginning of the line (Home)")
		fmt.Fprintln(os.Stderr, "  Ctrl + e            Move to the end of the line (End)")
		fmt.Fprintln(os.Stderr, "   Alt + b            Move back (left) one word")
		fmt.Fprintln(os.Stderr, "   Alt + f            Move forward (right) one word")
		fmt.Fprintln(os.Stderr, "  Ctrl + k            Delete the sentence after the cursor")
		fmt.Fprintln(os.Stderr, "  Ctrl + u            Delete the sentence before the cursor")
Josh Yan's avatar
Josh Yan committed
117
		fmt.Fprintln(os.Stderr, "  Ctrl + w            Delete the word before the cursor")
118
119
120
121
122
123
124
125
126
		fmt.Fprintln(os.Stderr, "")
		fmt.Fprintln(os.Stderr, "  Ctrl + l            Clear the screen")
		fmt.Fprintln(os.Stderr, "  Ctrl + c            Stop the model from responding")
		fmt.Fprintln(os.Stderr, "  Ctrl + d            Exit ollama (/bye)")
		fmt.Fprintln(os.Stderr, "")
	}

	usageShow := func() {
		fmt.Fprintln(os.Stderr, "Available Commands:")
127
		fmt.Fprintln(os.Stderr, "  /show info         Show details for this model")
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
		fmt.Fprintln(os.Stderr, "  /show license      Show model license")
		fmt.Fprintln(os.Stderr, "  /show modelfile    Show Modelfile for this model")
		fmt.Fprintln(os.Stderr, "  /show parameters   Show parameters for this model")
		fmt.Fprintln(os.Stderr, "  /show system       Show system message")
		fmt.Fprintln(os.Stderr, "  /show template     Show prompt template")
		fmt.Fprintln(os.Stderr, "")
	}

	// only list out the most common parameters
	usageParameters := func() {
		fmt.Fprintln(os.Stderr, "Available Parameters:")
		fmt.Fprintln(os.Stderr, "  /set parameter seed <int>             Random number seed")
		fmt.Fprintln(os.Stderr, "  /set parameter num_predict <int>      Max number of tokens to predict")
		fmt.Fprintln(os.Stderr, "  /set parameter top_k <int>            Pick from top k num of tokens")
		fmt.Fprintln(os.Stderr, "  /set parameter top_p <float>          Pick token based on sum of probabilities")
		fmt.Fprintln(os.Stderr, "  /set parameter num_ctx <int>          Set the context size")
		fmt.Fprintln(os.Stderr, "  /set parameter temperature <float>    Set creativity level")
		fmt.Fprintln(os.Stderr, "  /set parameter repeat_penalty <float> How strongly to penalize repetitions")
		fmt.Fprintln(os.Stderr, "  /set parameter repeat_last_n <int>    Set how far back to look for repetitions")
		fmt.Fprintln(os.Stderr, "  /set parameter num_gpu <int>          The number of layers to send to the GPU")
148
		fmt.Fprintln(os.Stderr, "  /set parameter stop <string> <string> ...   Set the stop parameters")
149
150
151
152
153
154
155
156
157
158
159
160
161
		fmt.Fprintln(os.Stderr, "")
	}

	scanner, err := readline.New(readline.Prompt{
		Prompt:         ">>> ",
		AltPrompt:      "... ",
		Placeholder:    "Send a message (/? for help)",
		AltPlaceholder: `Use """ to end multi-line input`,
	})
	if err != nil {
		return err
	}

162
	if envconfig.NoHistory {
163
164
165
		scanner.HistoryDisable()
	}

166
167
168
	fmt.Print(readline.StartBracketedPaste)
	defer fmt.Printf(readline.EndBracketedPaste)

169
	var sb strings.Builder
170
171
172
173
174
175
176
177
178
179
180
181
182
183
	var multiline MultilineState

	for {
		line, err := scanner.Readline()
		switch {
		case errors.Is(err, io.EOF):
			fmt.Println()
			return nil
		case errors.Is(err, readline.ErrInterrupt):
			if line == "" {
				fmt.Println("\nUse Ctrl + d or /bye to exit.")
			}

			scanner.Prompt.UseAlt = false
184
			sb.Reset()
185
186
187
188
189
190
191

			continue
		case err != nil:
			return err
		}

		switch {
192
193
194
195
196
197
		case multiline != MultilineNone:
			// check if there's a multiline terminating string
			before, ok := strings.CutSuffix(line, `"""`)
			sb.WriteString(before)
			if !ok {
				fmt.Fprintln(&sb)
198
199
200
201
202
				continue
			}

			switch multiline {
			case MultilineSystem:
203
				opts.System = sb.String()
204
				opts.Messages = append(opts.Messages, api.Message{Role: "system", Content: opts.System})
205
				fmt.Println("Set system message.")
206
				sb.Reset()
207
			}
208

209
			multiline = MultilineNone
210
211
212
213
214
215
216
217
218
219
220
			scanner.Prompt.UseAlt = false
		case strings.HasPrefix(line, `"""`):
			line := strings.TrimPrefix(line, `"""`)
			line, ok := strings.CutSuffix(line, `"""`)
			sb.WriteString(line)
			if !ok {
				// no multiline terminating string; need more input
				fmt.Fprintln(&sb)
				multiline = MultilinePrompt
				scanner.Prompt.UseAlt = true
			}
221
		case scanner.Pasting:
222
			fmt.Fprintln(&sb, line)
223
224
225
226
227
228
			continue
		case strings.HasPrefix(line, "/list"):
			args := strings.Fields(line)
			if err := ListHandler(cmd, args[1:]); err != nil {
				return err
			}
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
		case strings.HasPrefix(line, "/load"):
			args := strings.Fields(line)
			if len(args) != 2 {
				fmt.Println("Usage:\n  /load <modelname>")
				continue
			}
			opts.Model = args[1]
			opts.Messages = []api.Message{}
			fmt.Printf("Loading model '%s'\n", opts.Model)
			if err := loadModel(cmd, &opts); err != nil {
				return err
			}
			continue
		case strings.HasPrefix(line, "/save"):
			args := strings.Fields(line)
			if len(args) != 2 {
				fmt.Println("Usage:\n  /save <modelname>")
				continue
			}

			client, err := api.ClientFromEnvironment()
			if err != nil {
				fmt.Println("error: couldn't connect to ollama server")
				return err
			}

			req := &api.CreateRequest{
				Name:      args[1],
				Modelfile: buildModelfile(opts),
			}
			fn := func(resp api.ProgressResponse) error { return nil }
			err = client.Create(cmd.Context(), req, fn)
			if err != nil {
262
263
264
265
				if strings.Contains(err.Error(), errtypes.InvalidModelNameErrMsg) {
					fmt.Printf("error: The model name '%s' is invalid\n", args[1])
					continue
				}
266
267
268
269
				return err
			}
			fmt.Printf("Created new model '%s'\n", args[1])
			continue
Bryce Reitano's avatar
Bryce Reitano committed
270
271
		case strings.HasPrefix(line, "/clear"):
			opts.Messages = []api.Message{}
Patrick Devine's avatar
Patrick Devine committed
272
273
274
275
			if opts.System != "" {
				newMessage := api.Message{Role: "system", Content: opts.System}
				opts.Messages = append(opts.Messages, newMessage)
			}
Bryce Reitano's avatar
Bryce Reitano committed
276
277
			fmt.Println("Cleared session context")
			continue
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
		case strings.HasPrefix(line, "/set"):
			args := strings.Fields(line)
			if len(args) > 1 {
				switch args[1] {
				case "history":
					scanner.HistoryEnable()
				case "nohistory":
					scanner.HistoryDisable()
				case "wordwrap":
					opts.WordWrap = true
					fmt.Println("Set 'wordwrap' mode.")
				case "nowordwrap":
					opts.WordWrap = false
					fmt.Println("Set 'nowordwrap' mode.")
				case "verbose":
293
294
295
					if err := cmd.Flags().Set("verbose", "true"); err != nil {
						return err
					}
296
297
					fmt.Println("Set 'verbose' mode.")
				case "quiet":
298
299
300
					if err := cmd.Flags().Set("verbose", "false"); err != nil {
						return err
					}
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
					fmt.Println("Set 'quiet' mode.")
				case "format":
					if len(args) < 3 || args[2] != "json" {
						fmt.Println("Invalid or missing format. For 'json' mode use '/set format json'")
					} else {
						opts.Format = args[2]
						fmt.Printf("Set format to '%s' mode.\n", args[2])
					}
				case "noformat":
					opts.Format = ""
					fmt.Println("Disabled format.")
				case "parameter":
					if len(args) < 4 {
						usageParameters()
						continue
					}
Michael Yang's avatar
Michael Yang committed
317
					params := args[3:]
318
319
					fp, err := api.FormatParams(map[string][]string{args[2]: params})
					if err != nil {
320
						fmt.Printf("Couldn't set parameter: %q\n", err)
321
322
						continue
					}
323
					fmt.Printf("Set parameter '%s' to '%s'\n", args[2], strings.Join(params, ", "))
324
					opts.Options[args[2]] = fp[args[2]]
Patrick Devine's avatar
Patrick Devine committed
325
				case "system":
326
327
328
329
					if len(args) < 3 {
						usageSet()
						continue
					}
330

Patrick Devine's avatar
Patrick Devine committed
331
					multiline = MultilineSystem
332

333
					line := strings.Join(args[2:], " ")
334
335
336
					line, ok := strings.CutPrefix(line, `"""`)
					if !ok {
						multiline = MultilineNone
337
					} else {
338
339
340
341
342
343
344
345
346
347
348
349
350
						// only cut suffix if the line is multiline
						line, ok = strings.CutSuffix(line, `"""`)
						if ok {
							multiline = MultilineNone
						}
					}

					sb.WriteString(line)
					if multiline != MultilineNone {
						scanner.Prompt.UseAlt = true
						continue
					}

Patrick Devine's avatar
Patrick Devine committed
351
352
353
354
355
356
357
358
					opts.System = sb.String() // for display in modelfile
					newMessage := api.Message{Role: "system", Content: sb.String()}
					// Check if the slice is not empty and the last message is from 'system'
					if len(opts.Messages) > 0 && opts.Messages[len(opts.Messages)-1].Role == "system" {
						// Replace the last message
						opts.Messages[len(opts.Messages)-1] = newMessage
					} else {
						opts.Messages = append(opts.Messages, newMessage)
359
					}
Patrick Devine's avatar
Patrick Devine committed
360
361
					fmt.Println("Set system message.")
					sb.Reset()
362
363
364

					sb.Reset()
					continue
365
366
367
368
369
370
371
372
373
374
375
376
377
378
				default:
					fmt.Printf("Unknown command '/set %s'. Type /? for help\n", args[1])
				}
			} else {
				usageSet()
			}
		case strings.HasPrefix(line, "/show"):
			args := strings.Fields(line)
			if len(args) > 1 {
				client, err := api.ClientFromEnvironment()
				if err != nil {
					fmt.Println("error: couldn't connect to ollama server")
					return err
				}
379
				req := &api.ShowRequest{
Michael Yang's avatar
Michael Yang committed
380
381
382
					Name:    opts.Model,
					System:  opts.System,
					Options: opts.Options,
383
384
				}
				resp, err := client.Show(cmd.Context(), req)
385
386
387
388
389
390
				if err != nil {
					fmt.Println("error: couldn't get model")
					return err
				}

				switch args[1] {
391
				case "info":
392
					showInfo(resp)
393
394
				case "license":
					if resp.License == "" {
395
						fmt.Println("No license was specified for this model.")
396
397
398
399
400
401
402
					} else {
						fmt.Println(resp.License)
					}
				case "modelfile":
					fmt.Println(resp.Modelfile)
				case "parameters":
					if resp.Parameters == "" {
403
						fmt.Println("No parameters were specified for this model.")
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
					} else {
						if len(opts.Options) > 0 {
							fmt.Println("User defined parameters:")
							for k, v := range opts.Options {
								fmt.Printf("%-*s %v\n", 30, k, v)
							}
							fmt.Println()
						}
						fmt.Println("Model defined parameters:")
						fmt.Println(resp.Parameters)
					}
				case "system":
					switch {
					case opts.System != "":
						fmt.Println(opts.System + "\n")
					case resp.System != "":
						fmt.Println(resp.System + "\n")
					default:
422
						fmt.Println("No system message was specified for this model.")
423
424
					}
				case "template":
Patrick Devine's avatar
Patrick Devine committed
425
					if resp.Template != "" {
426
						fmt.Println(resp.Template)
Patrick Devine's avatar
Patrick Devine committed
427
					} else {
428
						fmt.Println("No prompt template was specified for this model.")
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
					}
				default:
					fmt.Printf("Unknown command '/show %s'. Type /? for help\n", args[1])
				}
			} else {
				usageShow()
			}
		case strings.HasPrefix(line, "/help"), strings.HasPrefix(line, "/?"):
			args := strings.Fields(line)
			if len(args) > 1 {
				switch args[1] {
				case "set", "/set":
					usageSet()
				case "show", "/show":
					usageShow()
				case "shortcut", "shortcuts":
					usageShortcuts()
				}
			} else {
				usage()
			}
450
		case strings.HasPrefix(line, "/exit"), strings.HasPrefix(line, "/bye"):
451
452
453
454
455
			return nil
		case strings.HasPrefix(line, "/"):
			args := strings.Fields(line)
			isFile := false

456
			if opts.MultiModal {
457
458
459
460
461
462
463
464
				for _, f := range extractFileNames(line) {
					if strings.HasPrefix(f, args[0]) {
						isFile = true
						break
					}
				}
			}

465
			if !isFile {
466
467
468
				fmt.Printf("Unknown command '%s'. Type /? for help\n", args[0])
				continue
			}
469
470

			sb.WriteString(line)
471
		default:
472
			sb.WriteString(line)
473
474
		}

475
		if sb.Len() > 0 && multiline == MultilineNone {
476
477
			newMessage := api.Message{Role: "user", Content: sb.String()}

478
			if opts.MultiModal {
479
				msg, images, err := extractFileData(sb.String())
480
481
482
				if err != nil {
					return err
				}
483
484
485
486
487
488
489
490

				// clear all previous images for better responses
				if len(images) > 0 {
					for i := range opts.Messages {
						opts.Messages[i].Images = nil
					}
				}

491
				newMessage.Content = msg
492
				newMessage.Images = images
493
			}
494

495
496
497
498
			opts.Messages = append(opts.Messages, newMessage)

			assistant, err := chat(cmd, opts)
			if err != nil {
499
500
				return err
			}
501
502
503
			if assistant != nil {
				opts.Messages = append(opts.Messages, *assistant)
			}
504

505
			sb.Reset()
506
507
508
509
		}
	}
}

510
func buildModelfile(opts runOptions) string {
Michael Yang's avatar
Michael Yang committed
511
512
513
	var f parser.File
	f.Commands = append(f.Commands, parser.Command{Name: "model", Args: cmp.Or(opts.ParentModel, opts.Model)})

514
	if opts.System != "" {
Michael Yang's avatar
Michael Yang committed
515
		f.Commands = append(f.Commands, parser.Command{Name: "system", Args: opts.System})
516
517
	}

Michael Yang's avatar
Michael Yang committed
518
519
	keys := maps.Keys(opts.Options)
	slices.Sort(keys)
520
	for _, k := range keys {
Michael Yang's avatar
Michael Yang committed
521
522
523
524
525
526
527
528
529
530
531
532
		v := opts.Options[k]
		var cmds []parser.Command
		switch t := v.(type) {
		case []string:
			for _, s := range t {
				cmds = append(cmds, parser.Command{Name: k, Args: s})
			}
		default:
			cmds = append(cmds, parser.Command{Name: k, Args: fmt.Sprintf("%v", t)})
		}

		f.Commands = append(f.Commands, cmds...)
533
534
535
	}

	for _, msg := range opts.Messages {
Michael Yang's avatar
Michael Yang committed
536
		f.Commands = append(f.Commands, parser.Command{Name: "message", Args: fmt.Sprintf("%s: %s", msg.Role, msg.Content)})
537
538
	}

Michael Yang's avatar
Michael Yang committed
539
	return f.String()
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
func normalizeFilePath(fp string) string {
	// Define a map of escaped characters and their replacements
	replacements := map[string]string{
		"\\ ":  " ",  // Escaped space
		"\\(":  "(",  // Escaped left parenthesis
		"\\)":  ")",  // Escaped right parenthesis
		"\\[":  "[",  // Escaped left square bracket
		"\\]":  "]",  // Escaped right square bracket
		"\\{":  "{",  // Escaped left curly brace
		"\\}":  "}",  // Escaped right curly brace
		"\\$":  "$",  // Escaped dollar sign
		"\\&":  "&",  // Escaped ampersand
		"\\;":  ";",  // Escaped semicolon
		"\\'":  "'",  // Escaped single quote
		"\\\\": "\\", // Escaped backslash
		"\\*":  "*",  // Escaped asterisk
		"\\?":  "?",  // Escaped question mark
	}

	for escaped, actual := range replacements {
		fp = strings.ReplaceAll(fp, escaped, actual)
	}
	return fp
}

func extractFileNames(input string) []string {
568
	// Regex to match file paths starting with optional drive letter, / ./ \ or .\ and include escaped or unescaped spaces (\ or %20)
569
	// and followed by more characters and a file extension
570
571
	// This will capture non filename strings, but we'll check for file existence to remove mismatches
	regexPattern := `(?:[a-zA-Z]:)?(?:\./|/|\\)[\S\\ ]+?\.(?i:jpg|jpeg|png|svg)\b`
572
573
574
575
576
	re := regexp.MustCompile(regexPattern)

	return re.FindAllString(input, -1)
}

577
func extractFileData(input string) (string, []api.ImageData, error) {
578
	filePaths := extractFileNames(input)
579
	var imgs []api.ImageData
580
581
582
583
584
585
586
587

	for _, fp := range filePaths {
		nfp := normalizeFilePath(fp)
		data, err := getImageData(nfp)
		if err != nil {
			if os.IsNotExist(err) {
				continue
			}
588
			fmt.Fprintf(os.Stderr, "Couldn't process image: %q\n", err)
589
590
			return "", imgs, err
		}
591
		fmt.Fprintf(os.Stderr, "Added image '%s'\n", nfp)
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
		input = strings.ReplaceAll(input, fp, "")
		imgs = append(imgs, data)
	}
	return input, imgs, nil
}

func getImageData(filePath string) ([]byte, error) {
	file, err := os.Open(filePath)
	if err != nil {
		return nil, err
	}
	defer file.Close()

	buf := make([]byte, 512)
	_, err = file.Read(buf)
	if err != nil {
		return nil, err
	}

	contentType := http.DetectContentType(buf)
612
	allowedTypes := []string{"image/jpeg", "image/jpg", "image/png"}
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
	if !slices.Contains(allowedTypes, contentType) {
		return nil, fmt.Errorf("invalid image type: %s", contentType)
	}

	info, err := file.Stat()
	if err != nil {
		return nil, err
	}

	// Check if the file size exceeds 100MB
	var maxSize int64 = 100 * 1024 * 1024 // 100MB in bytes
	if info.Size() > maxSize {
		return nil, fmt.Errorf("file size exceeds maximum limit (100MB)")
	}

	buf = make([]byte, info.Size())
	_, err = file.Seek(0, 0)
	if err != nil {
		return nil, err
	}

	_, err = io.ReadFull(file, buf)
	if err != nil {
		return nil, err
	}

	return buf, nil
}