models.go 3.06 KB
Newer Older
Bruce MacDonald's avatar
Bruce MacDonald committed
1
2
3
4
5
6
7
8
9
10
11
12
package server

import (
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	"os"
	"path"
	"strconv"
)

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

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
28
29
30
31
32
33
34
35
36
func (m *Model) FullName() string {
	home, err := os.UserHomeDir()
	if err != nil {
		panic(err)
	}

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

Bruce MacDonald's avatar
Bruce MacDonald committed
37
38
39
40
41
42
43
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
44
	body, err := io.ReadAll(resp.Body)
Bruce MacDonald's avatar
Bruce MacDonald committed
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
	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
61
func saveModel(model *Model, fn func(total, completed int64)) error {
Bruce MacDonald's avatar
Bruce MacDonald committed
62
63
64
65
66
	// 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
67
		return fmt.Errorf("failed to download model: %w", err)
Bruce MacDonald's avatar
Bruce MacDonald committed
68
69
	}
	// check for resume
Michael Yang's avatar
Michael Yang committed
70
	alreadyDownloaded := int64(0)
Michael Yang's avatar
Michael Yang committed
71
	fileInfo, err := os.Stat(model.FullName())
Bruce MacDonald's avatar
Bruce MacDonald committed
72
73
74
75
76
77
	if err != nil {
		if !os.IsNotExist(err) {
			return fmt.Errorf("failed to check resume model file: %w", err)
		}
		// file doesn't exist, create it now
	} else {
Michael Yang's avatar
Michael Yang committed
78
79
		alreadyDownloaded = fileInfo.Size()
		req.Header.Add("Range", fmt.Sprintf("bytes=%d-", alreadyDownloaded))
Bruce MacDonald's avatar
Bruce MacDonald committed
80
81
82
83
84
85
86
87
88
	}

	resp, err := client.Do(req)
	if err != nil {
		return fmt.Errorf("failed to download model: %w", err)
	}

	defer resp.Body.Close()

Bruce MacDonald's avatar
Bruce MacDonald committed
89
90
	if resp.StatusCode == http.StatusRequestedRangeNotSatisfiable {
		// already downloaded
Michael Yang's avatar
Michael Yang committed
91
		fn(alreadyDownloaded, alreadyDownloaded)
Bruce MacDonald's avatar
Bruce MacDonald committed
92
93
94
95
		return nil
	}

	if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusPartialContent {
Bruce MacDonald's avatar
Bruce MacDonald committed
96
97
98
		return fmt.Errorf("failed to download model: %s", resp.Status)
	}

Michael Yang's avatar
Michael Yang committed
99
	out, err := os.OpenFile(model.FullName(), os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0o644)
Bruce MacDonald's avatar
Bruce MacDonald committed
100
101
102
103
104
	if err != nil {
		panic(err)
	}
	defer out.Close()

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

	buf := make([]byte, 1024)
Bruce MacDonald's avatar
Bruce MacDonald committed
108
109
	totalBytes := alreadyDownloaded
	totalSize += alreadyDownloaded
Bruce MacDonald's avatar
Bruce MacDonald committed
110
111
112
113
114
115
116
117
118
119
120
121

	for {
		n, err := resp.Body.Read(buf)
		if err != nil && err != io.EOF {
			return err
		}
		if n == 0 {
			break
		}
		if _, err := out.Write(buf[:n]); err != nil {
			return err
		}
Michael Yang's avatar
Michael Yang committed
122
123

		totalBytes += int64(n)
Bruce MacDonald's avatar
Bruce MacDonald committed
124

Michael Yang's avatar
Michael Yang committed
125
		fn(totalSize, totalBytes)
Bruce MacDonald's avatar
Bruce MacDonald committed
126
	}
Bruce MacDonald's avatar
Bruce MacDonald committed
127

Michael Yang's avatar
Michael Yang committed
128
	fn(totalSize, totalSize)
Bruce MacDonald's avatar
Bruce MacDonald committed
129
130
	return nil
}