modelpath.go 3.39 KB
Newer Older
Patrick Devine's avatar
Patrick Devine committed
1
2
3
package server

import (
4
	"errors"
Patrick Devine's avatar
Patrick Devine committed
5
	"fmt"
6
	"io/fs"
Michael Yang's avatar
Michael Yang committed
7
	"net/url"
Patrick Devine's avatar
Patrick Devine committed
8
9
	"os"
	"path/filepath"
10
	"regexp"
Patrick Devine's avatar
Patrick Devine committed
11
	"strings"
12
13

	"github.com/ollama/ollama/envconfig"
14
	"github.com/ollama/ollama/types/model"
Patrick Devine's avatar
Patrick Devine committed
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
)

type ModelPath struct {
	ProtocolScheme string
	Registry       string
	Namespace      string
	Repository     string
	Tag            string
}

const (
	DefaultRegistry       = "registry.ollama.ai"
	DefaultNamespace      = "library"
	DefaultTag            = "latest"
	DefaultProtocolScheme = "https"
)

32
var (
33
	ErrInvalidImageFormat  = errors.New("invalid image format")
CYJiang's avatar
CYJiang committed
34
	ErrInvalidDigestFormat = errors.New("invalid digest format")
35
36
	ErrInvalidProtocol     = errors.New("invalid protocol scheme")
	ErrInsecureProtocol    = errors.New("insecure protocol http")
CYJiang's avatar
CYJiang committed
37
	ErrModelPathInvalid    = errors.New("invalid model path")
38
39
)

40
func ParseModelPath(name string) ModelPath {
41
42
43
44
45
46
47
	mp := ModelPath{
		ProtocolScheme: DefaultProtocolScheme,
		Registry:       DefaultRegistry,
		Namespace:      DefaultNamespace,
		Repository:     "",
		Tag:            DefaultTag,
	}
Patrick Devine's avatar
Patrick Devine committed
48

Michael Yang's avatar
Michael Yang committed
49
50
51
52
	before, after, found := strings.Cut(name, "://")
	if found {
		mp.ProtocolScheme = before
		name = after
53
54
	}

55
	name = strings.ReplaceAll(name, string(os.PathSeparator), "/")
56
	parts := strings.Split(name, "/")
57
	switch len(parts) {
Patrick Devine's avatar
Patrick Devine committed
58
	case 3:
59
60
61
		mp.Registry = parts[0]
		mp.Namespace = parts[1]
		mp.Repository = parts[2]
Patrick Devine's avatar
Patrick Devine committed
62
	case 2:
63
64
		mp.Namespace = parts[0]
		mp.Repository = parts[1]
Patrick Devine's avatar
Patrick Devine committed
65
	case 1:
66
		mp.Repository = parts[0]
Patrick Devine's avatar
Patrick Devine committed
67
68
	}

69
	if repo, tag, found := strings.Cut(mp.Repository, ":"); found {
70
71
		mp.Repository = repo
		mp.Tag = tag
Patrick Devine's avatar
Patrick Devine committed
72
73
	}

74
	return mp
Patrick Devine's avatar
Patrick Devine committed
75
76
77
78
79
80
81
82
83
84
85
}

func (mp ModelPath) GetNamespaceRepository() string {
	return fmt.Sprintf("%s/%s", mp.Namespace, mp.Repository)
}

func (mp ModelPath) GetFullTagname() string {
	return fmt.Sprintf("%s/%s/%s:%s", mp.Registry, mp.Namespace, mp.Repository, mp.Tag)
}

func (mp ModelPath) GetShortTagname() string {
86
87
88
89
90
	if mp.Registry == DefaultRegistry {
		if mp.Namespace == DefaultNamespace {
			return fmt.Sprintf("%s:%s", mp.Repository, mp.Tag)
		}
		return fmt.Sprintf("%s/%s:%s", mp.Namespace, mp.Repository, mp.Tag)
Patrick Devine's avatar
Patrick Devine committed
91
	}
92
	return fmt.Sprintf("%s/%s/%s:%s", mp.Registry, mp.Namespace, mp.Repository, mp.Tag)
Patrick Devine's avatar
Patrick Devine committed
93
94
}

95
96
// GetManifestPath returns the path to the manifest file for the given model path, it is up to the caller to create the directory if it does not exist.
func (mp ModelPath) GetManifestPath() (string, error) {
97
98
99
100
101
	name := model.Name{
		Host:      mp.Registry,
		Namespace: mp.Namespace,
		Model:     mp.Repository,
		Tag:       mp.Tag,
Michael Yang's avatar
Michael Yang committed
102
	}
103
104
105
106
	if !name.IsValid() {
		return "", fs.ErrNotExist
	}
	return filepath.Join(envconfig.Models(), "manifests", name.Filepath()), nil
Patrick Devine's avatar
Patrick Devine committed
107
108
}

Michael Yang's avatar
Michael Yang committed
109
110
111
112
113
114
115
func (mp ModelPath) BaseURL() *url.URL {
	return &url.URL{
		Scheme: mp.ProtocolScheme,
		Host:   mp.Registry,
	}
}

Patrick Devine's avatar
Patrick Devine committed
116
func GetManifestPath() (string, error) {
Michael Yang's avatar
models  
Michael Yang committed
117
	path := filepath.Join(envconfig.Models(), "manifests")
Michael Yang's avatar
Michael Yang committed
118
	if err := os.MkdirAll(path, 0o755); err != nil {
Michael Yang's avatar
Michael Yang committed
119
120
121
122
		return "", err
	}

	return path, nil
Patrick Devine's avatar
Patrick Devine committed
123
124
}

Patrick Devine's avatar
Patrick Devine committed
125
func GetBlobsPath(digest string) (string, error) {
126
127
128
129
130
131
132
133
	// only accept actual sha256 digests
	pattern := "^sha256[:-][0-9a-fA-F]{64}$"
	re := regexp.MustCompile(pattern)

	if digest != "" && !re.MatchString(digest) {
		return "", ErrInvalidDigestFormat
	}

134
	digest = strings.ReplaceAll(digest, ":", "-")
Michael Yang's avatar
models  
Michael Yang committed
135
	path := filepath.Join(envconfig.Models(), "blobs", digest)
136
137
138
139
140
141
	dirPath := filepath.Dir(path)
	if digest == "" {
		dirPath = path
	}

	if err := os.MkdirAll(dirPath, 0o755); err != nil {
Patrick Devine's avatar
Patrick Devine committed
142
143
144
		return "", err
	}

Michael Yang's avatar
Michael Yang committed
145
	return path, nil
Patrick Devine's avatar
Patrick Devine committed
146
}