code.oscarkilo.com/klex-git

Hash:
30af7a0ae6a46aef86d84d2c0e867bc7822078e4
Author:
Igor Naverniouk <[email protected]>
Date:
Fri Sep 27 11:43:50 2024 -0700
Message:
cleaner Messages API
diff --git a/api/api.go b/api/api.go
index 7ea4495..28350fc 100644
--- a/api/api.go
+++ b/api/api.go
@@ -58,6 +58,7 @@ func (c *Client) call(method, path string, req, res interface{}) error {
}

// F executes a function on one given input.
+// TODO: Implement F() in terms of Messagse(), rather than the otehr way around.
func (c *Client) F(f, in string) (string, error) {
var res FResponse
err := c.call("POST", "/f", FRequest{FName: f, In: in}, &res)
@@ -71,7 +72,13 @@ func (c *Client) F(f, in string) (string, error) {
}

// Messages executes an LLM function using the Messages API.
-func (c *Client) Messages(f string, req *MessagesRequest) (*MessagesResponse, error) {
+// Set req.Model to one of the Klex LLM function names.
+func (c *Client) Messages(req MessagesRequest) (*MessagesResponse, error) {
+ f := req.Model
+ req.Model = ""
+ if f == "" {
+ return nil, fmt.Errorf("MessagesRequest.Model is empty")
+ }
in, err := json.Marshal(req)
if err != nil {
return nil, fmt.Errorf("Cannot marshal request: %v", err)
diff --git a/api/messages.go b/api/messages.go
index a675035..bb604ec 100644
--- a/api/messages.go
+++ b/api/messages.go
@@ -24,8 +24,11 @@ type ContentSource struct {
// Type can only be "base64".
Type string `json:"type"`

- // MediaType can be "image/jpeg", "image/png", "image/gif",
- // or "image/webp".
+ // MediaType can be one of:
+ // - "image/jpeg",
+ // - "image/png",
+ // - "image/gif", or
+ // - "image/webp".
MediaType string `json:"media_type,omitempty"`

Data string `json:"data,omitempty"`
a/api/api.go
b/api/api.go
1
package api
1
package api
2
2
3
// This file is for Golang clients of Klex.
3
// This file is for Golang clients of Klex.
4
4
5
import (
5
import (
6
"bytes"
6
"bytes"
7
"encoding/json"
7
"encoding/json"
8
"fmt"
8
"fmt"
9
"io/ioutil"
9
"io/ioutil"
10
"log"
10
"log"
11
"net/http"
11
"net/http"
12
"sort"
12
"sort"
13
)
13
)
14
14
15
type Client struct {
15
type Client struct {
16
KlexURL string
16
KlexURL string
17
APIKey string
17
APIKey string
18
}
18
}
19
19
20
func NewClient(klexURL, apiKey string) *Client {
20
func NewClient(klexURL, apiKey string) *Client {
21
if klexURL == "" || apiKey == "" {
21
if klexURL == "" || apiKey == "" {
22
log.Printf("NewClient: missing klexURL or apiKey")
22
log.Printf("NewClient: missing klexURL or apiKey")
23
return nil
23
return nil
24
}
24
}
25
return &Client{klexURL, apiKey}
25
return &Client{klexURL, apiKey}
26
}
26
}
27
27
28
func (c *Client) call(method, path string, req, res interface{}) error {
28
func (c *Client) call(method, path string, req, res interface{}) error {
29
reqBody, err := json.Marshal(req)
29
reqBody, err := json.Marshal(req)
30
if err != nil {
30
if err != nil {
31
return fmt.Errorf("Cannot marshal request: %v", err)
31
return fmt.Errorf("Cannot marshal request: %v", err)
32
}
32
}
33
reqBytes := bytes.NewBuffer(reqBody)
33
reqBytes := bytes.NewBuffer(reqBody)
34
r, err := http.NewRequest(method, c.KlexURL + path, reqBytes)
34
r, err := http.NewRequest(method, c.KlexURL + path, reqBytes)
35
if err != nil {
35
if err != nil {
36
return fmt.Errorf("In http.NewRequest: %v", err)
36
return fmt.Errorf("In http.NewRequest: %v", err)
37
}
37
}
38
r.Header.Set("Authorization", "Bearer " + c.APIKey)
38
r.Header.Set("Authorization", "Bearer " + c.APIKey)
39
r.Header.Set("Content-Type", "application/json")
39
r.Header.Set("Content-Type", "application/json")
40
resHttp, err := http.DefaultClient.Do(r)
40
resHttp, err := http.DefaultClient.Do(r)
41
if err != nil {
41
if err != nil {
42
return fmt.Errorf("http.DefaultClient.Do: %v", err)
42
return fmt.Errorf("http.DefaultClient.Do: %v", err)
43
}
43
}
44
defer resHttp.Body.Close()
44
defer resHttp.Body.Close()
45
resBody, err := ioutil.ReadAll(resHttp.Body)
45
resBody, err := ioutil.ReadAll(resHttp.Body)
46
if err != nil {
46
if err != nil {
47
return fmt.Errorf("Response error: %v", err)
47
return fmt.Errorf("Response error: %v", err)
48
}
48
}
49
if resHttp.StatusCode != 200 && resHttp.StatusCode != 204 {
49
if resHttp.StatusCode != 200 && resHttp.StatusCode != 204 {
50
return fmt.Errorf("Status %d; response=%s", resHttp.StatusCode, resBody)
50
return fmt.Errorf("Status %d; response=%s", resHttp.StatusCode, resBody)
51
}
51
}
52
if res != nil {
52
if res != nil {
53
if err := json.Unmarshal(resBody, res); err != nil {
53
if err := json.Unmarshal(resBody, res); err != nil {
54
return fmt.Errorf("Bad response %s\nerror=%v", resBody, err)
54
return fmt.Errorf("Bad response %s\nerror=%v", resBody, err)
55
}
55
}
56
}
56
}
57
return nil
57
return nil
58
}
58
}
59
59
60
// F executes a function on one given input.
60
// F executes a function on one given input.
61
// TODO: Implement F() in terms of Messagse(), rather than the otehr way around.
61
func (c *Client) F(f, in string) (string, error) {
62
func (c *Client) F(f, in string) (string, error) {
62
var res FResponse
63
var res FResponse
63
err := c.call("POST", "/f", FRequest{FName: f, In: in}, &res)
64
err := c.call("POST", "/f", FRequest{FName: f, In: in}, &res)
64
if err != nil {
65
if err != nil {
65
return "", err
66
return "", err
66
}
67
}
67
if res.Err != "" {
68
if res.Err != "" {
68
return "", fmt.Errorf(res.Err)
69
return "", fmt.Errorf(res.Err)
69
}
70
}
70
return res.Out, nil
71
return res.Out, nil
71
}
72
}
72
73
73
// Messages executes an LLM function using the Messages API.
74
// Messages executes an LLM function using the Messages API.
74
func (c *Client) Messages(f string, req *MessagesRequest) (*MessagesResponse, error) {
75
// Set req.Model to one of the Klex LLM function names.
76
func (c *Client) Messages(req MessagesRequest) (*MessagesResponse, error) {
77
f := req.Model
78
req.Model = ""
79
if f == "" {
80
return nil, fmt.Errorf("MessagesRequest.Model is empty")
81
}
75
in, err := json.Marshal(req)
82
in, err := json.Marshal(req)
76
if err != nil {
83
if err != nil {
77
return nil, fmt.Errorf("Cannot marshal request: %v", err)
84
return nil, fmt.Errorf("Cannot marshal request: %v", err)
78
}
85
}
79
out, err := c.F(f, string(in))
86
out, err := c.F(f, string(in))
80
if err != nil {
87
if err != nil {
81
return nil, err
88
return nil, err
82
}
89
}
83
var res MessagesResponse
90
var res MessagesResponse
84
err = json.Unmarshal([]byte(out), &res)
91
err = json.Unmarshal([]byte(out), &res)
85
if err != nil {
92
if err != nil {
86
// Instead of failing, treat the whole output as text, and add an error.
93
// Instead of failing, treat the whole output as text, and add an error.
87
// Let the caller figure this out.
94
// Let the caller figure this out.
88
res.Error = &ErrorResponse{
95
res.Error = &ErrorResponse{
89
Type: "response-json",
96
Type: "response-json",
90
Message: err.Error(),
97
Message: err.Error(),
91
}
98
}
92
res.Content = []ContentBlock{{Type: "text", Text: out}}
99
res.Content = []ContentBlock{{Type: "text", Text: out}}
93
}
100
}
94
return &res, nil
101
return &res, nil
95
}
102
}
96
103
97
// NewDataset creates a new dataset or updates an existing one.
104
// NewDataset creates a new dataset or updates an existing one.
98
// This is the simplest way, meant for datasets smaller than ~1GB.
105
// This is the simplest way, meant for datasets smaller than ~1GB.
99
func (c *Client) NewDataset(name string, data map[string]string) error {
106
func (c *Client) NewDataset(name string, data map[string]string) error {
100
// TODO: this loses key names; get rid of this API.
107
// TODO: this loses key names; get rid of this API.
101
req := NewDatasetRequest{Name: name, Data: nil}
108
req := NewDatasetRequest{Name: name, Data: nil}
102
keys := make([]string, 0, len(data))
109
keys := make([]string, 0, len(data))
103
for k := range data {
110
for k := range data {
104
keys = append(keys, k)
111
keys = append(keys, k)
105
}
112
}
106
sort.Strings(keys)
113
sort.Strings(keys)
107
for _, k := range keys {
114
for _, k := range keys {
108
req.Data = append(req.Data, data[k])
115
req.Data = append(req.Data, data[k])
109
}
116
}
110
117
111
var res NewDatasetResponse
118
var res NewDatasetResponse
112
err := c.call("POST", "/datasets/new", req, &res)
119
err := c.call("POST", "/datasets/new", req, &res)
113
if err != nil {
120
if err != nil {
114
return fmt.Errorf("Error POSTing to /datasets/new: %v", err)
121
return fmt.Errorf("Error POSTing to /datasets/new: %v", err)
115
}
122
}
116
if res.Name != name || res.Size != len(data) {
123
if res.Name != name || res.Size != len(data) {
117
pretty, _ := json.MarshalIndent(res, "", " ")
124
pretty, _ := json.MarshalIndent(res, "", " ")
118
return fmt.Errorf("Unexpected response from /datasets/new: %s", pretty)
125
return fmt.Errorf("Unexpected response from /datasets/new: %s", pretty)
119
}
126
}
120
return nil
127
return nil
121
}
128
}
122
129
123
// BeginNewDataset starts a new dataset upload using the v2 API.
130
// BeginNewDataset starts a new dataset upload using the v2 API.
124
// Returns the version key to use in UploadKv() and EndNewDataset().
131
// Returns the version key to use in UploadKv() and EndNewDataset().
125
// Keep the key secret until EndNewDataset() returns successfully.
132
// Keep the key secret until EndNewDataset() returns successfully.
126
func (c *Client) BeginNewDataset(name string) (string, error) {
133
func (c *Client) BeginNewDataset(name string) (string, error) {
127
req := BeginNewDatasetRequest{Name: name}
134
req := BeginNewDatasetRequest{Name: name}
128
var res BeginNewDatasetResponse
135
var res BeginNewDatasetResponse
129
err := c.call("POST", "/datasets/begin_new", req, &res)
136
err := c.call("POST", "/datasets/begin_new", req, &res)
130
if err != nil {
137
if err != nil {
131
return "", fmt.Errorf("Error POSTing to /datasets/begin_new: %v", err)
138
return "", fmt.Errorf("Error POSTing to /datasets/begin_new: %v", err)
132
}
139
}
133
return res.VersionKey, nil
140
return res.VersionKey, nil
134
}
141
}
135
142
136
// UploadKv uploads more key-value pairs of the dataset being created.
143
// UploadKv uploads more key-value pairs of the dataset being created.
137
func (c *Client) UploadKV(versionKey string, records []KV) error {
144
func (c *Client) UploadKV(versionKey string, records []KV) error {
138
req := UploadKVRequest{VersionKey: versionKey, Records: records}
145
req := UploadKVRequest{VersionKey: versionKey, Records: records}
139
err := c.call("POST", "/datasets/upload_kv", req, nil)
146
err := c.call("POST", "/datasets/upload_kv", req, nil)
140
if err != nil {
147
if err != nil {
141
return fmt.Errorf("Error POSTing to /datasets/upload_kv: %v", err)
148
return fmt.Errorf("Error POSTing to /datasets/upload_kv: %v", err)
142
}
149
}
143
return nil
150
return nil
144
}
151
}
145
152
146
// EndNewDataset commits the dataset being created.
153
// EndNewDataset commits the dataset being created.
147
func (c *Client) EndNewDataset(name, version_key string, size int) error {
154
func (c *Client) EndNewDataset(name, version_key string, size int) error {
148
req := EndNewDatasetRequest{Name: name, VersionKey: version_key, Size: size}
155
req := EndNewDatasetRequest{Name: name, VersionKey: version_key, Size: size}
149
err := c.call("POST", "/datasets/end_new", req, nil)
156
err := c.call("POST", "/datasets/end_new", req, nil)
150
if err != nil {
157
if err != nil {
151
return fmt.Errorf("Error POSTing to /datasets/end_new: %v", err)
158
return fmt.Errorf("Error POSTing to /datasets/end_new: %v", err)
152
}
159
}
153
return nil
160
return nil
154
}
161
}
a/api/messages.go
b/api/messages.go
1
package api
1
package api
2
2
3
type ChatMessage struct {
3
type ChatMessage struct {
4
Role string `json:"role"`
4
Role string `json:"role"`
5
Content []ContentBlock `json:"content"`
5
Content []ContentBlock `json:"content"`
6
}
6
}
7
7
8
type MessagesRequest struct {
8
type MessagesRequest struct {
9
Model string `json:"model"`
9
Model string `json:"model"`
10
Messages []ChatMessage `json:"messages"`
10
Messages []ChatMessage `json:"messages"`
11
MaxTokens int `json:"max_tokens"`
11
MaxTokens int `json:"max_tokens"`
12
System string `json:"system"`
12
System string `json:"system"`
13
Temperature float64 `json:"temperature"`
13
Temperature float64 `json:"temperature"`
14
}
14
}
15
15
16
type ContentBlock struct {
16
type ContentBlock struct {
17
// Type is "text", "image", "tool_use", or "tool_result".
17
// Type is "text", "image", "tool_use", or "tool_result".
18
Type string `json:"type"`
18
Type string `json:"type"`
19
Text string `json:"text"`
19
Text string `json:"text"`
20
Source *ContentSource `json:"source"`
20
Source *ContentSource `json:"source"`
21
}
21
}
22
22
23
type ContentSource struct {
23
type ContentSource struct {
24
// Type can only be "base64".
24
// Type can only be "base64".
25
Type string `json:"type"`
25
Type string `json:"type"`
26
26
27
// MediaType can be "image/jpeg", "image/png", "image/gif",
27
// MediaType can be one of:
28
// or "image/webp".
28
// - "image/jpeg",
29
// - "image/png",
30
// - "image/gif", or
31
// - "image/webp".
29
MediaType string `json:"media_type,omitempty"`
32
MediaType string `json:"media_type,omitempty"`
30
33
31
Data string `json:"data,omitempty"`
34
Data string `json:"data,omitempty"`
32
}
35
}
33
36
34
type Usage struct {
37
type Usage struct {
35
InputTokens int `json:"input_tokens"`
38
InputTokens int `json:"input_tokens"`
36
CacheCreationInputTokens *int `json:"cache_creation_input_tokens"`
39
CacheCreationInputTokens *int `json:"cache_creation_input_tokens"`
37
CacheReadInputTokens *int `json:"cache_read_input_tokens"`
40
CacheReadInputTokens *int `json:"cache_read_input_tokens"`
38
OutputTokens int `json:"output_tokens"`
41
OutputTokens int `json:"output_tokens"`
39
}
42
}
40
43
41
type ErrorResponse struct {
44
type ErrorResponse struct {
42
Type string `json:"type"`
45
Type string `json:"type"`
43
Message string `json:"message"`
46
Message string `json:"message"`
44
}
47
}
45
48
46
type MessagesResponse struct {
49
type MessagesResponse struct {
47
Id string `json:"id"`
50
Id string `json:"id"`
48
Type string `json:"type"`
51
Type string `json:"type"`
49
Role string `json:"role"`
52
Role string `json:"role"`
50
Content []ContentBlock `json:"content"`
53
Content []ContentBlock `json:"content"`
51
Model string `json:"model"`
54
Model string `json:"model"`
52
StopReason string `json:"stop_reason"`
55
StopReason string `json:"stop_reason"`
53
StopSequence bool `json:"stop_sequence"`
56
StopSequence bool `json:"stop_sequence"`
54
Usage Usage `json:"usage"`
57
Usage Usage `json:"usage"`
55
Error *ErrorResponse `json:"error"`
58
Error *ErrorResponse `json:"error"`
56
}
59
}