Compare commits
13 Commits
insecure-r
...
fix-unknow
Author | SHA1 | Date | |
---|---|---|---|
![]() |
3168f51125 | ||
![]() |
37324a0a00 | ||
![]() |
20a5d99f77 | ||
![]() |
3b43cc019a | ||
![]() |
b8421dce3d | ||
![]() |
9f6e97865c | ||
![]() |
f5f0da06d9 | ||
![]() |
52f04e39f2 | ||
![]() |
3c8f4c03d7 | ||
![]() |
7ba1308595 | ||
![]() |
91cd54016c | ||
![]() |
09dc6273e3 | ||
![]() |
ebaa33ac28 |
10
README.md
10
README.md
@@ -125,3 +125,13 @@ Finally, run a model!
|
|||||||
```
|
```
|
||||||
./ollama run llama2
|
./ollama run llama2
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## REST API
|
||||||
|
|
||||||
|
### `POST /api/generate`
|
||||||
|
|
||||||
|
Generate text from a model.
|
||||||
|
|
||||||
|
```
|
||||||
|
curl -X POST http://localhost:11434/api/generate -d '{"model": "llama2", "prompt":"Why is the sky blue?"}'
|
||||||
|
```
|
||||||
|
@@ -27,7 +27,7 @@ func checkError(resp *http.Response, body []byte) error {
|
|||||||
err := json.Unmarshal(body, &apiError)
|
err := json.Unmarshal(body, &apiError)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// Use the full body as the message if we fail to decode a response.
|
// Use the full body as the message if we fail to decode a response.
|
||||||
apiError.Message = string(body)
|
apiError.ErrorMessage = string(body)
|
||||||
}
|
}
|
||||||
|
|
||||||
return apiError
|
return apiError
|
||||||
@@ -92,7 +92,6 @@ func (c *Client) do(ctx context.Context, method, path string, reqData, respData
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) stream(ctx context.Context, method, path string, data any, fn func([]byte) error) error {
|
func (c *Client) stream(ctx context.Context, method, path string, data any, fn func([]byte) error) error {
|
||||||
@@ -137,9 +136,9 @@ func (c *Client) stream(ctx context.Context, method, path string, data any, fn f
|
|||||||
|
|
||||||
if response.StatusCode >= 400 {
|
if response.StatusCode >= 400 {
|
||||||
return StatusError{
|
return StatusError{
|
||||||
StatusCode: response.StatusCode,
|
StatusCode: response.StatusCode,
|
||||||
Status: response.Status,
|
Status: response.Status,
|
||||||
Message: errorResponse.Error,
|
ErrorMessage: errorResponse.Error,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -211,15 +210,9 @@ func (c *Client) List(ctx context.Context) (*ListResponse, error) {
|
|||||||
return &lr, nil
|
return &lr, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type DeleteProgressFunc func(ProgressResponse) error
|
func (c *Client) Delete(ctx context.Context, req *DeleteRequest) error {
|
||||||
|
if err := c.do(ctx, http.MethodDelete, "/api/delete", req, nil); err != nil {
|
||||||
func (c *Client) Delete(ctx context.Context, req *DeleteRequest, fn DeleteProgressFunc) error {
|
return err
|
||||||
return c.stream(ctx, http.MethodDelete, "/api/delete", req, func(bts []byte) error {
|
}
|
||||||
var resp ProgressResponse
|
return nil
|
||||||
if err := json.Unmarshal(bts, &resp); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return fn(resp)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
21
api/types.go
21
api/types.go
@@ -8,16 +8,23 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type StatusError struct {
|
type StatusError struct {
|
||||||
StatusCode int
|
StatusCode int
|
||||||
Status string
|
Status string
|
||||||
Message string
|
ErrorMessage string `json:"error"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e StatusError) Error() string {
|
func (e StatusError) Error() string {
|
||||||
if e.Message != "" {
|
switch {
|
||||||
return fmt.Sprintf("%s: %s", e.Status, e.Message)
|
case e.Status != "" && e.ErrorMessage != "":
|
||||||
|
return fmt.Sprintf("%s: %s", e.Status, e.ErrorMessage)
|
||||||
|
case e.Status != "":
|
||||||
|
return e.Status
|
||||||
|
case e.ErrorMessage != "":
|
||||||
|
return e.ErrorMessage
|
||||||
|
default:
|
||||||
|
// this should not happen
|
||||||
|
return "something went wrong, please see the ollama server logs for details"
|
||||||
}
|
}
|
||||||
return e.Status
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type GenerateRequest struct {
|
type GenerateRequest struct {
|
||||||
@@ -43,6 +50,7 @@ type DeleteRequest struct {
|
|||||||
|
|
||||||
type PullRequest struct {
|
type PullRequest struct {
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
|
Insecure bool `json:"insecure,omitempty"`
|
||||||
Username string `json:"username"`
|
Username string `json:"username"`
|
||||||
Password string `json:"password"`
|
Password string `json:"password"`
|
||||||
}
|
}
|
||||||
@@ -56,6 +64,7 @@ type ProgressResponse struct {
|
|||||||
|
|
||||||
type PushRequest struct {
|
type PushRequest struct {
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
|
Insecure bool `json:"insecure,omitempty"`
|
||||||
Username string `json:"username"`
|
Username string `json:"username"`
|
||||||
Password string `json:"password"`
|
Password string `json:"password"`
|
||||||
}
|
}
|
||||||
|
38
cmd/cmd.go
38
cmd/cmd.go
@@ -69,7 +69,7 @@ func RunHandler(cmd *cobra.Command, args []string) error {
|
|||||||
_, err = os.Stat(fp)
|
_, err = os.Stat(fp)
|
||||||
switch {
|
switch {
|
||||||
case errors.Is(err, os.ErrNotExist):
|
case errors.Is(err, os.ErrNotExist):
|
||||||
if err := pull(args[0]); err != nil {
|
if err := pull(args[0], false); err != nil {
|
||||||
var apiStatusError api.StatusError
|
var apiStatusError api.StatusError
|
||||||
if !errors.As(err, &apiStatusError) {
|
if !errors.As(err, &apiStatusError) {
|
||||||
return err
|
return err
|
||||||
@@ -89,7 +89,12 @@ func RunHandler(cmd *cobra.Command, args []string) error {
|
|||||||
func PushHandler(cmd *cobra.Command, args []string) error {
|
func PushHandler(cmd *cobra.Command, args []string) error {
|
||||||
client := api.NewClient()
|
client := api.NewClient()
|
||||||
|
|
||||||
request := api.PushRequest{Name: args[0]}
|
insecure, err := cmd.Flags().GetBool("insecure")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
request := api.PushRequest{Name: args[0], Insecure: insecure}
|
||||||
fn := func(resp api.ProgressResponse) error {
|
fn := func(resp api.ProgressResponse) error {
|
||||||
fmt.Println(resp.Status)
|
fmt.Println(resp.Status)
|
||||||
return nil
|
return nil
|
||||||
@@ -135,28 +140,29 @@ func DeleteHandler(cmd *cobra.Command, args []string) error {
|
|||||||
client := api.NewClient()
|
client := api.NewClient()
|
||||||
|
|
||||||
request := api.DeleteRequest{Name: args[0]}
|
request := api.DeleteRequest{Name: args[0]}
|
||||||
fn := func(resp api.ProgressResponse) error {
|
if err := client.Delete(context.Background(), &request); err != nil {
|
||||||
fmt.Println(resp.Status)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := client.Delete(context.Background(), &request, fn); err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
fmt.Printf("deleted '%s'\n", args[0])
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func PullHandler(cmd *cobra.Command, args []string) error {
|
func PullHandler(cmd *cobra.Command, args []string) error {
|
||||||
return pull(args[0])
|
insecure, err := cmd.Flags().GetBool("insecure")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return pull(args[0], insecure)
|
||||||
}
|
}
|
||||||
|
|
||||||
func pull(model string) error {
|
func pull(model string, insecure bool) error {
|
||||||
client := api.NewClient()
|
client := api.NewClient()
|
||||||
|
|
||||||
var currentDigest string
|
var currentDigest string
|
||||||
var bar *progressbar.ProgressBar
|
var bar *progressbar.ProgressBar
|
||||||
|
|
||||||
request := api.PullRequest{Name: model}
|
request := api.PullRequest{Name: model, Insecure: insecure}
|
||||||
fn := func(resp api.ProgressResponse) error {
|
fn := func(resp api.ProgressResponse) error {
|
||||||
if resp.Digest != currentDigest && resp.Digest != "" {
|
if resp.Digest != currentDigest && resp.Digest != "" {
|
||||||
currentDigest = resp.Digest
|
currentDigest = resp.Digest
|
||||||
@@ -430,6 +436,8 @@ func NewCLI() *cobra.Command {
|
|||||||
RunE: PullHandler,
|
RunE: PullHandler,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pullCmd.Flags().Bool("insecure", false, "Use an insecure registry")
|
||||||
|
|
||||||
pushCmd := &cobra.Command{
|
pushCmd := &cobra.Command{
|
||||||
Use: "push MODEL",
|
Use: "push MODEL",
|
||||||
Short: "Push a model to a registry",
|
Short: "Push a model to a registry",
|
||||||
@@ -437,11 +445,13 @@ func NewCLI() *cobra.Command {
|
|||||||
RunE: PushHandler,
|
RunE: PushHandler,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pushCmd.Flags().Bool("insecure", false, "Use an insecure registry")
|
||||||
|
|
||||||
listCmd := &cobra.Command{
|
listCmd := &cobra.Command{
|
||||||
Use: "list",
|
Use: "list",
|
||||||
Aliases: []string{"ls"},
|
Aliases: []string{"ls"},
|
||||||
Short: "List models",
|
Short: "List models",
|
||||||
RunE: ListHandler,
|
RunE: ListHandler,
|
||||||
}
|
}
|
||||||
|
|
||||||
deleteCmd := &cobra.Command{
|
deleteCmd := &cobra.Command{
|
||||||
|
@@ -6,6 +6,12 @@ Install required tools:
|
|||||||
brew install go
|
brew install go
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Enable CGO:
|
||||||
|
|
||||||
|
```
|
||||||
|
export CGO_ENABLED=1
|
||||||
|
```
|
||||||
|
|
||||||
Then build ollama:
|
Then build ollama:
|
||||||
|
|
||||||
```
|
```
|
||||||
|
@@ -22,6 +22,12 @@ import (
|
|||||||
"github.com/jmorganca/ollama/parser"
|
"github.com/jmorganca/ollama/parser"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type RegistryOptions struct {
|
||||||
|
Insecure bool
|
||||||
|
Username string
|
||||||
|
Password string
|
||||||
|
}
|
||||||
|
|
||||||
type Model struct {
|
type Model struct {
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
ModelPath string
|
ModelPath string
|
||||||
@@ -45,7 +51,7 @@ func (m *Model) Prompt(request api.GenerateRequest) (string, error) {
|
|||||||
Context []int
|
Context []int
|
||||||
}
|
}
|
||||||
|
|
||||||
vars.First = len(vars.Context) == 0
|
vars.First = len(request.Context) == 0
|
||||||
vars.System = m.System
|
vars.System = m.System
|
||||||
vars.Prompt = request.Prompt
|
vars.Prompt = request.Prompt
|
||||||
vars.Context = request.Context
|
vars.Context = request.Context
|
||||||
@@ -102,8 +108,8 @@ func GetManifest(mp ModelPath) (*ManifestV2, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err = os.Stat(fp); err != nil && !errors.Is(err, os.ErrNotExist) {
|
if _, err = os.Stat(fp); err != nil {
|
||||||
return nil, fmt.Errorf("couldn't find model '%s'", mp.GetShortTagname())
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
var manifest *ManifestV2
|
var manifest *ManifestV2
|
||||||
@@ -487,12 +493,11 @@ func CreateLayer(f io.ReadSeeker) (*LayerReader, error) {
|
|||||||
return layer, nil
|
return layer, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func DeleteModel(name string, fn func(api.ProgressResponse)) error {
|
func DeleteModel(name string) error {
|
||||||
mp := ParseModelPath(name)
|
mp := ParseModelPath(name)
|
||||||
|
|
||||||
manifest, err := GetManifest(mp)
|
manifest, err := GetManifest(mp)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fn(api.ProgressResponse{Status: "couldn't retrieve manifest"})
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
deleteMap := make(map[string]bool)
|
deleteMap := make(map[string]bool)
|
||||||
@@ -503,12 +508,10 @@ func DeleteModel(name string, fn func(api.ProgressResponse)) error {
|
|||||||
|
|
||||||
fp, err := GetManifestPath()
|
fp, err := GetManifestPath()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fn(api.ProgressResponse{Status: "problem getting manifest path"})
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
err = filepath.Walk(fp, func(path string, info os.FileInfo, err error) error {
|
err = filepath.Walk(fp, func(path string, info os.FileInfo, err error) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fn(api.ProgressResponse{Status: "problem walking manifest dir"})
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if !info.IsDir() {
|
if !info.IsDir() {
|
||||||
@@ -542,9 +545,13 @@ func DeleteModel(name string, fn func(api.ProgressResponse)) error {
|
|||||||
// only delete the files which are still in the deleteMap
|
// only delete the files which are still in the deleteMap
|
||||||
for k, v := range deleteMap {
|
for k, v := range deleteMap {
|
||||||
if v {
|
if v {
|
||||||
err := os.Remove(k)
|
fp, err := GetBlobsPath(k)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("couldn't remove file '%s': %v", k, err)
|
log.Printf("couldn't get file path for '%s': %v", k, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if err := os.Remove(fp); err != nil {
|
||||||
|
log.Printf("couldn't remove file '%s': %v", fp, err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -559,12 +566,11 @@ func DeleteModel(name string, fn func(api.ProgressResponse)) error {
|
|||||||
log.Printf("couldn't remove manifest file '%s': %v", fp, err)
|
log.Printf("couldn't remove manifest file '%s': %v", fp, err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
fn(api.ProgressResponse{Status: fmt.Sprintf("deleted '%s'", name)})
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func PushModel(name, username, password string, fn func(api.ProgressResponse)) error {
|
func PushModel(name string, regOpts *RegistryOptions, fn func(api.ProgressResponse)) error {
|
||||||
mp := ParseModelPath(name)
|
mp := ParseModelPath(name)
|
||||||
|
|
||||||
fn(api.ProgressResponse{Status: "retrieving manifest"})
|
fn(api.ProgressResponse{Status: "retrieving manifest"})
|
||||||
@@ -586,7 +592,7 @@ func PushModel(name, username, password string, fn func(api.ProgressResponse)) e
|
|||||||
total += manifest.Config.Size
|
total += manifest.Config.Size
|
||||||
|
|
||||||
for _, layer := range layers {
|
for _, layer := range layers {
|
||||||
exists, err := checkBlobExistence(mp, layer.Digest, username, password)
|
exists, err := checkBlobExistence(mp, layer.Digest, regOpts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -609,13 +615,13 @@ func PushModel(name, username, password string, fn func(api.ProgressResponse)) e
|
|||||||
Completed: completed,
|
Completed: completed,
|
||||||
})
|
})
|
||||||
|
|
||||||
location, err := startUpload(mp, username, password)
|
location, err := startUpload(mp, regOpts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("couldn't start upload: %v", err)
|
log.Printf("couldn't start upload: %v", err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
err = uploadBlob(location, layer, username, password)
|
err = uploadBlob(location, layer, regOpts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("error uploading blob: %v", err)
|
log.Printf("error uploading blob: %v", err)
|
||||||
return err
|
return err
|
||||||
@@ -634,7 +640,7 @@ func PushModel(name, username, password string, fn func(api.ProgressResponse)) e
|
|||||||
Total: total,
|
Total: total,
|
||||||
Completed: completed,
|
Completed: completed,
|
||||||
})
|
})
|
||||||
url := fmt.Sprintf("%s://%s/v2/%s/manifests/%s", mp.ProtocolScheme, mp.Registry, mp.GetNamespaceRepository(), mp.Tag)
|
url := fmt.Sprintf("%s/v2/%s/manifests/%s", mp.Registry, mp.GetNamespaceRepository(), mp.Tag)
|
||||||
headers := map[string]string{
|
headers := map[string]string{
|
||||||
"Content-Type": "application/vnd.docker.distribution.manifest.v2+json",
|
"Content-Type": "application/vnd.docker.distribution.manifest.v2+json",
|
||||||
}
|
}
|
||||||
@@ -644,7 +650,7 @@ func PushModel(name, username, password string, fn func(api.ProgressResponse)) e
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
resp, err := makeRequest("PUT", url, headers, bytes.NewReader(manifestJSON), username, password)
|
resp, err := makeRequest("PUT", url, headers, bytes.NewReader(manifestJSON), regOpts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -665,12 +671,12 @@ func PushModel(name, username, password string, fn func(api.ProgressResponse)) e
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func PullModel(name, username, password string, fn func(api.ProgressResponse)) error {
|
func PullModel(name string, regOpts *RegistryOptions, fn func(api.ProgressResponse)) error {
|
||||||
mp := ParseModelPath(name)
|
mp := ParseModelPath(name)
|
||||||
|
|
||||||
fn(api.ProgressResponse{Status: "pulling manifest"})
|
fn(api.ProgressResponse{Status: "pulling manifest"})
|
||||||
|
|
||||||
manifest, err := pullModelManifest(mp, username, password)
|
manifest, err := pullModelManifest(mp, regOpts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("pull model manifest: %q", err)
|
return fmt.Errorf("pull model manifest: %q", err)
|
||||||
}
|
}
|
||||||
@@ -680,7 +686,7 @@ func PullModel(name, username, password string, fn func(api.ProgressResponse)) e
|
|||||||
layers = append(layers, &manifest.Config)
|
layers = append(layers, &manifest.Config)
|
||||||
|
|
||||||
for _, layer := range layers {
|
for _, layer := range layers {
|
||||||
if err := downloadBlob(mp, layer.Digest, username, password, fn); err != nil {
|
if err := downloadBlob(mp, layer.Digest, regOpts, fn); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -715,13 +721,13 @@ func PullModel(name, username, password string, fn func(api.ProgressResponse)) e
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func pullModelManifest(mp ModelPath, username, password string) (*ManifestV2, error) {
|
func pullModelManifest(mp ModelPath, regOpts *RegistryOptions) (*ManifestV2, error) {
|
||||||
url := fmt.Sprintf("%s://%s/v2/%s/manifests/%s", mp.ProtocolScheme, mp.Registry, mp.GetNamespaceRepository(), mp.Tag)
|
url := fmt.Sprintf("%s/v2/%s/manifests/%s", mp.Registry, mp.GetNamespaceRepository(), mp.Tag)
|
||||||
headers := map[string]string{
|
headers := map[string]string{
|
||||||
"Accept": "application/vnd.docker.distribution.manifest.v2+json",
|
"Accept": "application/vnd.docker.distribution.manifest.v2+json",
|
||||||
}
|
}
|
||||||
|
|
||||||
resp, err := makeRequest("GET", url, headers, nil, username, password)
|
resp, err := makeRequest("GET", url, headers, nil, regOpts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("couldn't get manifest: %v", err)
|
log.Printf("couldn't get manifest: %v", err)
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -782,10 +788,10 @@ func GetSHA256Digest(r io.Reader) (string, int) {
|
|||||||
return fmt.Sprintf("sha256:%x", h.Sum(nil)), int(n)
|
return fmt.Sprintf("sha256:%x", h.Sum(nil)), int(n)
|
||||||
}
|
}
|
||||||
|
|
||||||
func startUpload(mp ModelPath, username string, password string) (string, error) {
|
func startUpload(mp ModelPath, regOpts *RegistryOptions) (string, error) {
|
||||||
url := fmt.Sprintf("%s://%s/v2/%s/blobs/uploads/", mp.ProtocolScheme, mp.Registry, mp.GetNamespaceRepository())
|
url := fmt.Sprintf("%s/v2/%s/blobs/uploads/", mp.Registry, mp.GetNamespaceRepository())
|
||||||
|
|
||||||
resp, err := makeRequest("POST", url, nil, nil, username, password)
|
resp, err := makeRequest("POST", url, nil, nil, regOpts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("couldn't start upload: %v", err)
|
log.Printf("couldn't start upload: %v", err)
|
||||||
return "", err
|
return "", err
|
||||||
@@ -808,10 +814,10 @@ func startUpload(mp ModelPath, username string, password string) (string, error)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Function to check if a blob already exists in the Docker registry
|
// Function to check if a blob already exists in the Docker registry
|
||||||
func checkBlobExistence(mp ModelPath, digest string, username string, password string) (bool, error) {
|
func checkBlobExistence(mp ModelPath, digest string, regOpts *RegistryOptions) (bool, error) {
|
||||||
url := fmt.Sprintf("%s://%s/v2/%s/blobs/%s", mp.ProtocolScheme, mp.Registry, mp.GetNamespaceRepository(), digest)
|
url := fmt.Sprintf("%s/v2/%s/blobs/%s", mp.Registry, mp.GetNamespaceRepository(), digest)
|
||||||
|
|
||||||
resp, err := makeRequest("HEAD", url, nil, nil, username, password)
|
resp, err := makeRequest("HEAD", url, nil, nil, regOpts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("couldn't check for blob: %v", err)
|
log.Printf("couldn't check for blob: %v", err)
|
||||||
return false, err
|
return false, err
|
||||||
@@ -822,7 +828,7 @@ func checkBlobExistence(mp ModelPath, digest string, username string, password s
|
|||||||
return resp.StatusCode == http.StatusOK, nil
|
return resp.StatusCode == http.StatusOK, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func uploadBlob(location string, layer *Layer, username string, password string) error {
|
func uploadBlob(location string, layer *Layer, regOpts *RegistryOptions) error {
|
||||||
// Create URL
|
// Create URL
|
||||||
url := fmt.Sprintf("%s&digest=%s", location, layer.Digest)
|
url := fmt.Sprintf("%s&digest=%s", location, layer.Digest)
|
||||||
|
|
||||||
@@ -845,7 +851,7 @@ func uploadBlob(location string, layer *Layer, username string, password string)
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
resp, err := makeRequest("PUT", url, headers, f, username, password)
|
resp, err := makeRequest("PUT", url, headers, f, regOpts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("couldn't upload blob: %v", err)
|
log.Printf("couldn't upload blob: %v", err)
|
||||||
return err
|
return err
|
||||||
@@ -861,7 +867,7 @@ func uploadBlob(location string, layer *Layer, username string, password string)
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func downloadBlob(mp ModelPath, digest string, username, password string, fn func(api.ProgressResponse)) error {
|
func downloadBlob(mp ModelPath, digest string, regOpts *RegistryOptions, fn func(api.ProgressResponse)) error {
|
||||||
fp, err := GetBlobsPath(digest)
|
fp, err := GetBlobsPath(digest)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -890,12 +896,12 @@ func downloadBlob(mp ModelPath, digest string, username, password string, fn fun
|
|||||||
size = fi.Size()
|
size = fi.Size()
|
||||||
}
|
}
|
||||||
|
|
||||||
url := fmt.Sprintf("%s://%s/v2/%s/blobs/%s", mp.ProtocolScheme, mp.Registry, mp.GetNamespaceRepository(), digest)
|
url := fmt.Sprintf("%s/v2/%s/blobs/%s", mp.Registry, mp.GetNamespaceRepository(), digest)
|
||||||
headers := map[string]string{
|
headers := map[string]string{
|
||||||
"Range": fmt.Sprintf("bytes=%d-", size),
|
"Range": fmt.Sprintf("bytes=%d-", size),
|
||||||
}
|
}
|
||||||
|
|
||||||
resp, err := makeRequest("GET", url, headers, nil, username, password)
|
resp, err := makeRequest("GET", url, headers, nil, regOpts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("couldn't download blob: %v", err)
|
log.Printf("couldn't download blob: %v", err)
|
||||||
return err
|
return err
|
||||||
@@ -959,7 +965,17 @@ func downloadBlob(mp ModelPath, digest string, username, password string, fn fun
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func makeRequest(method, url string, headers map[string]string, body io.Reader, username, password string) (*http.Response, error) {
|
func makeRequest(method, url string, headers map[string]string, body io.Reader, regOpts *RegistryOptions) (*http.Response, error) {
|
||||||
|
if !strings.HasPrefix(url, "http") {
|
||||||
|
if regOpts.Insecure {
|
||||||
|
url = "http://" + url
|
||||||
|
} else {
|
||||||
|
url = "https://" + url
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("url = %s", url)
|
||||||
|
|
||||||
req, err := http.NewRequest(method, url, body)
|
req, err := http.NewRequest(method, url, body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -970,8 +986,8 @@ func makeRequest(method, url string, headers map[string]string, body io.Reader,
|
|||||||
}
|
}
|
||||||
|
|
||||||
// TODO: better auth
|
// TODO: better auth
|
||||||
if username != "" && password != "" {
|
if regOpts.Username != "" && regOpts.Password != "" {
|
||||||
req.SetBasicAuth(username, password)
|
req.SetBasicAuth(regOpts.Username, regOpts.Password)
|
||||||
}
|
}
|
||||||
|
|
||||||
client := &http.Client{
|
client := &http.Client{
|
||||||
|
@@ -45,7 +45,7 @@ func ParseModelPath(name string) ModelPath {
|
|||||||
return ModelPath{}
|
return ModelPath{}
|
||||||
}
|
}
|
||||||
|
|
||||||
colonParts := strings.Split(name, ":")
|
colonParts := strings.Split(slashParts[len(slashParts)-1], ":")
|
||||||
if len(colonParts) == 2 {
|
if len(colonParts) == 2 {
|
||||||
tag = colonParts[1]
|
tag = colonParts[1]
|
||||||
} else {
|
} else {
|
||||||
@@ -70,10 +70,13 @@ func (mp ModelPath) GetFullTagname() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (mp ModelPath) GetShortTagname() string {
|
func (mp ModelPath) GetShortTagname() string {
|
||||||
if mp.Registry == DefaultRegistry && mp.Namespace == DefaultNamespace {
|
if mp.Registry == DefaultRegistry {
|
||||||
return fmt.Sprintf("%s:%s", mp.Repository, mp.Tag)
|
if mp.Namespace == DefaultNamespace {
|
||||||
|
return fmt.Sprintf("%s:%s", mp.Repository, mp.Tag)
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("%s/%s:%s", mp.Namespace, mp.Repository, mp.Tag)
|
||||||
}
|
}
|
||||||
return fmt.Sprintf("%s/%s:%s", mp.Namespace, mp.Repository, mp.Tag)
|
return fmt.Sprintf("%s/%s/%s:%s", mp.Registry, mp.Namespace, mp.Repository, mp.Tag)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (mp ModelPath) GetManifestPath(createDir bool) (string, error) {
|
func (mp ModelPath) GetManifestPath(createDir bool) (string, error) {
|
||||||
|
@@ -2,6 +2,8 @@ package server
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"log"
|
"log"
|
||||||
"net"
|
"net"
|
||||||
@@ -92,7 +94,13 @@ func PullModelHandler(c *gin.Context) {
|
|||||||
ch <- r
|
ch <- r
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := PullModel(req.Name, req.Username, req.Password, fn); err != nil {
|
regOpts := &RegistryOptions{
|
||||||
|
Insecure: req.Insecure,
|
||||||
|
Username: req.Username,
|
||||||
|
Password: req.Password,
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := PullModel(req.Name, regOpts, fn); err != nil {
|
||||||
ch <- gin.H{"error": err.Error()}
|
ch <- gin.H{"error": err.Error()}
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
@@ -114,7 +122,13 @@ func PushModelHandler(c *gin.Context) {
|
|||||||
ch <- r
|
ch <- r
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := PushModel(req.Name, req.Username, req.Password, fn); err != nil {
|
regOpts := &RegistryOptions{
|
||||||
|
Insecure: req.Insecure,
|
||||||
|
Username: req.Username,
|
||||||
|
Password: req.Password,
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := PushModel(req.Name, regOpts, fn); err != nil {
|
||||||
ch <- gin.H{"error": err.Error()}
|
ch <- gin.H{"error": err.Error()}
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
@@ -153,20 +167,14 @@ func DeleteModelHandler(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
ch := make(chan any)
|
if err := DeleteModel(req.Name); err != nil {
|
||||||
go func() {
|
if os.IsNotExist(err) {
|
||||||
defer close(ch)
|
c.JSON(http.StatusNotFound, gin.H{"error": fmt.Sprintf("model '%s' not found", req.Name)})
|
||||||
fn := func(r api.ProgressResponse) {
|
} else {
|
||||||
ch <- r
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := DeleteModel(req.Name, fn); err != nil {
|
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||||
return
|
|
||||||
}
|
}
|
||||||
}()
|
return
|
||||||
|
}
|
||||||
streamResponse(c, ch)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func ListModelsHandler(c *gin.Context) {
|
func ListModelsHandler(c *gin.Context) {
|
||||||
@@ -178,6 +186,10 @@ func ListModelsHandler(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
err = filepath.Walk(fp, func(path string, info os.FileInfo, err error) error {
|
err = filepath.Walk(fp, func(path string, info os.FileInfo, err error) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
if errors.Is(err, os.ErrNotExist) {
|
||||||
|
log.Printf("manifest file does not exist: %s", fp)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if !info.IsDir() {
|
if !info.IsDir() {
|
||||||
|
@@ -11,18 +11,23 @@ export default async function Home() {
|
|||||||
<Image src='/ollama.png' width={64} height={64} alt='ollamaIcon' />
|
<Image src='/ollama.png' width={64} height={64} alt='ollamaIcon' />
|
||||||
<section className='my-12 text-center'>
|
<section className='my-12 text-center'>
|
||||||
<div className='flex flex-col space-y-2'>
|
<div className='flex flex-col space-y-2'>
|
||||||
<h2 className='md:max-w-[18rem] mx-auto my-2 text-3xl tracking-tight'>Portable large language models</h2>
|
<h2 className='md:max-w-md mx-auto my-2 text-3xl tracking-tight'>
|
||||||
|
Get up and running with large language models, locally.
|
||||||
|
</h2>
|
||||||
<h3 className='md:max-w-xs mx-auto text-base text-neutral-500'>
|
<h3 className='md:max-w-xs mx-auto text-base text-neutral-500'>
|
||||||
Bundle a model’s weights, configuration, prompts, data and more into self-contained packages that run anywhere.
|
Run Llama 2 and other models on macOS. Customize and create your own.
|
||||||
</h3>
|
</h3>
|
||||||
</div>
|
</div>
|
||||||
<div className='mx-auto flex flex-col space-y-4 mt-12'>
|
<div className='mx-auto max-w-xs flex flex-col space-y-4 mt-12'>
|
||||||
<Link href='/download' className='md:mx-10 lg:mx-14 bg-black text-white rounded-full px-4 py-2 focus:outline-none cursor-pointer'>
|
<Link
|
||||||
|
href='/download'
|
||||||
|
className='md:mx-10 lg:mx-14 bg-black text-white rounded-full px-4 py-2 focus:outline-none cursor-pointer'
|
||||||
|
>
|
||||||
Download
|
Download
|
||||||
</Link>
|
</Link>
|
||||||
<p className='text-neutral-500 text-sm '>
|
<p className='text-neutral-500 text-sm '>
|
||||||
Available for macOS with Apple Silicon <br />
|
Available for macOS with Apple Silicon <br />
|
||||||
Windows & Linux support coming soon.
|
Windows & Linux support coming soon.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
Reference in New Issue
Block a user