client.go 2.87 KB
Newer Older
Jeffrey Morgan's avatar
Jeffrey Morgan committed
1
2
3
package api

import (
Jeffrey Morgan's avatar
Jeffrey Morgan committed
4
	"bufio"
Jeffrey Morgan's avatar
Jeffrey Morgan committed
5
6
7
	"bytes"
	"context"
	"encoding/json"
8
	"fmt"
Jeffrey Morgan's avatar
Jeffrey Morgan committed
9
10
	"io"
	"net/http"
Michael Yang's avatar
Michael Yang committed
11
	"net/url"
Jeffrey Morgan's avatar
Jeffrey Morgan committed
12
13
14
)

type Client struct {
Michael Yang's avatar
Michael Yang committed
15
16
17
18
19
20
21
22
23
24
25
26
	base url.URL
}

func NewClient(hosts ...string) *Client {
	host := "127.0.0.1:11434"
	if len(hosts) > 0 {
		host = hosts[0]
	}

	return &Client{
		base: url.URL{Scheme: "http", Host: host},
	}
Jeffrey Morgan's avatar
Jeffrey Morgan committed
27
28
}

29
30
31
32
33
34
35
36
37
38
39
40
func StatusError(status int, message ...string) error {
	if status < 400 {
		return nil
	}

	if len(message) > 0 && len(message[0]) > 0 {
		return fmt.Errorf("%d %s: %s", status, http.StatusText(status), message[0])
	}

	return fmt.Errorf("%d %s", status, http.StatusText(status))
}

Michael Yang's avatar
Michael Yang committed
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
type options struct {
	requestBody  io.Reader
	responseFunc func(bts []byte) error
}

func OptionRequestBody(data any) func(*options) {
	bts, err := json.Marshal(data)
	if err != nil {
		panic(err)
	}

	return func(opts *options) {
		opts.requestBody = bytes.NewReader(bts)
	}
}

func OptionResponseFunc(fn func([]byte) error) func(*options) {
	return func(opts *options) {
		opts.responseFunc = fn
	}
}

func (c *Client) stream(ctx context.Context, method, path string, fns ...func(*options)) error {
	var opts options
	for _, fn := range fns {
		fn(&opts)
Jeffrey Morgan's avatar
Jeffrey Morgan committed
67
68
	}

Michael Yang's avatar
Michael Yang committed
69
	request, err := http.NewRequestWithContext(ctx, method, c.base.JoinPath(path).String(), opts.requestBody)
Jeffrey Morgan's avatar
Jeffrey Morgan committed
70
71
72
73
	if err != nil {
		return err
	}

Michael Yang's avatar
Michael Yang committed
74
75
	request.Header.Set("Content-Type", "application/json")
	request.Header.Set("Accept", "application/json")
Jeffrey Morgan's avatar
Jeffrey Morgan committed
76

Michael Yang's avatar
Michael Yang committed
77
	response, err := http.DefaultClient.Do(request)
Jeffrey Morgan's avatar
Jeffrey Morgan committed
78
79
80
	if err != nil {
		return err
	}
Michael Yang's avatar
Michael Yang committed
81
	defer response.Body.Close()
Jeffrey Morgan's avatar
Jeffrey Morgan committed
82

Michael Yang's avatar
Michael Yang committed
83
84
85
	if opts.responseFunc != nil {
		scanner := bufio.NewScanner(response.Body)
		for scanner.Scan() {
86
87
88
89
90
91
92
93
94
95
96
97
98
99
			var errorResponse struct {
				Error string `json:"error"`
			}

			bts := scanner.Bytes()
			if err := json.Unmarshal(bts, &errorResponse); err != nil {
				return err
			}

			if err := StatusError(response.StatusCode, errorResponse.Error); err != nil {
				return err
			}

			if err := opts.responseFunc(bts); err != nil {
Michael Yang's avatar
Michael Yang committed
100
101
				return err
			}
Jeffrey Morgan's avatar
Jeffrey Morgan committed
102
103
104
		}
	}

Michael Yang's avatar
Michael Yang committed
105
106
	return nil
}
Jeffrey Morgan's avatar
Jeffrey Morgan committed
107

Michael Yang's avatar
Michael Yang committed
108
type GenerateResponseFunc func(GenerateResponse) error
Jeffrey Morgan's avatar
Jeffrey Morgan committed
109

Michael Yang's avatar
Michael Yang committed
110
func (c *Client) Generate(ctx context.Context, req *GenerateRequest, fn GenerateResponseFunc) error {
Michael Yang's avatar
Michael Yang committed
111
112
113
114
115
116
117
118
119
120
121
	return c.stream(ctx, http.MethodPost, "/api/generate",
		OptionRequestBody(req),
		OptionResponseFunc(func(bts []byte) error {
			var resp GenerateResponse
			if err := json.Unmarshal(bts, &resp); err != nil {
				return err
			}

			return fn(resp)
		}),
	)
Jeffrey Morgan's avatar
Jeffrey Morgan committed
122
}
Bruce MacDonald's avatar
Bruce MacDonald committed
123

Michael Yang's avatar
Michael Yang committed
124
125
126
type PullProgressFunc func(PullProgress) error

func (c *Client) Pull(ctx context.Context, req *PullRequest, fn PullProgressFunc) error {
Michael Yang's avatar
Michael Yang committed
127
128
129
130
131
132
133
134
	return c.stream(ctx, http.MethodPost, "/api/pull",
		OptionRequestBody(req),
		OptionResponseFunc(func(bts []byte) error {
			var resp PullProgress
			if err := json.Unmarshal(bts, &resp); err != nil {
				return err
			}

Bruce MacDonald's avatar
Bruce MacDonald committed
135
136
137
138
139
			if resp.Error.Message != "" {
				// couldn't pull the model from the directory, proceed anyway
				return nil
			}

Michael Yang's avatar
Michael Yang committed
140
141
142
			return fn(resp)
		}),
	)
Bruce MacDonald's avatar
Bruce MacDonald committed
143
}