Skip to content
GitLab
Menu
Projects
Groups
Snippets
Loading...
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
Menu
Open sidebar
OpenDAS
ollama
Commits
548a7df0
Commit
548a7df0
authored
Apr 17, 2024
by
Michael Yang
Browse files
update list handler to use model.Name
parent
b2f00aa9
Changes
5
Show whitespace changes
Inline
Side-by-side
Showing
5 changed files
with
215 additions
and
53 deletions
+215
-53
server/images.go
server/images.go
+0
-11
server/manifest.go
server/manifest.go
+79
-0
server/routes.go
server/routes.go
+42
-39
types/model/name.go
types/model/name.go
+51
-3
types/model/name_test.go
types/model/name_test.go
+43
-0
No files found.
server/images.go
View file @
548a7df0
...
@@ -52,7 +52,6 @@ type Model struct {
...
@@ -52,7 +52,6 @@ type Model struct {
System
string
System
string
License
[]
string
License
[]
string
Digest
string
Digest
string
Size
int64
Options
map
[
string
]
interface
{}
Options
map
[
string
]
interface
{}
Messages
[]
Message
Messages
[]
Message
}
}
...
@@ -161,15 +160,6 @@ type RootFS struct {
...
@@ -161,15 +160,6 @@ type RootFS struct {
DiffIDs
[]
string
`json:"diff_ids"`
DiffIDs
[]
string
`json:"diff_ids"`
}
}
func
(
m
*
ManifestV2
)
GetTotalSize
()
(
total
int64
)
{
for
_
,
layer
:=
range
m
.
Layers
{
total
+=
layer
.
Size
}
total
+=
m
.
Config
.
Size
return
total
}
func
GetManifest
(
mp
ModelPath
)
(
*
ManifestV2
,
string
,
error
)
{
func
GetManifest
(
mp
ModelPath
)
(
*
ManifestV2
,
string
,
error
)
{
fp
,
err
:=
mp
.
GetManifestPath
()
fp
,
err
:=
mp
.
GetManifestPath
()
if
err
!=
nil
{
if
err
!=
nil
{
...
@@ -210,7 +200,6 @@ func GetModel(name string) (*Model, error) {
...
@@ -210,7 +200,6 @@ func GetModel(name string) (*Model, error) {
Digest
:
digest
,
Digest
:
digest
,
Template
:
"{{ .Prompt }}"
,
Template
:
"{{ .Prompt }}"
,
License
:
[]
string
{},
License
:
[]
string
{},
Size
:
manifest
.
GetTotalSize
(),
}
}
filename
,
err
:=
GetBlobsPath
(
manifest
.
Config
.
Digest
)
filename
,
err
:=
GetBlobsPath
(
manifest
.
Config
.
Digest
)
...
...
server/manifest
s
.go
→
server/manifest.go
View file @
548a7df0
...
@@ -2,11 +2,56 @@ package server
...
@@ -2,11 +2,56 @@ package server
import
(
import
(
"bytes"
"bytes"
"crypto/sha256"
"encoding/json"
"encoding/json"
"fmt"
"io"
"os"
"os"
"path/filepath"
"path/filepath"
"github.com/ollama/ollama/types/model"
)
)
type
Manifest
struct
{
ManifestV2
Digest
string
`json:"-"`
}
func
(
m
*
Manifest
)
Size
()
(
size
int64
)
{
for
_
,
layer
:=
range
append
(
m
.
Layers
,
m
.
Config
)
{
size
+=
layer
.
Size
}
return
}
func
ParseNamedManifest
(
name
model
.
Name
)
(
*
Manifest
,
error
)
{
if
!
name
.
IsFullyQualified
()
{
return
nil
,
model
.
Unqualified
(
name
)
}
manifests
,
err
:=
GetManifestPath
()
if
err
!=
nil
{
return
nil
,
err
}
var
manifest
ManifestV2
manifestfile
,
err
:=
os
.
Open
(
filepath
.
Join
(
manifests
,
name
.
Filepath
()))
if
err
!=
nil
{
return
nil
,
err
}
sha256sum
:=
sha256
.
New
()
if
err
:=
json
.
NewDecoder
(
io
.
TeeReader
(
manifestfile
,
sha256sum
))
.
Decode
(
&
manifest
);
err
!=
nil
{
return
nil
,
err
}
return
&
Manifest
{
ManifestV2
:
manifest
,
Digest
:
fmt
.
Sprintf
(
"%x"
,
sha256sum
.
Sum
(
nil
)),
},
nil
}
func
WriteManifest
(
name
string
,
config
*
Layer
,
layers
[]
*
Layer
)
error
{
func
WriteManifest
(
name
string
,
config
*
Layer
,
layers
[]
*
Layer
)
error
{
manifest
:=
ManifestV2
{
manifest
:=
ManifestV2
{
SchemaVersion
:
2
,
SchemaVersion
:
2
,
...
...
server/routes.go
View file @
548a7df0
...
@@ -719,62 +719,65 @@ func GetModelInfo(req api.ShowRequest) (*api.ShowResponse, error) {
...
@@ -719,62 +719,65 @@ func GetModelInfo(req api.ShowRequest) (*api.ShowResponse, error) {
}
}
func
(
s
*
Server
)
ListModelsHandler
(
c
*
gin
.
Context
)
{
func
(
s
*
Server
)
ListModelsHandler
(
c
*
gin
.
Context
)
{
models
:=
make
([]
api
.
ModelResponse
,
0
)
manifests
,
err
:=
GetManifestPath
()
manifestsPath
,
err
:=
GetManifestPath
()
if
err
!=
nil
{
if
err
!=
nil
{
c
.
JSON
(
http
.
StatusInternalServerError
,
gin
.
H
{
"error"
:
err
.
Error
()})
c
.
JSON
(
http
.
StatusInternalServerError
,
gin
.
H
{
"error"
:
err
.
Error
()})
return
return
}
}
modelResponse
:=
func
(
modelName
string
)
(
api
.
ModelResponse
,
error
)
{
var
models
[]
api
.
ModelResponse
model
,
err
:=
GetModel
(
modelName
)
if
err
:=
filepath
.
Walk
(
manifests
,
func
(
path
string
,
info
os
.
FileInfo
,
_
error
)
error
{
if
!
info
.
IsDir
()
{
rel
,
err
:=
filepath
.
Rel
(
manifests
,
path
)
if
err
!=
nil
{
if
err
!=
nil
{
return
api
.
ModelResponse
{},
err
return
err
}
modelDetails
:=
api
.
ModelDetails
{
Format
:
model
.
Config
.
ModelFormat
,
Family
:
model
.
Config
.
ModelFamily
,
Families
:
model
.
Config
.
ModelFamilies
,
ParameterSize
:
model
.
Config
.
ModelType
,
QuantizationLevel
:
model
.
Config
.
FileType
,
}
}
return
api
.
ModelResponse
{
n
:=
model
.
ParseNameFromFilepath
(
rel
)
Model
:
model
.
ShortName
,
m
,
err
:=
ParseNamedManifest
(
n
)
Name
:
model
.
ShortName
,
if
err
!=
nil
{
Size
:
model
.
Size
,
return
err
Digest
:
model
.
Digest
,
Details
:
modelDetails
,
},
nil
}
}
walkFunc
:=
func
(
path
string
,
info
os
.
FileInfo
,
_
error
)
error
{
f
,
err
:=
m
.
Config
.
Open
()
if
!
info
.
IsDir
()
{
path
,
tag
:=
filepath
.
Split
(
path
)
model
:=
strings
.
Trim
(
strings
.
TrimPrefix
(
path
,
manifestsPath
),
string
(
os
.
PathSeparator
))
modelPath
:=
strings
.
Join
([]
string
{
model
,
tag
},
":"
)
canonicalModelPath
:=
strings
.
ReplaceAll
(
modelPath
,
string
(
os
.
PathSeparator
),
"/"
)
resp
,
err
:=
modelResponse
(
canonicalModelPath
)
if
err
!=
nil
{
if
err
!=
nil
{
slog
.
Info
(
fmt
.
Sprintf
(
"skipping file: %s"
,
canonicalModelPath
))
return
err
// nolint: nilerr
return
nil
}
}
defer
f
.
Close
()
resp
.
ModifiedAt
=
info
.
ModTime
()
var
c
ConfigV2
models
=
append
(
models
,
resp
)
if
err
:=
json
.
NewDecoder
(
f
)
.
Decode
(
&
c
);
err
!=
nil
{
return
err
}
}
return
nil
// tag should never be masked
models
=
append
(
models
,
api
.
ModelResponse
{
Model
:
n
.
DisplayShortest
(),
Name
:
n
.
DisplayShortest
(),
Size
:
m
.
Size
(),
Digest
:
m
.
Digest
,
ModifiedAt
:
info
.
ModTime
(),
Details
:
api
.
ModelDetails
{
Format
:
c
.
ModelFormat
,
Family
:
c
.
ModelFamily
,
Families
:
c
.
ModelFamilies
,
ParameterSize
:
c
.
ModelType
,
QuantizationLevel
:
c
.
FileType
,
},
})
}
}
if
err
:=
filepath
.
Walk
(
manifestsPath
,
walkFunc
);
err
!=
nil
{
return
nil
});
err
!=
nil
{
c
.
JSON
(
http
.
StatusInternalServerError
,
gin
.
H
{
"error"
:
err
.
Error
()})
c
.
JSON
(
http
.
StatusInternalServerError
,
gin
.
H
{
"error"
:
err
.
Error
()})
return
return
}
}
slices
.
SortStableFunc
(
models
,
func
(
i
,
j
api
.
ModelResponse
)
int
{
// most recently modified first
return
cmp
.
Compare
(
j
.
ModifiedAt
.
Unix
(),
i
.
ModifiedAt
.
Unix
())
})
c
.
JSON
(
http
.
StatusOK
,
api
.
ListResponse
{
Models
:
models
})
c
.
JSON
(
http
.
StatusOK
,
api
.
ListResponse
{
Models
:
models
})
}
}
...
...
types/model/name.go
View file @
548a7df0
...
@@ -35,6 +35,12 @@ func Unqualified(n Name) error {
...
@@ -35,6 +35,12 @@ func Unqualified(n Name) error {
// spot in logs.
// spot in logs.
const
MissingPart
=
"!MISSING!"
const
MissingPart
=
"!MISSING!"
const
(
defaultHost
=
"registry.ollama.ai"
defaultNamespace
=
"library"
defaultTag
=
"latest"
)
// DefaultName returns a name with the default values for the host, namespace,
// DefaultName returns a name with the default values for the host, namespace,
// and tag parts. The model and digest parts are empty.
// and tag parts. The model and digest parts are empty.
//
//
...
@@ -43,9 +49,9 @@ const MissingPart = "!MISSING!"
...
@@ -43,9 +49,9 @@ const MissingPart = "!MISSING!"
// - The default tag is ("latest")
// - The default tag is ("latest")
func
DefaultName
()
Name
{
func
DefaultName
()
Name
{
return
Name
{
return
Name
{
Host
:
"registry.ollama.ai"
,
Host
:
defaultHost
,
Namespace
:
"library"
,
Namespace
:
defaultNamespace
,
Tag
:
"latest"
,
Tag
:
defaultTag
,
}
}
}
}
...
@@ -169,6 +175,27 @@ func ParseNameBare(s string) Name {
...
@@ -169,6 +175,27 @@ func ParseNameBare(s string) Name {
return
n
return
n
}
}
// ParseNameFromFilepath parses a 4-part filepath as a Name. The parts are
// expected to be in the form:
//
// { host } "/" { namespace } "/" { model } "/" { tag }
func
ParseNameFromFilepath
(
s
string
)
(
n
Name
)
{
parts
:=
strings
.
Split
(
s
,
string
(
filepath
.
Separator
))
if
len
(
parts
)
!=
4
{
return
Name
{}
}
n
.
Host
=
parts
[
0
]
n
.
Namespace
=
parts
[
1
]
n
.
Model
=
parts
[
2
]
n
.
Tag
=
parts
[
3
]
if
!
n
.
IsFullyQualified
()
{
return
Name
{}
}
return
n
}
// Merge merges the host, namespace, and tag parts of the two names,
// Merge merges the host, namespace, and tag parts of the two names,
// preferring the non-empty parts of a.
// preferring the non-empty parts of a.
func
Merge
(
a
,
b
Name
)
Name
{
func
Merge
(
a
,
b
Name
)
Name
{
...
@@ -203,6 +230,27 @@ func (n Name) String() string {
...
@@ -203,6 +230,27 @@ func (n Name) String() string {
return
b
.
String
()
return
b
.
String
()
}
}
// DisplayShort returns a short string version of the name.
func
(
n
Name
)
DisplayShortest
()
string
{
var
sb
strings
.
Builder
if
n
.
Host
!=
defaultHost
{
sb
.
WriteString
(
n
.
Host
)
sb
.
WriteByte
(
'/'
)
sb
.
WriteString
(
n
.
Namespace
)
sb
.
WriteByte
(
'/'
)
}
else
if
n
.
Namespace
!=
defaultNamespace
{
sb
.
WriteString
(
n
.
Namespace
)
sb
.
WriteByte
(
'/'
)
}
// always include model and tag
sb
.
WriteString
(
n
.
Model
)
sb
.
WriteString
(
":"
)
sb
.
WriteString
(
n
.
Tag
)
return
sb
.
String
()
}
// IsValid reports whether all parts of the name are present and valid. The
// IsValid reports whether all parts of the name are present and valid. The
// digest is a special case, and is checked for validity only if present.
// digest is a special case, and is checked for validity only if present.
func
(
n
Name
)
IsValid
()
bool
{
func
(
n
Name
)
IsValid
()
bool
{
...
...
types/model/name_test.go
View file @
548a7df0
...
@@ -309,6 +309,49 @@ func TestParseDigest(t *testing.T) {
...
@@ -309,6 +309,49 @@ func TestParseDigest(t *testing.T) {
}
}
}
}
func
TestParseNameFromFilepath
(
t
*
testing
.
T
)
{
cases
:=
map
[
string
]
Name
{
filepath
.
Join
(
"host"
,
"namespace"
,
"model"
,
"tag"
)
:
{
Host
:
"host"
,
Namespace
:
"namespace"
,
Model
:
"model"
,
Tag
:
"tag"
},
filepath
.
Join
(
"host:port"
,
"namespace"
,
"model"
,
"tag"
)
:
{
Host
:
"host:port"
,
Namespace
:
"namespace"
,
Model
:
"model"
,
Tag
:
"tag"
},
filepath
.
Join
(
"namespace"
,
"model"
,
"tag"
)
:
{},
filepath
.
Join
(
"model"
,
"tag"
)
:
{},
filepath
.
Join
(
"model"
)
:
{},
filepath
.
Join
(
".."
,
".."
,
"model"
,
"tag"
)
:
{},
filepath
.
Join
(
""
,
"namespace"
,
"."
,
"tag"
)
:
{},
filepath
.
Join
(
"."
,
"."
,
"."
,
"."
)
:
{},
filepath
.
Join
(
"/"
,
"path"
,
"to"
,
"random"
,
"file"
)
:
{},
}
for
in
,
want
:=
range
cases
{
t
.
Run
(
in
,
func
(
t
*
testing
.
T
)
{
got
:=
ParseNameFromFilepath
(
in
)
if
!
reflect
.
DeepEqual
(
got
,
want
)
{
t
.
Errorf
(
"parseNameFromFilepath(%q) = %v; want %v"
,
in
,
got
,
want
)
}
})
}
}
func
TestDisplayShortest
(
t
*
testing
.
T
)
{
cases
:=
map
[
string
]
string
{
"registry.ollama.ai/library/model:latest"
:
"model:latest"
,
"registry.ollama.ai/library/model:tag"
:
"model:tag"
,
"registry.ollama.ai/namespace/model:tag"
:
"namespace/model:tag"
,
"host/namespace/model:tag"
:
"host/namespace/model:tag"
,
"host/library/model:tag"
:
"host/library/model:tag"
,
}
for
in
,
want
:=
range
cases
{
t
.
Run
(
in
,
func
(
t
*
testing
.
T
)
{
got
:=
ParseNameBare
(
in
)
.
DisplayShortest
()
if
got
!=
want
{
t
.
Errorf
(
"parseName(%q).DisplayShortest() = %q; want %q"
,
in
,
got
,
want
)
}
})
}
}
func
FuzzName
(
f
*
testing
.
F
)
{
func
FuzzName
(
f
*
testing
.
F
)
{
for
s
:=
range
testCases
{
for
s
:=
range
testCases
{
f
.
Add
(
s
)
f
.
Add
(
s
)
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
.
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment