Compare commits
6 Commits
v0.3.4
...
brucemacd/
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f84cc9939c | ||
|
|
5b3a21b578 | ||
|
|
ad0c19dde4 | ||
|
|
69eb06c40e | ||
|
|
1829fb61bd | ||
|
|
685a53534b |
@@ -669,7 +669,7 @@ curl http://localhost:11434/api/chat -d '{
|
||||
|
||||
```
|
||||
curl http://localhost:11434/api/chat -d '{
|
||||
"model": "mistral",
|
||||
"model": "llama3.1",
|
||||
"messages": [
|
||||
{
|
||||
"role": "user",
|
||||
@@ -708,7 +708,7 @@ curl http://localhost:11434/api/chat -d '{
|
||||
|
||||
```json
|
||||
{
|
||||
"model": "mistral:7b-instruct-v0.3-q4_K_M",
|
||||
"model": "llama3.1",
|
||||
"created_at": "2024-07-22T20:33:28.123648Z",
|
||||
"message": {
|
||||
"role": "assistant",
|
||||
@@ -1175,7 +1175,10 @@ curl http://localhost:11434/api/embed -d '{
|
||||
"embeddings": [[
|
||||
0.010071029, -0.0017594862, 0.05007221, 0.04692972, 0.054916814,
|
||||
0.008599704, 0.105441414, -0.025878139, 0.12958129, 0.031952348
|
||||
]]
|
||||
]],
|
||||
"total_duration": 14143917,
|
||||
"load_duration": 1019500,
|
||||
"prompt_eval_count": 8
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@@ -57,6 +57,11 @@ func Host() *url.URL {
|
||||
}
|
||||
}
|
||||
|
||||
// HasCustomOrigins returns true if custom origins are configured. Origins can be configured via the OLLAMA_ORIGINS environment variable.
|
||||
func HasCustomOrigins() bool {
|
||||
return Var("OLLAMA_ORIGINS") != ""
|
||||
}
|
||||
|
||||
// Origins returns a list of allowed origins. Origins can be configured via the OLLAMA_ORIGINS environment variable.
|
||||
func Origins() (origins []string) {
|
||||
if s := Var("OLLAMA_ORIGINS"); s != "" {
|
||||
|
||||
@@ -250,19 +250,21 @@ func GetModel(name string) (*Model, error) {
|
||||
Template: template.DefaultTemplate,
|
||||
}
|
||||
|
||||
filename, err := GetBlobsPath(manifest.Config.Digest)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if manifest.Config.Digest != "" {
|
||||
filename, err := GetBlobsPath(manifest.Config.Digest)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
configFile, err := os.Open(filename)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer configFile.Close()
|
||||
configFile, err := os.Open(filename)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer configFile.Close()
|
||||
|
||||
if err := json.NewDecoder(configFile).Decode(&model.Config); err != nil {
|
||||
return nil, err
|
||||
if err := json.NewDecoder(configFile).Decode(&model.Config); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
for _, layer := range manifest.Layers {
|
||||
@@ -714,8 +716,7 @@ func deleteUnusedLayers(skipModelPath *ModelPath, deleteMap map[string]struct{})
|
||||
// save (i.e. delete from the deleteMap) any files used in other manifests
|
||||
manifest, _, err := GetManifest(fmp)
|
||||
if err != nil {
|
||||
//nolint:nilerr
|
||||
return nil
|
||||
return err
|
||||
}
|
||||
|
||||
for _, layer := range manifest.Layers {
|
||||
@@ -782,7 +783,8 @@ func PruneLayers() error {
|
||||
|
||||
err = deleteUnusedLayers(nil, deleteMap)
|
||||
if err != nil {
|
||||
return err
|
||||
slog.Error(fmt.Sprintf("couldn't remove unused layers: %v", err))
|
||||
return nil
|
||||
}
|
||||
|
||||
slog.Info(fmt.Sprintf("total unused blobs removed: %d", len(deleteMap)))
|
||||
@@ -839,7 +841,9 @@ func PushModel(ctx context.Context, name string, regOpts *registryOptions, fn fu
|
||||
|
||||
var layers []*Layer
|
||||
layers = append(layers, manifest.Layers...)
|
||||
layers = append(layers, manifest.Config)
|
||||
if manifest.Config.Digest != "" {
|
||||
layers = append(layers, &manifest.Config)
|
||||
}
|
||||
|
||||
for _, layer := range layers {
|
||||
if err := uploadBlob(ctx, mp, layer, regOpts, fn); err != nil {
|
||||
@@ -890,7 +894,9 @@ func PullModel(ctx context.Context, name string, regOpts *registryOptions, fn fu
|
||||
for _, l := range manifest.Layers {
|
||||
deleteMap[l.Digest] = struct{}{}
|
||||
}
|
||||
deleteMap[manifest.Config.Digest] = struct{}{}
|
||||
if manifest.Config.Digest != "" {
|
||||
deleteMap[manifest.Config.Digest] = struct{}{}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -907,7 +913,9 @@ func PullModel(ctx context.Context, name string, regOpts *registryOptions, fn fu
|
||||
|
||||
var layers []*Layer
|
||||
layers = append(layers, manifest.Layers...)
|
||||
layers = append(layers, manifest.Config)
|
||||
if manifest.Config.Digest != "" {
|
||||
layers = append(layers, &manifest.Config)
|
||||
}
|
||||
|
||||
skipVerify := make(map[string]bool)
|
||||
for _, layer := range layers {
|
||||
@@ -971,7 +979,8 @@ func PullModel(ctx context.Context, name string, regOpts *registryOptions, fn fu
|
||||
fn(api.ProgressResponse{Status: "removing any unused layers"})
|
||||
err = deleteUnusedLayers(nil, deleteMap)
|
||||
if err != nil {
|
||||
return err
|
||||
slog.Error(fmt.Sprintf("couldn't remove unused layers: %v", err))
|
||||
fn(api.ProgressResponse{Status: fmt.Sprintf("couldn't remove unused layers: %v", err)})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ package server
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
@@ -61,6 +62,10 @@ func NewLayer(r io.Reader, mediatype string) (*Layer, error) {
|
||||
}
|
||||
|
||||
func NewLayerFromLayer(digest, mediatype, from string) (*Layer, error) {
|
||||
if digest == "" {
|
||||
return nil, errors.New("creating new layer from layer with empty digest")
|
||||
}
|
||||
|
||||
blob, err := GetBlobsPath(digest)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -81,6 +86,10 @@ func NewLayerFromLayer(digest, mediatype, from string) (*Layer, error) {
|
||||
}
|
||||
|
||||
func (l *Layer) Open() (io.ReadSeekCloser, error) {
|
||||
if l.Digest == "" {
|
||||
return nil, errors.New("opening layer with empty digest")
|
||||
}
|
||||
|
||||
blob, err := GetBlobsPath(l.Digest)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -90,13 +99,17 @@ func (l *Layer) Open() (io.ReadSeekCloser, error) {
|
||||
}
|
||||
|
||||
func (l *Layer) Remove() error {
|
||||
if l.Digest == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
ms, err := Manifests()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, m := range ms {
|
||||
for _, layer := range append(m.Layers, m.Config) {
|
||||
for _, layer := range append(m.Layers, &m.Config) {
|
||||
if layer.Digest == l.Digest {
|
||||
// something is using this layer
|
||||
return nil
|
||||
|
||||
@@ -16,7 +16,7 @@ import (
|
||||
type Manifest struct {
|
||||
SchemaVersion int `json:"schemaVersion"`
|
||||
MediaType string `json:"mediaType"`
|
||||
Config *Layer `json:"config"`
|
||||
Config Layer `json:"config"`
|
||||
Layers []*Layer `json:"layers"`
|
||||
|
||||
filepath string
|
||||
@@ -25,7 +25,7 @@ type Manifest struct {
|
||||
}
|
||||
|
||||
func (m *Manifest) Size() (size int64) {
|
||||
for _, layer := range append(m.Layers, m.Config) {
|
||||
for _, layer := range append(m.Layers, &m.Config) {
|
||||
size += layer.Size
|
||||
}
|
||||
|
||||
@@ -46,11 +46,13 @@ func (m *Manifest) Remove() error {
|
||||
}
|
||||
|
||||
func (m *Manifest) RemoveLayers() error {
|
||||
for _, layer := range append(m.Layers, m.Config) {
|
||||
if err := layer.Remove(); errors.Is(err, os.ErrNotExist) {
|
||||
slog.Debug("layer does not exist", "digest", layer.Digest)
|
||||
} else if err != nil {
|
||||
return err
|
||||
for _, layer := range append(m.Layers, &m.Config) {
|
||||
if layer.Digest != "" {
|
||||
if err := layer.Remove(); errors.Is(err, os.ErrNotExist) {
|
||||
slog.Debug("layer does not exist", "digest", layer.Digest)
|
||||
} else if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -113,7 +115,7 @@ func WriteManifest(name model.Name, config *Layer, layers []*Layer) error {
|
||||
m := Manifest{
|
||||
SchemaVersion: 2,
|
||||
MediaType: "application/vnd.docker.distribution.manifest.v2+json",
|
||||
Config: config,
|
||||
Config: *config,
|
||||
Layers: layers,
|
||||
}
|
||||
|
||||
|
||||
116
server/routes.go
116
server/routes.go
@@ -824,17 +824,20 @@ func (s *Server) ListModelsHandler(c *gin.Context) {
|
||||
|
||||
models := []api.ListModelResponse{}
|
||||
for n, m := range ms {
|
||||
f, err := m.Config.Open()
|
||||
if err != nil {
|
||||
slog.Warn("bad manifest filepath", "name", n, "error", err)
|
||||
continue
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
var cf ConfigV2
|
||||
if err := json.NewDecoder(f).Decode(&cf); err != nil {
|
||||
slog.Warn("bad manifest config", "name", n, "error", err)
|
||||
continue
|
||||
|
||||
if m.Config.Digest != "" {
|
||||
f, err := m.Config.Open()
|
||||
if err != nil {
|
||||
slog.Warn("bad manifest filepath", "name", n, "error", err)
|
||||
continue
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
if err := json.NewDecoder(f).Decode(&cf); err != nil {
|
||||
slog.Warn("bad manifest config", "name", n, "error", err)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
// tag should never be masked
|
||||
@@ -1048,52 +1051,73 @@ func allowedHostsMiddleware(addr net.Addr) gin.HandlerFunc {
|
||||
}
|
||||
|
||||
func (s *Server) GenerateRoutes() http.Handler {
|
||||
config := cors.DefaultConfig()
|
||||
config.AllowWildcard = true
|
||||
config.AllowBrowserExtensions = true
|
||||
config.AllowHeaders = []string{"Authorization", "Content-Type", "User-Agent", "Accept", "X-Requested-With"}
|
||||
baseConfig := cors.DefaultConfig()
|
||||
baseConfig.AllowWildcard = true
|
||||
baseConfig.AllowBrowserExtensions = true
|
||||
baseConfig.AllowHeaders = []string{"Authorization", "Content-Type", "User-Agent", "Accept", "X-Requested-With"}
|
||||
openAIProperties := []string{"lang", "package-version", "os", "arch", "runtime", "runtime-version", "async"}
|
||||
for _, prop := range openAIProperties {
|
||||
config.AllowHeaders = append(config.AllowHeaders, "x-stainless-"+prop)
|
||||
baseConfig.AllowHeaders = append(baseConfig.AllowHeaders, "x-stainless-"+prop)
|
||||
}
|
||||
config.AllowOrigins = envconfig.Origins()
|
||||
|
||||
r := gin.Default()
|
||||
r.Use(
|
||||
cors.New(config),
|
||||
allowedHostsMiddleware(s.addr),
|
||||
)
|
||||
|
||||
r.POST("/api/pull", s.PullModelHandler)
|
||||
r.POST("/api/generate", s.GenerateHandler)
|
||||
r.POST("/api/chat", s.ChatHandler)
|
||||
r.POST("/api/embed", s.EmbedHandler)
|
||||
r.POST("/api/embeddings", s.EmbeddingsHandler)
|
||||
r.POST("/api/create", s.CreateModelHandler)
|
||||
r.POST("/api/push", s.PushModelHandler)
|
||||
r.POST("/api/copy", s.CopyModelHandler)
|
||||
r.DELETE("/api/delete", s.DeleteModelHandler)
|
||||
r.POST("/api/show", s.ShowModelHandler)
|
||||
r.POST("/api/blobs/:digest", s.CreateBlobHandler)
|
||||
r.HEAD("/api/blobs/:digest", s.HeadBlobHandler)
|
||||
r.GET("/api/ps", s.ProcessHandler)
|
||||
openConfig := baseConfig
|
||||
openConfig.AllowOrigins = envconfig.Origins()
|
||||
if !envconfig.HasCustomOrigins() {
|
||||
openConfig.AllowOrigins = append(openConfig.AllowOrigins, "https://ollama.com")
|
||||
openConfig.AllowOrigins = append(openConfig.AllowOrigins, "https://www.ollama.com")
|
||||
}
|
||||
|
||||
// Compatibility endpoints
|
||||
r.POST("/v1/chat/completions", openai.ChatMiddleware(), s.ChatHandler)
|
||||
r.POST("/v1/completions", openai.CompletionsMiddleware(), s.GenerateHandler)
|
||||
r.POST("/v1/embeddings", openai.EmbeddingsMiddleware(), s.EmbedHandler)
|
||||
r.GET("/v1/models", openai.ListMiddleware(), s.ListModelsHandler)
|
||||
r.GET("/v1/models/:model", openai.RetrieveMiddleware(), s.ShowModelHandler)
|
||||
openBaseGroup := r.Group("/")
|
||||
openBaseGroup.Use(cors.New(openConfig), allowedHostsMiddleware(s.addr))
|
||||
{
|
||||
openBaseGroup.GET("/", func(c *gin.Context) { c.String(http.StatusOK, "Ollama is running") })
|
||||
openBaseGroup.HEAD("/", func(c *gin.Context) { c.String(http.StatusOK, "Ollama is running") })
|
||||
}
|
||||
|
||||
for _, method := range []string{http.MethodGet, http.MethodHead} {
|
||||
r.Handle(method, "/", func(c *gin.Context) {
|
||||
c.String(http.StatusOK, "Ollama is running")
|
||||
openAPIGroup := r.Group("/api")
|
||||
openAPIGroup.Use(cors.New(openConfig), allowedHostsMiddleware(s.addr))
|
||||
{
|
||||
openAPIGroup.OPTIONS("/*path", func(c *gin.Context) {
|
||||
c.Status(http.StatusOK)
|
||||
})
|
||||
openAPIGroup.POST("/pull", s.PullModelHandler)
|
||||
openAPIGroup.POST("/generate", s.GenerateHandler)
|
||||
openAPIGroup.POST("/chat", s.ChatHandler)
|
||||
openAPIGroup.POST("/embed", s.EmbedHandler)
|
||||
openAPIGroup.POST("/embeddings", s.EmbeddingsHandler)
|
||||
openAPIGroup.POST("/show", s.ShowModelHandler)
|
||||
openAPIGroup.GET("/tags", s.ListModelsHandler)
|
||||
openAPIGroup.HEAD("/tags", s.ListModelsHandler)
|
||||
openAPIGroup.GET("/version", func(c *gin.Context) { c.JSON(http.StatusOK, gin.H{"version": version.Version}) })
|
||||
openAPIGroup.HEAD("/version", func(c *gin.Context) { c.JSON(http.StatusOK, gin.H{"version": version.Version}) })
|
||||
}
|
||||
|
||||
r.Handle(method, "/api/tags", s.ListModelsHandler)
|
||||
r.Handle(method, "/api/version", func(c *gin.Context) {
|
||||
c.JSON(http.StatusOK, gin.H{"version": version.Version})
|
||||
})
|
||||
restrictedConfig := baseConfig
|
||||
restrictedConfig.AllowOrigins = envconfig.Origins()
|
||||
restrictedAPIGroup := r.Group("/api")
|
||||
restrictedAPIGroup.Use(cors.New(restrictedConfig), allowedHostsMiddleware(s.addr))
|
||||
{
|
||||
restrictedAPIGroup.POST("/create", s.CreateModelHandler)
|
||||
restrictedAPIGroup.POST("/push", s.PushModelHandler)
|
||||
restrictedAPIGroup.POST("/copy", s.CopyModelHandler)
|
||||
restrictedAPIGroup.DELETE("/delete", s.DeleteModelHandler)
|
||||
restrictedAPIGroup.POST("/blobs/:digest", s.CreateBlobHandler)
|
||||
restrictedAPIGroup.HEAD("/blobs/:digest", s.HeadBlobHandler)
|
||||
restrictedAPIGroup.GET("/ps", s.ProcessHandler)
|
||||
}
|
||||
|
||||
openAIConfig := baseConfig
|
||||
openAIConfig.AllowOrigins = envconfig.Origins()
|
||||
openAIGroup := r.Group("/v1")
|
||||
openAIGroup.Use(cors.New(openAIConfig), allowedHostsMiddleware(s.addr))
|
||||
{
|
||||
openAIGroup.POST("/chat/completions", openai.ChatMiddleware(), s.ChatHandler)
|
||||
openAIGroup.POST("/completions", openai.CompletionsMiddleware(), s.GenerateHandler)
|
||||
openAIGroup.POST("/embeddings", openai.EmbeddingsMiddleware(), s.EmbedHandler)
|
||||
openAIGroup.GET("/models", openai.ListMiddleware(), s.ListModelsHandler)
|
||||
openAIGroup.GET("/models/:model", openai.RetrieveMiddleware(), s.ShowModelHandler)
|
||||
}
|
||||
|
||||
return r
|
||||
|
||||
Reference in New Issue
Block a user