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

import (
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	"os"
	"path"
	"strconv"
Bruce MacDonald's avatar
Bruce MacDonald committed
11
12

	"github.com/jmorganca/ollama/api"
Bruce MacDonald's avatar
Bruce MacDonald committed
13
14
15
)

// const directoryURL = "https://ollama.ai/api/models"
Bruce MacDonald's avatar
Bruce MacDonald committed
16
// TODO
Bruce MacDonald's avatar
Bruce MacDonald committed
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
const directoryURL = "https://raw.githubusercontent.com/jmorganca/ollama/go/models.json"

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
32
33
34
35
36
37
38
39
40
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
41
func pull(model string, progressCh chan<- api.PullProgress) error {
Bruce MacDonald's avatar
Bruce MacDonald committed
42
43
44
45
46
47
48
49
50
51
52
53
54
55
	remote, err := getRemote(model)
	if err != nil {
		return fmt.Errorf("failed to pull model: %w", err)
	}
	return saveModel(remote, progressCh)
}

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
56
	body, err := io.ReadAll(resp.Body)
Bruce MacDonald's avatar
Bruce MacDonald committed
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
	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)
}

Bruce MacDonald's avatar
Bruce MacDonald committed
73
func saveModel(model *Model, progressCh chan<- api.PullProgress) error {
Bruce MacDonald's avatar
Bruce MacDonald committed
74
75
76
77
78
	// 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
79
		return fmt.Errorf("failed to download model: %w", err)
Bruce MacDonald's avatar
Bruce MacDonald committed
80
81
	}
	// check for resume
Michael Yang's avatar
Michael Yang committed
82
	alreadyDownloaded := int64(0)
Michael Yang's avatar
Michael Yang committed
83
	fileInfo, err := os.Stat(model.FullName())
Bruce MacDonald's avatar
Bruce MacDonald committed
84
85
86
87
88
89
	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
90
91
		alreadyDownloaded = fileInfo.Size()
		req.Header.Add("Range", fmt.Sprintf("bytes=%d-", alreadyDownloaded))
Bruce MacDonald's avatar
Bruce MacDonald committed
92
93
94
95
96
97
98
99
100
	}

	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
101
102
103
104
105
106
107
108
109
110
111
	if resp.StatusCode == http.StatusRequestedRangeNotSatisfiable {
		// already downloaded
		progressCh <- api.PullProgress{
			Total:     alreadyDownloaded,
			Completed: alreadyDownloaded,
			Percent:   100,
		}
		return nil
	}

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

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

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

	buf := make([]byte, 1024)
Bruce MacDonald's avatar
Bruce MacDonald committed
124
125
	totalBytes := alreadyDownloaded
	totalSize += alreadyDownloaded
Bruce MacDonald's avatar
Bruce MacDonald committed
126
127
128
129
130
131
132
133
134
135
136
137

	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
138
139

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

		// send progress updates
Bruce MacDonald's avatar
Bruce MacDonald committed
142
143
144
145
146
		progressCh <- api.PullProgress{
			Total:     totalSize,
			Completed: totalBytes,
			Percent:   float64(totalBytes) / float64(totalSize) * 100,
		}
Bruce MacDonald's avatar
Bruce MacDonald committed
147
148
	}

Bruce MacDonald's avatar
Bruce MacDonald committed
149
150
151
152
153
	progressCh <- api.PullProgress{
		Total:     totalSize,
		Completed: totalSize,
		Percent:   100,
	}
Bruce MacDonald's avatar
Bruce MacDonald committed
154
155
156

	return nil
}