modelpath.go 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164
  1. package server
  2. import (
  3. "errors"
  4. "fmt"
  5. "net/url"
  6. "os"
  7. "path/filepath"
  8. "strings"
  9. )
  10. type ModelPath struct {
  11. ProtocolScheme string
  12. Registry string
  13. Namespace string
  14. Repository string
  15. Tag string
  16. }
  17. const (
  18. DefaultRegistry = "registry.ollama.ai"
  19. DefaultNamespace = "library"
  20. DefaultTag = "latest"
  21. DefaultProtocolScheme = "https"
  22. )
  23. var (
  24. ErrInvalidImageFormat = errors.New("invalid image format")
  25. ErrInvalidProtocol = errors.New("invalid protocol scheme")
  26. ErrInsecureProtocol = errors.New("insecure protocol http")
  27. )
  28. func ParseModelPath(name string) ModelPath {
  29. mp := ModelPath{
  30. ProtocolScheme: DefaultProtocolScheme,
  31. Registry: DefaultRegistry,
  32. Namespace: DefaultNamespace,
  33. Repository: "",
  34. Tag: DefaultTag,
  35. }
  36. before, after, found := strings.Cut(name, "://")
  37. if found {
  38. mp.ProtocolScheme = before
  39. name = after
  40. }
  41. name = strings.ReplaceAll(name, string(os.PathSeparator), "/")
  42. parts := strings.Split(name, "/")
  43. switch len(parts) {
  44. case 3:
  45. mp.Registry = parts[0]
  46. mp.Namespace = parts[1]
  47. mp.Repository = parts[2]
  48. case 2:
  49. mp.Namespace = parts[0]
  50. mp.Repository = parts[1]
  51. case 1:
  52. mp.Repository = parts[0]
  53. }
  54. if repo, tag, found := strings.Cut(mp.Repository, ":"); found {
  55. mp.Repository = repo
  56. mp.Tag = tag
  57. }
  58. return mp
  59. }
  60. var errModelPathInvalid = errors.New("invalid model path")
  61. func (mp ModelPath) Validate() error {
  62. if mp.Repository == "" {
  63. return fmt.Errorf("%w: model repository name is required", errModelPathInvalid)
  64. }
  65. if strings.Contains(mp.Tag, ":") {
  66. return fmt.Errorf("%w: ':' (colon) is not allowed in tag names", errModelPathInvalid)
  67. }
  68. return nil
  69. }
  70. func (mp ModelPath) GetNamespaceRepository() string {
  71. return fmt.Sprintf("%s/%s", mp.Namespace, mp.Repository)
  72. }
  73. func (mp ModelPath) GetFullTagname() string {
  74. return fmt.Sprintf("%s/%s/%s:%s", mp.Registry, mp.Namespace, mp.Repository, mp.Tag)
  75. }
  76. func (mp ModelPath) GetShortTagname() string {
  77. if mp.Registry == DefaultRegistry {
  78. if mp.Namespace == DefaultNamespace {
  79. return fmt.Sprintf("%s:%s", mp.Repository, mp.Tag)
  80. }
  81. return fmt.Sprintf("%s/%s:%s", mp.Namespace, mp.Repository, mp.Tag)
  82. }
  83. return fmt.Sprintf("%s/%s/%s:%s", mp.Registry, mp.Namespace, mp.Repository, mp.Tag)
  84. }
  85. // modelsDir returns the value of the OLLAMA_MODELS environment variable or the user's home directory if OLLAMA_MODELS is not set.
  86. // The models directory is where Ollama stores its model files and manifests.
  87. func modelsDir() (string, error) {
  88. if models, exists := os.LookupEnv("OLLAMA_MODELS"); exists {
  89. return models, nil
  90. }
  91. home, err := os.UserHomeDir()
  92. if err != nil {
  93. return "", err
  94. }
  95. return filepath.Join(home, ".ollama", "models"), nil
  96. }
  97. // 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.
  98. func (mp ModelPath) GetManifestPath() (string, error) {
  99. dir, err := modelsDir()
  100. if err != nil {
  101. return "", err
  102. }
  103. return filepath.Join(dir, "manifests", mp.Registry, mp.Namespace, mp.Repository, mp.Tag), nil
  104. }
  105. func (mp ModelPath) BaseURL() *url.URL {
  106. return &url.URL{
  107. Scheme: mp.ProtocolScheme,
  108. Host: mp.Registry,
  109. }
  110. }
  111. func GetManifestPath() (string, error) {
  112. dir, err := modelsDir()
  113. if err != nil {
  114. return "", err
  115. }
  116. path := filepath.Join(dir, "manifests")
  117. if err := os.MkdirAll(path, 0o755); err != nil {
  118. return "", err
  119. }
  120. return path, nil
  121. }
  122. func GetBlobsPath(digest string) (string, error) {
  123. dir, err := modelsDir()
  124. if err != nil {
  125. return "", err
  126. }
  127. digest = strings.ReplaceAll(digest, ":", "-")
  128. path := filepath.Join(dir, "blobs", digest)
  129. dirPath := filepath.Dir(path)
  130. if digest == "" {
  131. dirPath = path
  132. }
  133. if err := os.MkdirAll(dirPath, 0o755); err != nil {
  134. return "", err
  135. }
  136. return path, nil
  137. }