code.oscarkilo.com/klex-git

Hash:
9b10563eb028d2752a9628f3726a9ba415b5e545
Author:
Igor Naverniouk <[email protected]>
Date:
Tue May 13 13:47:14 2025 -0700
Message:
embedding API
diff --git a/api/api.go b/api/api.go
index 28350fc..3a64e9a 100644
--- a/api/api.go
+++ b/api/api.go
@@ -58,7 +58,6 @@ 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)
diff --git a/embed/main.go b/embed/main.go
new file mode 100644
index 0000000..b2078ae
--- /dev/null
+++ b/embed/main.go
@@ -0,0 +1,52 @@
+package main
+
+// This binary converts text into embedding vecors.
+
+import "encoding/json"
+import "flag"
+import "fmt"
+import "io/ioutil"
+import "log"
+import "os"
+
+import "oscarkilo.com/klex-git/api"
+import "oscarkilo.com/klex-git/config"
+
+var model = flag.String("model", "openai:text-embedding-3-small", "")
+
+func main() {
+ flag.Parse()
+
+ // Find the API keys and configure a Klex client.
+ config, err := config.ReadConfig()
+ if err != nil {
+ log.Fatalf("Failed to read config: %v", err)
+ }
+ client := api.NewClient(config.KlexUrl, config.ApiKey)
+ if client == nil {
+ log.Fatalf("Failed to create Klex client")
+ }
+
+ // Read stdin as text.
+ sin, err := ioutil.ReadAll(os.Stdin)
+ if err != nil {
+ log.Fatalf("Failed to read stdin: %v", err)
+ }
+
+ json_vector, err := client.F("embed-" + *model, string(sin))
+ if err != nil {
+ log.Fatalf("Failed to call F: %v", err)
+ }
+ var vector []float32
+ err = json.Unmarshal([]byte(json_vector), &vector)
+ if err != nil {
+ log.Fatalf("Failed to parse vector: %v", err)
+ }
+
+ for i, w := range vector {
+ fmt.Printf(" %6g", w)
+ if i % 10 == 9 || i == len(vector) - 1 {
+ fmt.Println()
+ }
+ }
+}
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.
62
func (c *Client) F(f, in string) (string, error) {
61
func (c *Client) F(f, in string) (string, error) {
63
var res FResponse
62
var res FResponse
64
err := c.call("POST", "/f", FRequest{FName: f, In: in}, &res)
63
err := c.call("POST", "/f", FRequest{FName: f, In: in}, &res)
65
if err != nil {
64
if err != nil {
66
return "", err
65
return "", err
67
}
66
}
68
if res.Err != "" {
67
if res.Err != "" {
69
return "", fmt.Errorf(res.Err)
68
return "", fmt.Errorf(res.Err)
70
}
69
}
71
return res.Out, nil
70
return res.Out, nil
72
}
71
}
73
72
74
// Messages executes an LLM function using the Messages API.
73
// Messages executes an LLM function using the Messages API.
75
// Set req.Model to one of the Klex LLM function names.
74
// Set req.Model to one of the Klex LLM function names.
76
func (c *Client) Messages(req MessagesRequest) (*MessagesResponse, error) {
75
func (c *Client) Messages(req MessagesRequest) (*MessagesResponse, error) {
77
f := req.Model
76
f := req.Model
78
req.Model = ""
77
req.Model = ""
79
if f == "" {
78
if f == "" {
80
return nil, fmt.Errorf("MessagesRequest.Model is empty")
79
return nil, fmt.Errorf("MessagesRequest.Model is empty")
81
}
80
}
82
in, err := json.Marshal(req)
81
in, err := json.Marshal(req)
83
if err != nil {
82
if err != nil {
84
return nil, fmt.Errorf("Cannot marshal request: %v", err)
83
return nil, fmt.Errorf("Cannot marshal request: %v", err)
85
}
84
}
86
out, err := c.F(f, string(in))
85
out, err := c.F(f, string(in))
87
if err != nil {
86
if err != nil {
88
return nil, err
87
return nil, err
89
}
88
}
90
var res MessagesResponse
89
var res MessagesResponse
91
err = json.Unmarshal([]byte(out), &res)
90
err = json.Unmarshal([]byte(out), &res)
92
if err != nil {
91
if err != nil {
93
// Instead of failing, treat the whole output as text, and add an error.
92
// Instead of failing, treat the whole output as text, and add an error.
94
// Let the caller figure this out.
93
// Let the caller figure this out.
95
res.Error = &ErrorResponse{
94
res.Error = &ErrorResponse{
96
Type: "response-json",
95
Type: "response-json",
97
Message: err.Error(),
96
Message: err.Error(),
98
}
97
}
99
res.Content = []ContentBlock{{Type: "text", Text: out}}
98
res.Content = []ContentBlock{{Type: "text", Text: out}}
100
}
99
}
101
return &res, nil
100
return &res, nil
102
}
101
}
103
102
104
// NewDataset creates a new dataset or updates an existing one.
103
// NewDataset creates a new dataset or updates an existing one.
105
// This is the simplest way, meant for datasets smaller than ~1GB.
104
// This is the simplest way, meant for datasets smaller than ~1GB.
106
func (c *Client) NewDataset(name string, data map[string]string) error {
105
func (c *Client) NewDataset(name string, data map[string]string) error {
107
// TODO: this loses key names; get rid of this API.
106
// TODO: this loses key names; get rid of this API.
108
req := NewDatasetRequest{Name: name, Data: nil}
107
req := NewDatasetRequest{Name: name, Data: nil}
109
keys := make([]string, 0, len(data))
108
keys := make([]string, 0, len(data))
110
for k := range data {
109
for k := range data {
111
keys = append(keys, k)
110
keys = append(keys, k)
112
}
111
}
113
sort.Strings(keys)
112
sort.Strings(keys)
114
for _, k := range keys {
113
for _, k := range keys {
115
req.Data = append(req.Data, data[k])
114
req.Data = append(req.Data, data[k])
116
}
115
}
117
116
118
var res NewDatasetResponse
117
var res NewDatasetResponse
119
err := c.call("POST", "/datasets/new", req, &res)
118
err := c.call("POST", "/datasets/new", req, &res)
120
if err != nil {
119
if err != nil {
121
return fmt.Errorf("Error POSTing to /datasets/new: %v", err)
120
return fmt.Errorf("Error POSTing to /datasets/new: %v", err)
122
}
121
}
123
if res.Name != name || res.Size != len(data) {
122
if res.Name != name || res.Size != len(data) {
124
pretty, _ := json.MarshalIndent(res, "", " ")
123
pretty, _ := json.MarshalIndent(res, "", " ")
125
return fmt.Errorf("Unexpected response from /datasets/new: %s", pretty)
124
return fmt.Errorf("Unexpected response from /datasets/new: %s", pretty)
126
}
125
}
127
return nil
126
return nil
128
}
127
}
129
128
130
// BeginNewDataset starts a new dataset upload using the v2 API.
129
// BeginNewDataset starts a new dataset upload using the v2 API.
131
// Returns the version key to use in UploadKv() and EndNewDataset().
130
// Returns the version key to use in UploadKv() and EndNewDataset().
132
// Keep the key secret until EndNewDataset() returns successfully.
131
// Keep the key secret until EndNewDataset() returns successfully.
133
func (c *Client) BeginNewDataset(name string) (string, error) {
132
func (c *Client) BeginNewDataset(name string) (string, error) {
134
req := BeginNewDatasetRequest{Name: name}
133
req := BeginNewDatasetRequest{Name: name}
135
var res BeginNewDatasetResponse
134
var res BeginNewDatasetResponse
136
err := c.call("POST", "/datasets/begin_new", req, &res)
135
err := c.call("POST", "/datasets/begin_new", req, &res)
137
if err != nil {
136
if err != nil {
138
return "", fmt.Errorf("Error POSTing to /datasets/begin_new: %v", err)
137
return "", fmt.Errorf("Error POSTing to /datasets/begin_new: %v", err)
139
}
138
}
140
return res.VersionKey, nil
139
return res.VersionKey, nil
141
}
140
}
142
141
143
// UploadKv uploads more key-value pairs of the dataset being created.
142
// UploadKv uploads more key-value pairs of the dataset being created.
144
func (c *Client) UploadKV(versionKey string, records []KV) error {
143
func (c *Client) UploadKV(versionKey string, records []KV) error {
145
req := UploadKVRequest{VersionKey: versionKey, Records: records}
144
req := UploadKVRequest{VersionKey: versionKey, Records: records}
146
err := c.call("POST", "/datasets/upload_kv", req, nil)
145
err := c.call("POST", "/datasets/upload_kv", req, nil)
147
if err != nil {
146
if err != nil {
148
return fmt.Errorf("Error POSTing to /datasets/upload_kv: %v", err)
147
return fmt.Errorf("Error POSTing to /datasets/upload_kv: %v", err)
149
}
148
}
150
return nil
149
return nil
151
}
150
}
152
151
153
// EndNewDataset commits the dataset being created.
152
// EndNewDataset commits the dataset being created.
154
func (c *Client) EndNewDataset(name, version_key string, size int) error {
153
func (c *Client) EndNewDataset(name, version_key string, size int) error {
155
req := EndNewDatasetRequest{Name: name, VersionKey: version_key, Size: size}
154
req := EndNewDatasetRequest{Name: name, VersionKey: version_key, Size: size}
156
err := c.call("POST", "/datasets/end_new", req, nil)
155
err := c.call("POST", "/datasets/end_new", req, nil)
157
if err != nil {
156
if err != nil {
158
return fmt.Errorf("Error POSTing to /datasets/end_new: %v", err)
157
return fmt.Errorf("Error POSTing to /datasets/end_new: %v", err)
159
}
158
}
160
return nil
159
return nil
161
}
160
}
/dev/null
b/embed/main.go
1
package main
2
3
// This binary converts text into embedding vecors.
4
5
import "encoding/json"
6
import "flag"
7
import "fmt"
8
import "io/ioutil"
9
import "log"
10
import "os"
11
12
import "oscarkilo.com/klex-git/api"
13
import "oscarkilo.com/klex-git/config"
14
15
var model = flag.String("model", "openai:text-embedding-3-small", "")
16
17
func main() {
18
flag.Parse()
19
20
// Find the API keys and configure a Klex client.
21
config, err := config.ReadConfig()
22
if err != nil {
23
log.Fatalf("Failed to read config: %v", err)
24
}
25
client := api.NewClient(config.KlexUrl, config.ApiKey)
26
if client == nil {
27
log.Fatalf("Failed to create Klex client")
28
}
29
30
// Read stdin as text.
31
sin, err := ioutil.ReadAll(os.Stdin)
32
if err != nil {
33
log.Fatalf("Failed to read stdin: %v", err)
34
}
35
36
json_vector, err := client.F("embed-" + *model, string(sin))
37
if err != nil {
38
log.Fatalf("Failed to call F: %v", err)
39
}
40
var vector []float32
41
err = json.Unmarshal([]byte(json_vector), &vector)
42
if err != nil {
43
log.Fatalf("Failed to parse vector: %v", err)
44
}
45
46
for i, w := range vector {
47
fmt.Printf(" %6g", w)
48
if i % 10 == 9 || i == len(vector) - 1 {
49
fmt.Println()
50
}
51
}
52
}