code.oscarkilo.com/klex-git

Hash:
371fc7e884146e145b3f41627b4135d91a662d96
Author:
Igor Naverniouk <[email protected]>
Date:
Sun May 18 20:14:27 2025 -0700
Message:
simplification
diff --git a/api/api.go b/api/api.go
index 6e2156f..2aa5c17 100644
--- a/api/api.go
+++ b/api/api.go
@@ -105,12 +105,8 @@ func (c *Client) Messages(req MessagesRequest) (*MessagesResponse, error) {

// Embed returns semantic embedding vectors for the given text.
func (c *Client) Embed(req EmbedRequest) ([][]float32, error) {
- url := fmt.Sprintf(
- "/embed/do?model=%s&dims=%d&path=%t",
- req.Model, req.Dims, req.FullPath,
- )
var resp [][]float32
- err := c.call("POST", url, req.Text, &resp)
+ err := c.call("POST", "/embed/do", req, &resp)
return resp, err
}

diff --git a/api/embed.go b/api/embed.go
index 19e4042..eb1615d 100644
--- a/api/embed.go
+++ b/api/embed.go
@@ -14,6 +14,6 @@ type EmbedRequest struct {
Dims int `json:"dims"`

// FullPath returns a sequence of vectors, one per prefix of Text.
- // Instead of the usual array of numbers, you'll get a 2D array.
+ // Because of this possibility, /embed/do always returns a 2D array.
FullPath bool `json:"full_path"`
}
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
if b, e := json.MarshalIndent(req, "", " "); e == nil { // DEBUG
29
if b, e := json.MarshalIndent(req, "", " "); e == nil { // DEBUG
30
log.Printf("Request %s %s: %s", method, path, string(b)) // DEBUG
30
log.Printf("Request %s %s: %s", method, path, string(b)) // DEBUG
31
} // DEBUG
31
} // DEBUG
32
reqBody, err := json.Marshal(req)
32
reqBody, err := json.Marshal(req)
33
if err != nil {
33
if err != nil {
34
return fmt.Errorf("Cannot marshal request: %v", err)
34
return fmt.Errorf("Cannot marshal request: %v", err)
35
}
35
}
36
reqBytes := bytes.NewBuffer(reqBody)
36
reqBytes := bytes.NewBuffer(reqBody)
37
r, err := http.NewRequest(method, c.KlexURL + path, reqBytes)
37
r, err := http.NewRequest(method, c.KlexURL + path, reqBytes)
38
if err != nil {
38
if err != nil {
39
return fmt.Errorf("In http.NewRequest: %v", err)
39
return fmt.Errorf("In http.NewRequest: %v", err)
40
}
40
}
41
r.Header.Set("Authorization", "Bearer " + c.APIKey)
41
r.Header.Set("Authorization", "Bearer " + c.APIKey)
42
r.Header.Set("Content-Type", "application/json")
42
r.Header.Set("Content-Type", "application/json")
43
resHttp, err := http.DefaultClient.Do(r)
43
resHttp, err := http.DefaultClient.Do(r)
44
if err != nil {
44
if err != nil {
45
return fmt.Errorf("http.DefaultClient.Do: %v", err)
45
return fmt.Errorf("http.DefaultClient.Do: %v", err)
46
}
46
}
47
defer resHttp.Body.Close()
47
defer resHttp.Body.Close()
48
resBody, err := ioutil.ReadAll(resHttp.Body)
48
resBody, err := ioutil.ReadAll(resHttp.Body)
49
if err != nil {
49
if err != nil {
50
return fmt.Errorf("Response error: %v", err)
50
return fmt.Errorf("Response error: %v", err)
51
}
51
}
52
if resHttp.StatusCode != 200 && resHttp.StatusCode != 204 {
52
if resHttp.StatusCode != 200 && resHttp.StatusCode != 204 {
53
return fmt.Errorf("Status %d; response=%s", resHttp.StatusCode, resBody)
53
return fmt.Errorf("Status %d; response=%s", resHttp.StatusCode, resBody)
54
}
54
}
55
if res != nil {
55
if res != nil {
56
if err := json.Unmarshal(resBody, res); err != nil {
56
if err := json.Unmarshal(resBody, res); err != nil {
57
return fmt.Errorf("Bad response %s\nerror=%v", resBody, err)
57
return fmt.Errorf("Bad response %s\nerror=%v", resBody, err)
58
}
58
}
59
}
59
}
60
return nil
60
return nil
61
}
61
}
62
62
63
// F executes a function on one given input.
63
// F executes a function on one given input.
64
func (c *Client) F(f, in string) (string, error) {
64
func (c *Client) F(f, in string) (string, error) {
65
var res FResponse
65
var res FResponse
66
err := c.call("POST", "/f", FRequest{FName: f, In: in}, &res)
66
err := c.call("POST", "/f", FRequest{FName: f, In: in}, &res)
67
if err != nil {
67
if err != nil {
68
return "", err
68
return "", err
69
}
69
}
70
if res.Err != "" {
70
if res.Err != "" {
71
return "", fmt.Errorf(res.Err)
71
return "", fmt.Errorf(res.Err)
72
}
72
}
73
return res.Out, nil
73
return res.Out, nil
74
}
74
}
75
75
76
// Messages executes an LLM function using the Messages API.
76
// Messages executes an LLM function using the Messages API.
77
// Set req.Model to one of the Klex LLM function names.
77
// Set req.Model to one of the Klex LLM function names.
78
func (c *Client) Messages(req MessagesRequest) (*MessagesResponse, error) {
78
func (c *Client) Messages(req MessagesRequest) (*MessagesResponse, error) {
79
f := req.Model
79
f := req.Model
80
req.Model = ""
80
req.Model = ""
81
if f == "" {
81
if f == "" {
82
return nil, fmt.Errorf("MessagesRequest.Model is empty")
82
return nil, fmt.Errorf("MessagesRequest.Model is empty")
83
}
83
}
84
in, err := json.Marshal(req)
84
in, err := json.Marshal(req)
85
if err != nil {
85
if err != nil {
86
return nil, fmt.Errorf("Cannot marshal request: %v", err)
86
return nil, fmt.Errorf("Cannot marshal request: %v", err)
87
}
87
}
88
out, err := c.F(f, string(in))
88
out, err := c.F(f, string(in))
89
if err != nil {
89
if err != nil {
90
return nil, err
90
return nil, err
91
}
91
}
92
var res MessagesResponse
92
var res MessagesResponse
93
err = json.Unmarshal([]byte(out), &res)
93
err = json.Unmarshal([]byte(out), &res)
94
if err != nil {
94
if err != nil {
95
// Instead of failing, treat the whole output as text, and add an error.
95
// Instead of failing, treat the whole output as text, and add an error.
96
// Let the caller figure this out.
96
// Let the caller figure this out.
97
res.Error = &ErrorResponse{
97
res.Error = &ErrorResponse{
98
Type: "response-json",
98
Type: "response-json",
99
Message: err.Error(),
99
Message: err.Error(),
100
}
100
}
101
res.Content = []ContentBlock{{Type: "text", Text: out}}
101
res.Content = []ContentBlock{{Type: "text", Text: out}}
102
}
102
}
103
return &res, nil
103
return &res, nil
104
}
104
}
105
105
106
// Embed returns semantic embedding vectors for the given text.
106
// Embed returns semantic embedding vectors for the given text.
107
func (c *Client) Embed(req EmbedRequest) ([][]float32, error) {
107
func (c *Client) Embed(req EmbedRequest) ([][]float32, error) {
108
url := fmt.Sprintf(
109
"/embed/do?model=%s&dims=%d&path=%t",
110
req.Model, req.Dims, req.FullPath,
111
)
112
var resp [][]float32
108
var resp [][]float32
113
err := c.call("POST", url, req.Text, &resp)
109
err := c.call("POST", "/embed/do", req, &resp)
114
return resp, err
110
return resp, err
115
}
111
}
116
112
117
// NewDataset creates a new dataset or updates an existing one.
113
// NewDataset creates a new dataset or updates an existing one.
118
// This is the simplest way, meant for datasets smaller than ~1GB.
114
// This is the simplest way, meant for datasets smaller than ~1GB.
119
func (c *Client) NewDataset(name string, data map[string]string) error {
115
func (c *Client) NewDataset(name string, data map[string]string) error {
120
// TODO: this loses key names; get rid of this API.
116
// TODO: this loses key names; get rid of this API.
121
req := NewDatasetRequest{Name: name, Data: nil}
117
req := NewDatasetRequest{Name: name, Data: nil}
122
keys := make([]string, 0, len(data))
118
keys := make([]string, 0, len(data))
123
for k := range data {
119
for k := range data {
124
keys = append(keys, k)
120
keys = append(keys, k)
125
}
121
}
126
sort.Strings(keys)
122
sort.Strings(keys)
127
for _, k := range keys {
123
for _, k := range keys {
128
req.Data = append(req.Data, data[k])
124
req.Data = append(req.Data, data[k])
129
}
125
}
130
126
131
var res NewDatasetResponse
127
var res NewDatasetResponse
132
err := c.call("POST", "/datasets/new", req, &res)
128
err := c.call("POST", "/datasets/new", req, &res)
133
if err != nil {
129
if err != nil {
134
return fmt.Errorf("Error POSTing to /datasets/new: %v", err)
130
return fmt.Errorf("Error POSTing to /datasets/new: %v", err)
135
}
131
}
136
if res.Name != name || res.Size != len(data) {
132
if res.Name != name || res.Size != len(data) {
137
pretty, _ := json.MarshalIndent(res, "", " ")
133
pretty, _ := json.MarshalIndent(res, "", " ")
138
return fmt.Errorf("Unexpected response from /datasets/new: %s", pretty)
134
return fmt.Errorf("Unexpected response from /datasets/new: %s", pretty)
139
}
135
}
140
return nil
136
return nil
141
}
137
}
142
138
143
// BeginNewDataset starts a new dataset upload using the v2 API.
139
// BeginNewDataset starts a new dataset upload using the v2 API.
144
// Returns the version key to use in UploadKv() and EndNewDataset().
140
// Returns the version key to use in UploadKv() and EndNewDataset().
145
// Keep the key secret until EndNewDataset() returns successfully.
141
// Keep the key secret until EndNewDataset() returns successfully.
146
func (c *Client) BeginNewDataset(name string) (string, error) {
142
func (c *Client) BeginNewDataset(name string) (string, error) {
147
req := BeginNewDatasetRequest{Name: name}
143
req := BeginNewDatasetRequest{Name: name}
148
var res BeginNewDatasetResponse
144
var res BeginNewDatasetResponse
149
err := c.call("POST", "/datasets/begin_new", req, &res)
145
err := c.call("POST", "/datasets/begin_new", req, &res)
150
if err != nil {
146
if err != nil {
151
return "", fmt.Errorf("Error POSTing to /datasets/begin_new: %v", err)
147
return "", fmt.Errorf("Error POSTing to /datasets/begin_new: %v", err)
152
}
148
}
153
return res.VersionKey, nil
149
return res.VersionKey, nil
154
}
150
}
155
151
156
// UploadKv uploads more key-value pairs of the dataset being created.
152
// UploadKv uploads more key-value pairs of the dataset being created.
157
func (c *Client) UploadKV(versionKey string, records []KV) error {
153
func (c *Client) UploadKV(versionKey string, records []KV) error {
158
req := UploadKVRequest{VersionKey: versionKey, Records: records}
154
req := UploadKVRequest{VersionKey: versionKey, Records: records}
159
err := c.call("POST", "/datasets/upload_kv", req, nil)
155
err := c.call("POST", "/datasets/upload_kv", req, nil)
160
if err != nil {
156
if err != nil {
161
return fmt.Errorf("Error POSTing to /datasets/upload_kv: %v", err)
157
return fmt.Errorf("Error POSTing to /datasets/upload_kv: %v", err)
162
}
158
}
163
return nil
159
return nil
164
}
160
}
165
161
166
// EndNewDataset commits the dataset being created.
162
// EndNewDataset commits the dataset being created.
167
func (c *Client) EndNewDataset(name, version_key string, size int) error {
163
func (c *Client) EndNewDataset(name, version_key string, size int) error {
168
req := EndNewDatasetRequest{Name: name, VersionKey: version_key, Size: size}
164
req := EndNewDatasetRequest{Name: name, VersionKey: version_key, Size: size}
169
err := c.call("POST", "/datasets/end_new", req, nil)
165
err := c.call("POST", "/datasets/end_new", req, nil)
170
if err != nil {
166
if err != nil {
171
return fmt.Errorf("Error POSTing to /datasets/end_new: %v", err)
167
return fmt.Errorf("Error POSTing to /datasets/end_new: %v", err)
172
}
168
}
173
return nil
169
return nil
174
}
170
}
a/api/embed.go
b/api/embed.go
1
package api
1
package api
2
2
3
type EmbedRequest struct {
3
type EmbedRequest struct {
4
// Text should be shorter than ~8000 tokens.
4
// Text should be shorter than ~8000 tokens.
5
Text string `json:"text"`
5
Text string `json:"text"`
6
6
7
// Model is an embedding model name.
7
// Model is an embedding model name.
8
// These are hard-coded in //funky/builtins.
8
// These are hard-coded in //funky/builtins.
9
// A good choice is "openai:text-embedding-3-small".
9
// A good choice is "openai:text-embedding-3-small".
10
Model string `json:"model"`
10
Model string `json:"model"`
11
11
12
// Dims is the number of vector dimensions to return.
12
// Dims is the number of vector dimensions to return.
13
// A good chioce is 1536 for openai:text-embedding-3-small.
13
// A good chioce is 1536 for openai:text-embedding-3-small.
14
Dims int `json:"dims"`
14
Dims int `json:"dims"`
15
15
16
// FullPath returns a sequence of vectors, one per prefix of Text.
16
// FullPath returns a sequence of vectors, one per prefix of Text.
17
// Instead of the usual array of numbers, you'll get a 2D array.
17
// Because of this possibility, /embed/do always returns a 2D array.
18
FullPath bool `json:"full_path"`
18
FullPath bool `json:"full_path"`
19
}
19
}