Skip to content

Commit 77954be

Browse files
authored
Merge pull request ollama#898 from jmorganca/mxyng/build-context
create remote models
2 parents ecd7134 + 54f92f0 commit 77954be

File tree

6 files changed

+371
-194
lines changed

6 files changed

+371
-194
lines changed

api/client.go

+25-1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"bytes"
66
"context"
77
"encoding/json"
8+
"errors"
89
"fmt"
910
"io"
1011
"net"
@@ -95,11 +96,19 @@ func (c *Client) do(ctx context.Context, method, path string, reqData, respData
9596
var reqBody io.Reader
9697
var data []byte
9798
var err error
98-
if reqData != nil {
99+
100+
switch reqData := reqData.(type) {
101+
case io.Reader:
102+
// reqData is already an io.Reader
103+
reqBody = reqData
104+
case nil:
105+
// noop
106+
default:
99107
data, err = json.Marshal(reqData)
100108
if err != nil {
101109
return err
102110
}
111+
103112
reqBody = bytes.NewReader(data)
104113
}
105114

@@ -287,3 +296,18 @@ func (c *Client) Heartbeat(ctx context.Context) error {
287296
}
288297
return nil
289298
}
299+
300+
func (c *Client) CreateBlob(ctx context.Context, digest string, r io.Reader) error {
301+
if err := c.do(ctx, http.MethodHead, fmt.Sprintf("/api/blobs/%s", digest), nil, nil); err != nil {
302+
var statusError StatusError
303+
if !errors.As(err, &statusError) || statusError.StatusCode != http.StatusNotFound {
304+
return err
305+
}
306+
307+
if err := c.do(ctx, http.MethodPost, fmt.Sprintf("/api/blobs/%s", digest), r, nil); err != nil {
308+
return err
309+
}
310+
}
311+
312+
return nil
313+
}

api/types.go

+4-3
Original file line numberDiff line numberDiff line change
@@ -99,9 +99,10 @@ type EmbeddingResponse struct {
9999
}
100100

101101
type CreateRequest struct {
102-
Name string `json:"name"`
103-
Path string `json:"path"`
104-
Stream *bool `json:"stream,omitempty"`
102+
Name string `json:"name"`
103+
Path string `json:"path"`
104+
Modelfile string `json:"modelfile"`
105+
Stream *bool `json:"stream,omitempty"`
105106
}
106107

107108
type DeleteRequest struct {

cmd/cmd.go

+59-13
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
package cmd
22

33
import (
4+
"bytes"
45
"context"
56
"crypto/ed25519"
67
"crypto/rand"
8+
"crypto/sha256"
79
"encoding/pem"
810
"errors"
911
"fmt"
@@ -27,6 +29,7 @@ import (
2729

2830
"github.com/jmorganca/ollama/api"
2931
"github.com/jmorganca/ollama/format"
32+
"github.com/jmorganca/ollama/parser"
3033
"github.com/jmorganca/ollama/progressbar"
3134
"github.com/jmorganca/ollama/readline"
3235
"github.com/jmorganca/ollama/server"
@@ -45,17 +48,64 @@ func CreateHandler(cmd *cobra.Command, args []string) error {
4548
return err
4649
}
4750

48-
var spinner *Spinner
51+
modelfile, err := os.ReadFile(filename)
52+
if err != nil {
53+
return err
54+
}
55+
56+
spinner := NewSpinner("transferring context")
57+
go spinner.Spin(100 * time.Millisecond)
58+
59+
commands, err := parser.Parse(bytes.NewReader(modelfile))
60+
if err != nil {
61+
return err
62+
}
63+
64+
home, err := os.UserHomeDir()
65+
if err != nil {
66+
return err
67+
}
68+
69+
for _, c := range commands {
70+
switch c.Name {
71+
case "model", "adapter":
72+
path := c.Args
73+
if path == "~" {
74+
path = home
75+
} else if strings.HasPrefix(path, "~/") {
76+
path = filepath.Join(home, path[2:])
77+
}
78+
79+
bin, err := os.Open(path)
80+
if errors.Is(err, os.ErrNotExist) && c.Name == "model" {
81+
continue
82+
} else if err != nil {
83+
return err
84+
}
85+
defer bin.Close()
86+
87+
hash := sha256.New()
88+
if _, err := io.Copy(hash, bin); err != nil {
89+
return err
90+
}
91+
bin.Seek(0, io.SeekStart)
92+
93+
digest := fmt.Sprintf("sha256:%x", hash.Sum(nil))
94+
if err = client.CreateBlob(cmd.Context(), digest, bin); err != nil {
95+
return err
96+
}
97+
98+
modelfile = bytes.ReplaceAll(modelfile, []byte(c.Args), []byte("@"+digest))
99+
}
100+
}
49101

50102
var currentDigest string
51103
var bar *progressbar.ProgressBar
52104

53-
request := api.CreateRequest{Name: args[0], Path: filename}
105+
request := api.CreateRequest{Name: args[0], Path: filename, Modelfile: string(modelfile)}
54106
fn := func(resp api.ProgressResponse) error {
55107
if resp.Digest != currentDigest && resp.Digest != "" {
56-
if spinner != nil {
57-
spinner.Stop()
58-
}
108+
spinner.Stop()
59109
currentDigest = resp.Digest
60110
// pulling
61111
bar = progressbar.DefaultBytes(
@@ -67,9 +117,7 @@ func CreateHandler(cmd *cobra.Command, args []string) error {
67117
bar.Set64(resp.Completed)
68118
} else {
69119
currentDigest = ""
70-
if spinner != nil {
71-
spinner.Stop()
72-
}
120+
spinner.Stop()
73121
spinner = NewSpinner(resp.Status)
74122
go spinner.Spin(100 * time.Millisecond)
75123
}
@@ -81,11 +129,9 @@ func CreateHandler(cmd *cobra.Command, args []string) error {
81129
return err
82130
}
83131

84-
if spinner != nil {
85-
spinner.Stop()
86-
if spinner.description != "success" {
87-
return errors.New("unexpected end to create model")
88-
}
132+
spinner.Stop()
133+
if spinner.description != "success" {
134+
return errors.New("unexpected end to create model")
89135
}
90136

91137
return nil

docs/api.md

+53-3
Original file line numberDiff line numberDiff line change
@@ -292,12 +292,13 @@ curl -X POST http://localhost:11434/api/generate -d '{
292292
POST /api/create
293293
```
294294

295-
Create a model from a [`Modelfile`](./modelfile.md)
295+
Create a model from a [`Modelfile`](./modelfile.md). It is recommended to set `modelfile` to the content of the Modelfile rather than just set `path`. This is a requirement for remote create. Remote model creation should also create any file blobs, fields such as `FROM` and `ADAPTER`, explicitly with the server using [Create a Blob](#create-a-blob) and the value to the path indicated in the response.
296296

297297
### Parameters
298298

299299
- `name`: name of the model to create
300-
- `path`: path to the Modelfile
300+
- `path`: path to the Modelfile (deprecated: please use modelfile instead)
301+
- `modelfile`: contents of the Modelfile
301302
- `stream`: (optional) if `false` the response will be returned as a single response object, rather than a stream of objects
302303

303304
### Examples
@@ -307,7 +308,8 @@ Create a model from a [`Modelfile`](./modelfile.md)
307308
```shell
308309
curl -X POST http://localhost:11434/api/create -d '{
309310
"name": "mario",
310-
"path": "~/Modelfile"
311+
"path": "~/Modelfile",
312+
"modelfile": "FROM llama2"
311313
}'
312314
```
313315

@@ -321,6 +323,54 @@ A stream of JSON objects. When finished, `status` is `success`.
321323
}
322324
```
323325

326+
### Check if a Blob Exists
327+
328+
```shell
329+
HEAD /api/blobs/:digest
330+
```
331+
332+
Check if a blob is known to the server.
333+
334+
#### Query Parameters
335+
336+
- `digest`: the SHA256 digest of the blob
337+
338+
#### Examples
339+
340+
##### Request
341+
342+
```shell
343+
curl -I http://localhost:11434/api/blobs/sha256:29fdb92e57cf0827ded04ae6461b5931d01fa595843f55d36f5b275a52087dd2
344+
```
345+
346+
##### Response
347+
348+
Return 200 OK if the blob exists, 404 Not Found if it does not.
349+
350+
### Create a Blob
351+
352+
```shell
353+
POST /api/blobs/:digest
354+
```
355+
356+
Create a blob from a file. Returns the server file path.
357+
358+
#### Query Parameters
359+
360+
- `digest`: the expected SHA256 digest of the file
361+
362+
#### Examples
363+
364+
##### Request
365+
366+
```shell
367+
curl -T model.bin -X POST http://localhost:11434/api/blobs/sha256:29fdb92e57cf0827ded04ae6461b5931d01fa595843f55d36f5b275a52087dd2
368+
```
369+
370+
##### Response
371+
372+
Return 201 Created if the blob was successfully created.
373+
324374
## List Local Models
325375

326376
```shell

0 commit comments

Comments
 (0)