|
@@ -9,6 +9,7 @@ import (
|
|
"errors"
|
|
"errors"
|
|
"fmt"
|
|
"fmt"
|
|
"io"
|
|
"io"
|
|
|
|
+ "io/fs"
|
|
"log/slog"
|
|
"log/slog"
|
|
"math"
|
|
"math"
|
|
"net"
|
|
"net"
|
|
@@ -120,10 +121,26 @@ func (s *Server) GenerateHandler(c *gin.Context) {
|
|
return
|
|
return
|
|
}
|
|
}
|
|
|
|
|
|
- model, err := GetModel(req.Model)
|
|
|
|
|
|
+ name := model.ParseName(req.Model)
|
|
|
|
+ if !name.IsValid() {
|
|
|
|
+ // Ideally this is "invalid model name" but we're keeping with
|
|
|
|
+ // what the API currently returns until we can change it.
|
|
|
|
+ c.JSON(http.StatusNotFound, gin.H{"error": fmt.Sprintf("model '%s' not found", req.Model)})
|
|
|
|
+ return
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // We cannot currently consolidate this into GetModel because all we'll
|
|
|
|
+ // induce infinite recursion given the current code structure.
|
|
|
|
+ name, err := getExistingName(name)
|
|
|
|
+ if err != nil {
|
|
|
|
+ c.JSON(http.StatusNotFound, gin.H{"error": fmt.Sprintf("model '%s' not found", req.Model)})
|
|
|
|
+ return
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ model, err := GetModel(name.String())
|
|
if err != nil {
|
|
if err != nil {
|
|
switch {
|
|
switch {
|
|
- case os.IsNotExist(err):
|
|
|
|
|
|
+ case errors.Is(err, fs.ErrNotExist):
|
|
c.JSON(http.StatusNotFound, gin.H{"error": fmt.Sprintf("model '%s' not found", req.Model)})
|
|
c.JSON(http.StatusNotFound, gin.H{"error": fmt.Sprintf("model '%s' not found", req.Model)})
|
|
case err.Error() == "invalid model name":
|
|
case err.Error() == "invalid model name":
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
|
@@ -157,7 +174,7 @@ func (s *Server) GenerateHandler(c *gin.Context) {
|
|
caps = append(caps, CapabilityInsert)
|
|
caps = append(caps, CapabilityInsert)
|
|
}
|
|
}
|
|
|
|
|
|
- r, m, opts, err := s.scheduleRunner(c.Request.Context(), req.Model, caps, req.Options, req.KeepAlive)
|
|
|
|
|
|
+ r, m, opts, err := s.scheduleRunner(c.Request.Context(), name.String(), caps, req.Options, req.KeepAlive)
|
|
if errors.Is(err, errCapabilityCompletion) {
|
|
if errors.Is(err, errCapabilityCompletion) {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": fmt.Sprintf("%q does not support generate", req.Model)})
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": fmt.Sprintf("%q does not support generate", req.Model)})
|
|
return
|
|
return
|
|
@@ -386,7 +403,13 @@ func (s *Server) EmbedHandler(c *gin.Context) {
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
- r, m, opts, err := s.scheduleRunner(c.Request.Context(), req.Model, []Capability{}, req.Options, req.KeepAlive)
|
|
|
|
|
|
+ name, err := getExistingName(model.ParseName(req.Model))
|
|
|
|
+ if err != nil {
|
|
|
|
+ c.JSON(http.StatusNotFound, gin.H{"error": fmt.Sprintf("model '%s' not found", req.Model)})
|
|
|
|
+ return
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ r, m, opts, err := s.scheduleRunner(c.Request.Context(), name.String(), []Capability{}, req.Options, req.KeepAlive)
|
|
if err != nil {
|
|
if err != nil {
|
|
handleScheduleError(c, req.Model, err)
|
|
handleScheduleError(c, req.Model, err)
|
|
return
|
|
return
|
|
@@ -489,7 +512,13 @@ func (s *Server) EmbeddingsHandler(c *gin.Context) {
|
|
return
|
|
return
|
|
}
|
|
}
|
|
|
|
|
|
- r, _, _, err := s.scheduleRunner(c.Request.Context(), req.Model, []Capability{}, req.Options, req.KeepAlive)
|
|
|
|
|
|
+ name := model.ParseName(req.Model)
|
|
|
|
+ if !name.IsValid() {
|
|
|
|
+ c.JSON(http.StatusBadRequest, gin.H{"error": "model is required"})
|
|
|
|
+ return
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ r, _, _, err := s.scheduleRunner(c.Request.Context(), name.String(), []Capability{}, req.Options, req.KeepAlive)
|
|
if err != nil {
|
|
if err != nil {
|
|
handleScheduleError(c, req.Model, err)
|
|
handleScheduleError(c, req.Model, err)
|
|
return
|
|
return
|
|
@@ -582,11 +611,11 @@ func (s *Server) PushHandler(c *gin.Context) {
|
|
return
|
|
return
|
|
}
|
|
}
|
|
|
|
|
|
- var model string
|
|
|
|
|
|
+ var mname string
|
|
if req.Model != "" {
|
|
if req.Model != "" {
|
|
- model = req.Model
|
|
|
|
|
|
+ mname = req.Model
|
|
} else if req.Name != "" {
|
|
} else if req.Name != "" {
|
|
- model = req.Name
|
|
|
|
|
|
+ mname = req.Name
|
|
} else {
|
|
} else {
|
|
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": "model is required"})
|
|
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": "model is required"})
|
|
return
|
|
return
|
|
@@ -606,7 +635,13 @@ func (s *Server) PushHandler(c *gin.Context) {
|
|
ctx, cancel := context.WithCancel(c.Request.Context())
|
|
ctx, cancel := context.WithCancel(c.Request.Context())
|
|
defer cancel()
|
|
defer cancel()
|
|
|
|
|
|
- if err := PushModel(ctx, model, regOpts, fn); err != nil {
|
|
|
|
|
|
+ name, err := getExistingName(model.ParseName(mname))
|
|
|
|
+ if err != nil {
|
|
|
|
+ ch <- gin.H{"error": err.Error()}
|
|
|
|
+ return
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if err := PushModel(ctx, name.DisplayShortest(), regOpts, fn); err != nil {
|
|
ch <- gin.H{"error": err.Error()}
|
|
ch <- gin.H{"error": err.Error()}
|
|
}
|
|
}
|
|
}()
|
|
}()
|
|
@@ -619,17 +654,29 @@ func (s *Server) PushHandler(c *gin.Context) {
|
|
streamResponse(c, ch)
|
|
streamResponse(c, ch)
|
|
}
|
|
}
|
|
|
|
|
|
-// getExistingName returns the original, on disk name if the input name is a
|
|
|
|
-// case-insensitive match, otherwise it returns the input name.
|
|
|
|
|
|
+// getExistingName searches the models directory for the longest prefix match of
|
|
|
|
+// the input name and returns the input name with all existing parts replaced
|
|
|
|
+// with each part found. If no parts are found, the input name is returned as
|
|
|
|
+// is.
|
|
func getExistingName(n model.Name) (model.Name, error) {
|
|
func getExistingName(n model.Name) (model.Name, error) {
|
|
var zero model.Name
|
|
var zero model.Name
|
|
existing, err := Manifests(true)
|
|
existing, err := Manifests(true)
|
|
if err != nil {
|
|
if err != nil {
|
|
return zero, err
|
|
return zero, err
|
|
}
|
|
}
|
|
|
|
+ var set model.Name // tracks parts already canonicalized
|
|
for e := range existing {
|
|
for e := range existing {
|
|
- if n.EqualFold(e) {
|
|
|
|
- return e, nil
|
|
|
|
|
|
+ if set.Host == "" && strings.EqualFold(e.Host, n.Host) {
|
|
|
|
+ n.Host = e.Host
|
|
|
|
+ }
|
|
|
|
+ if set.Namespace == "" && strings.EqualFold(e.Namespace, n.Namespace) {
|
|
|
|
+ n.Namespace = e.Namespace
|
|
|
|
+ }
|
|
|
|
+ if set.Model == "" && strings.EqualFold(e.Model, n.Model) {
|
|
|
|
+ n.Model = e.Model
|
|
|
|
+ }
|
|
|
|
+ if set.Tag == "" && strings.EqualFold(e.Tag, n.Tag) {
|
|
|
|
+ n.Tag = e.Tag
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return n, nil
|
|
return n, nil
|
|
@@ -658,7 +705,7 @@ func (s *Server) CreateHandler(c *gin.Context) {
|
|
}
|
|
}
|
|
|
|
|
|
if r.Path == "" && r.Modelfile == "" {
|
|
if r.Path == "" && r.Modelfile == "" {
|
|
- c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": "path or modelfile are required"})
|
|
|
|
|
|
+ c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": "path or Modelfile are required"})
|
|
return
|
|
return
|
|
}
|
|
}
|
|
|
|
|
|
@@ -722,6 +769,12 @@ func (s *Server) DeleteHandler(c *gin.Context) {
|
|
return
|
|
return
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+ n, err := getExistingName(n)
|
|
|
|
+ if err != nil {
|
|
|
|
+ c.JSON(http.StatusNotFound, gin.H{"error": fmt.Sprintf("model '%s' not found", cmp.Or(r.Model, r.Name))})
|
|
|
|
+ return
|
|
|
|
+ }
|
|
|
|
+
|
|
m, err := ParseNamedManifest(n)
|
|
m, err := ParseNamedManifest(n)
|
|
if err != nil {
|
|
if err != nil {
|
|
switch {
|
|
switch {
|
|
@@ -782,7 +835,16 @@ func (s *Server) ShowHandler(c *gin.Context) {
|
|
}
|
|
}
|
|
|
|
|
|
func GetModelInfo(req api.ShowRequest) (*api.ShowResponse, error) {
|
|
func GetModelInfo(req api.ShowRequest) (*api.ShowResponse, error) {
|
|
- m, err := GetModel(req.Model)
|
|
|
|
|
|
+ name := model.ParseName(req.Model)
|
|
|
|
+ if !name.IsValid() {
|
|
|
|
+ return nil, errModelPathInvalid
|
|
|
|
+ }
|
|
|
|
+ name, err := getExistingName(name)
|
|
|
|
+ if err != nil {
|
|
|
|
+ return nil, err
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ m, err := GetModel(name.String())
|
|
if err != nil {
|
|
if err != nil {
|
|
return nil, err
|
|
return nil, err
|
|
}
|
|
}
|
|
@@ -805,12 +867,7 @@ func GetModelInfo(req api.ShowRequest) (*api.ShowResponse, error) {
|
|
msgs[i] = api.Message{Role: msg.Role, Content: msg.Content}
|
|
msgs[i] = api.Message{Role: msg.Role, Content: msg.Content}
|
|
}
|
|
}
|
|
|
|
|
|
- n := model.ParseName(req.Model)
|
|
|
|
- if !n.IsValid() {
|
|
|
|
- return nil, errors.New("invalid model name")
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- manifest, err := ParseNamedManifest(n)
|
|
|
|
|
|
+ manifest, err := ParseNamedManifest(name)
|
|
if err != nil {
|
|
if err != nil {
|
|
return nil, err
|
|
return nil, err
|
|
}
|
|
}
|
|
@@ -1431,7 +1488,18 @@ func (s *Server) ChatHandler(c *gin.Context) {
|
|
caps = append(caps, CapabilityTools)
|
|
caps = append(caps, CapabilityTools)
|
|
}
|
|
}
|
|
|
|
|
|
- r, m, opts, err := s.scheduleRunner(c.Request.Context(), req.Model, caps, req.Options, req.KeepAlive)
|
|
|
|
|
|
+ name := model.ParseName(req.Model)
|
|
|
|
+ if !name.IsValid() {
|
|
|
|
+ c.JSON(http.StatusBadRequest, gin.H{"error": "model is required"})
|
|
|
|
+ return
|
|
|
|
+ }
|
|
|
|
+ name, err := getExistingName(name)
|
|
|
|
+ if err != nil {
|
|
|
|
+ c.JSON(http.StatusBadRequest, gin.H{"error": "model is required"})
|
|
|
|
+ return
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ r, m, opts, err := s.scheduleRunner(c.Request.Context(), name.String(), caps, req.Options, req.KeepAlive)
|
|
if errors.Is(err, errCapabilityCompletion) {
|
|
if errors.Is(err, errCapabilityCompletion) {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": fmt.Sprintf("%q does not support chat", req.Model)})
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": fmt.Sprintf("%q does not support chat", req.Model)})
|
|
return
|
|
return
|