models.go 2.94 KB
Newer Older
Bruce MacDonald's avatar
Bruce MacDonald committed
1
2
3
4
package server

import (
	"encoding/json"
Michael Yang's avatar
Michael Yang committed
5
	"errors"
Bruce MacDonald's avatar
Bruce MacDonald committed
6
7
8
9
	"fmt"
	"io"
	"net/http"
	"os"
Michael Yang's avatar
Michael Yang committed
10
	"path/filepath"
Bruce MacDonald's avatar
Bruce MacDonald committed
11
12
13
	"strconv"
)

Bruce MacDonald's avatar
Bruce MacDonald committed
14
const directoryURL = "https://ollama.ai/api/models"
Bruce MacDonald's avatar
Bruce MacDonald committed
15
16
17
18
19
20
21
22
23
24
25
26
27
28

type Model struct {
	Name             string `json:"name"`
	DisplayName      string `json:"display_name"`
	Parameters       string `json:"parameters"`
	URL              string `json:"url"`
	ShortDescription string `json:"short_description"`
	Description      string `json:"description"`
	PublishedBy      string `json:"published_by"`
	OriginalAuthor   string `json:"original_author"`
	OriginalURL      string `json:"original_url"`
	License          string `json:"license"`
}

Michael Yang's avatar
Michael Yang committed
29
30
31
32
33
34
func (m *Model) FullName() string {
	home, err := os.UserHomeDir()
	if err != nil {
		panic(err)
	}

Michael Yang's avatar
Michael Yang committed
35
	return filepath.Join(home, ".ollama", "models", m.Name+".bin")
Michael Yang's avatar
Michael Yang committed
36
37
}

Michael Yang's avatar
Michael Yang committed
38
39
func (m *Model) TempFile() string {
	fullName := m.FullName()
Michael Yang's avatar
Michael Yang committed
40
41
42
	return filepath.Join(
		filepath.Dir(fullName),
		fmt.Sprintf(".%s.part", filepath.Base(fullName)),
Michael Yang's avatar
Michael Yang committed
43
44
45
	)
}

Bruce MacDonald's avatar
Bruce MacDonald committed
46
47
48
49
50
51
52
func getRemote(model string) (*Model, error) {
	// resolve the model download from our directory
	resp, err := http.Get(directoryURL)
	if err != nil {
		return nil, fmt.Errorf("failed to get directory: %w", err)
	}
	defer resp.Body.Close()
Michael Yang's avatar
Michael Yang committed
53
	body, err := io.ReadAll(resp.Body)
Bruce MacDonald's avatar
Bruce MacDonald committed
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
	if err != nil {
		return nil, fmt.Errorf("failed to read directory: %w", err)
	}
	var models []Model
	err = json.Unmarshal(body, &models)
	if err != nil {
		return nil, fmt.Errorf("failed to parse directory: %w", err)
	}
	for _, m := range models {
		if m.Name == model {
			return &m, nil
		}
	}
	return nil, fmt.Errorf("model not found in directory: %s", model)
}

Michael Yang's avatar
Michael Yang committed
70
func saveModel(model *Model, fn func(total, completed int64)) error {
Bruce MacDonald's avatar
Bruce MacDonald committed
71
72
73
74
75
	// this models cache directory is created by the server on startup

	client := &http.Client{}
	req, err := http.NewRequest("GET", model.URL, nil)
	if err != nil {
Bruce MacDonald's avatar
Bruce MacDonald committed
76
		return fmt.Errorf("failed to download model: %w", err)
Bruce MacDonald's avatar
Bruce MacDonald committed
77
	}
Michael Yang's avatar
Michael Yang committed
78
79
80
81

	var size int64

	// completed file doesn't exist, check partial file
Michael Yang's avatar
Michael Yang committed
82
	fi, err := os.Stat(model.TempFile())
Michael Yang's avatar
Michael Yang committed
83
84
85
86
87
88
89
	switch {
	case errors.Is(err, os.ErrNotExist):
		// noop, file doesn't exist so create it
	case err != nil:
		return fmt.Errorf("stat: %w", err)
	default:
		size = fi.Size()
Bruce MacDonald's avatar
Bruce MacDonald committed
90
91
	}

Michael Yang's avatar
Michael Yang committed
92
93
	req.Header.Add("Range", fmt.Sprintf("bytes=%d-", size))

Bruce MacDonald's avatar
Bruce MacDonald committed
94
95
96
97
98
99
	resp, err := client.Do(req)
	if err != nil {
		return fmt.Errorf("failed to download model: %w", err)
	}
	defer resp.Body.Close()

Michael Yang's avatar
Michael Yang committed
100
	if resp.StatusCode >= 400 {
Bruce MacDonald's avatar
Bruce MacDonald committed
101
102
103
		return fmt.Errorf("failed to download model: %s", resp.Status)
	}

Michael Yang's avatar
Michael Yang committed
104
	out, err := os.OpenFile(model.TempFile(), os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0o644)
Bruce MacDonald's avatar
Bruce MacDonald committed
105
106
107
108
109
	if err != nil {
		panic(err)
	}
	defer out.Close()

Michael Yang's avatar
Michael Yang committed
110
111
	remaining, _ := strconv.ParseInt(resp.Header.Get("Content-Length"), 10, 64)
	completed := size
Bruce MacDonald's avatar
Bruce MacDonald committed
112

Michael Yang's avatar
Michael Yang committed
113
	total := remaining + completed
Bruce MacDonald's avatar
Bruce MacDonald committed
114
115

	for {
Michael Yang's avatar
Michael Yang committed
116
117
118
		fn(total, completed)
		if completed >= total {
			return os.Rename(model.TempFile(), model.FullName())
Bruce MacDonald's avatar
Bruce MacDonald committed
119
		}
Michael Yang's avatar
Michael Yang committed
120

Michael Yang's avatar
Michael Yang committed
121
		n, err := io.CopyN(out, resp.Body, 8192)
Michael Yang's avatar
Michael Yang committed
122
123
		if err != nil && !errors.Is(err, io.EOF) {
			return err
Bruce MacDonald's avatar
Bruce MacDonald committed
124
125
		}

Michael Yang's avatar
Michael Yang committed
126
		completed += n
Bruce MacDonald's avatar
Bruce MacDonald committed
127
	}
Bruce MacDonald's avatar
Bruce MacDonald committed
128
}