models.go 3.2 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
10
11
12
13
	"fmt"
	"io"
	"net/http"
	"os"
	"path"
	"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
35
36
37
func (m *Model) FullName() string {
	home, err := os.UserHomeDir()
	if err != nil {
		panic(err)
	}

	return path.Join(home, ".ollama", "models", m.Name+".bin")
}

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

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
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101

	// check if completed file exists
	fi, err := os.Stat(model.FullName())
	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:
		fn(fi.Size(), fi.Size())
		return nil
	}

	var size int64

	// completed file doesn't exist, check partial file
	fi, err = os.Stat(model.TempFile())
	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
102
103
	}

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

Bruce MacDonald's avatar
Bruce MacDonald committed
106
107
108
109
110
111
	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
112
	if resp.StatusCode >= 400 {
Bruce MacDonald's avatar
Bruce MacDonald committed
113
114
115
		return fmt.Errorf("failed to download model: %s", resp.Status)
	}

Michael Yang's avatar
Michael Yang committed
116
	out, err := os.OpenFile(model.TempFile(), os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0o644)
Bruce MacDonald's avatar
Bruce MacDonald committed
117
118
119
120
121
	if err != nil {
		panic(err)
	}
	defer out.Close()

Michael Yang's avatar
Michael Yang committed
122
	totalSize, _ := strconv.ParseInt(resp.Header.Get("Content-Length"), 10, 64)
Bruce MacDonald's avatar
Bruce MacDonald committed
123

Michael Yang's avatar
Michael Yang committed
124
125
	totalBytes := size
	totalSize += size
Bruce MacDonald's avatar
Bruce MacDonald committed
126
127

	for {
Michael Yang's avatar
Michael Yang committed
128
129
		n, err := io.CopyN(out, resp.Body, 8192)
		if err != nil && !errors.Is(err, io.EOF) {
Bruce MacDonald's avatar
Bruce MacDonald committed
130
131
			return err
		}
Michael Yang's avatar
Michael Yang committed
132

Bruce MacDonald's avatar
Bruce MacDonald committed
133
134
135
136
		if n == 0 {
			break
		}

Michael Yang's avatar
Michael Yang committed
137
		totalBytes += n
Michael Yang's avatar
Michael Yang committed
138
		fn(totalSize, totalBytes)
Bruce MacDonald's avatar
Bruce MacDonald committed
139
	}
Bruce MacDonald's avatar
Bruce MacDonald committed
140

Michael Yang's avatar
Michael Yang committed
141
	fn(totalSize, totalSize)
Michael Yang's avatar
Michael Yang committed
142
	return os.Rename(model.TempFile(), model.FullName())
Bruce MacDonald's avatar
Bruce MacDonald committed
143
}