code.oscarkilo.com/klex-git

Hash:
f670df8b1bc4575bec4b8b47176a773ce68f4960
Author:
Igor Naverniouk <[email protected]>
Date:
Sun May 18 20:17:32 2025 -0700
Message:
cleanup
diff --git a/api/api.go b/api/api.go
index 2aa5c17..f3f6ff8 100644
--- a/api/api.go
+++ b/api/api.go
@@ -26,9 +26,6 @@ func NewClient(klexURL, apiKey string) *Client {
}

func (c *Client) call(method, path string, req, res interface{}) error {
- if b, e := json.MarshalIndent(req, "", " "); e == nil { // DEBUG
- log.Printf("Request %s %s: %s", method, path, string(b)) // DEBUG
- } // DEBUG
reqBody, err := json.Marshal(req)
if err != nil {
return fmt.Errorf("Cannot marshal request: %v", err)
diff --git a/embed/main.go b/embed/main.go
index df04fad..6a99b02 100644
--- a/embed/main.go
+++ b/embed/main.go
@@ -2,17 +2,14 @@ package main

// This binary converts text into embedding vecors.

-//import "encoding/json"
import "flag"
import "fmt"
import "io/ioutil"
import "log"
import "os"
-//import "sync"

import "oscarkilo.com/klex-git/api"
import "oscarkilo.com/klex-git/config"
-//import "oscarkilo.com/klex-git/util"

var model = flag.String("model", "openai:text-embedding-3-small", "")
var dims = flag.Int("dims", 1536, "Number of vector dimensions to return")
@@ -36,7 +33,6 @@ func main() {
if err != nil {
log.Fatalf("Failed to read stdin: %v", err)
}
- //text := []string{string(sin)}

vectors, err := client.Embed(api.EmbedRequest{
Text: string(sin),
@@ -48,31 +44,6 @@ func main() {
log.Fatalf("Failed to call Embed: %v", err)
}

- /*
- if *full_path {
- text = util.SplitByWord(text[0])
- }
-
- f_name := fmt.Sprintf("embed-%s@%d", *model, *dims)
- vectors := make([][]float32, len(text))
- wg := sync.WaitGroup{}
- for i := range text {
- wg.Add(1)
- go func(i int) {
- json_vector, err := client.F(f_name, text[i])
- if err != nil {
- log.Fatalf("Failed to call F: %v", err)
- }
- err = json.Unmarshal([]byte(json_vector), &vectors[i])
- if err != nil {
- log.Fatalf("Failed to parse vector: %v", err)
- }
- wg.Done()
- }(i)
- }
- wg.Wait()
- */
-
for _, vector := range vectors {
for i, w := range vector {
if i > 0 {
diff --git a/util/prompts.go b/util/prompts.go
deleted file mode 100644
index 5611247..0000000
--- a/util/prompts.go
+++ /dev/null
@@ -1,30 +0,0 @@
-package util
-
-import "unicode"
-
-// SplitByWord returns all whitespace-terminated prefixes of 'text'.
-// The first entry will be the first word and its trailing whitespace.
-// The last entry will be the whole 'text'.
-func SplitByWord(text string) []string {
- var prefixes []string
- runes := []rune(text)
-
- const ALL_WHITESPACE = 0
- const SAW_CHARS = 1
- const SAW_WHITESPACE_AFTER_CHARS = 2
- state := ALL_WHITESPACE
-
- for i, c := range runes {
- if unicode.IsSpace(c) {
- if state == SAW_CHARS {
- state = SAW_WHITESPACE_AFTER_CHARS
- }
- } else {
- if state == SAW_WHITESPACE_AFTER_CHARS {
- prefixes = append(prefixes, string(runes[:i]))
- }
- state = SAW_CHARS
- }
- }
- return append(prefixes, text)
-}
diff --git a/util/prompts_test.go b/util/prompts_test.go
deleted file mode 100644
index 35a6022..0000000
--- a/util/prompts_test.go
+++ /dev/null
@@ -1,55 +0,0 @@
-package util
-
-import "encoding/json"
-import "testing"
-
-func same(a, b []string) bool {
- if len(a) != len(b) {
- return false
- }
- for i := range a {
- if a[i] != b[i] {
- return false
- }
- }
- return true
-}
-
-func TestSplitByWord(t *testing.T) {
- // check verifies that golden == SplitByWord(golden[-1]).
- check := func(golden ...string) {
- t.Helper()
- in := golden[len(golden)-1]
- out := SplitByWord(in)
- if !same(golden, out) {
- ijson, _ := json.Marshal(in)
- gjson, _ := json.MarshalIndent(golden, "", " ")
- ojson, _ := json.MarshalIndent(out, "", " ")
- t.Errorf("SplitByWord(%s):\nwant: %s\nhave: %s", ijson, gjson, ojson)
- }
- }
-
- check("")
- check(" ")
- check(" \n\t \n")
- check(
- "hello ",
- "hello world",
- )
- check(
- "Once ",
- "Once upon\t",
- "Once upon\ta\n ",
- "Once upon\ta\n time, ",
- )
- check(
- "Snap, ",
- "Snap, crackle, ",
- "Snap, crackle, and ",
- "Snap, crackle, and pop.",
- )
- check(
- " leading ",
- " leading whitespace",
- )
-}
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
30
log.Printf("Request %s %s: %s", method, path, string(b)) // DEBUG
31
} // DEBUG
32
reqBody, err := json.Marshal(req)
29
reqBody, err := json.Marshal(req)
33
if err != nil {
30
if err != nil {
34
return fmt.Errorf("Cannot marshal request: %v", err)
31
return fmt.Errorf("Cannot marshal request: %v", err)
35
}
32
}
36
reqBytes := bytes.NewBuffer(reqBody)
33
reqBytes := bytes.NewBuffer(reqBody)
37
r, err := http.NewRequest(method, c.KlexURL + path, reqBytes)
34
r, err := http.NewRequest(method, c.KlexURL + path, reqBytes)
38
if err != nil {
35
if err != nil {
39
return fmt.Errorf("In http.NewRequest: %v", err)
36
return fmt.Errorf("In http.NewRequest: %v", err)
40
}
37
}
41
r.Header.Set("Authorization", "Bearer " + c.APIKey)
38
r.Header.Set("Authorization", "Bearer " + c.APIKey)
42
r.Header.Set("Content-Type", "application/json")
39
r.Header.Set("Content-Type", "application/json")
43
resHttp, err := http.DefaultClient.Do(r)
40
resHttp, err := http.DefaultClient.Do(r)
44
if err != nil {
41
if err != nil {
45
return fmt.Errorf("http.DefaultClient.Do: %v", err)
42
return fmt.Errorf("http.DefaultClient.Do: %v", err)
46
}
43
}
47
defer resHttp.Body.Close()
44
defer resHttp.Body.Close()
48
resBody, err := ioutil.ReadAll(resHttp.Body)
45
resBody, err := ioutil.ReadAll(resHttp.Body)
49
if err != nil {
46
if err != nil {
50
return fmt.Errorf("Response error: %v", err)
47
return fmt.Errorf("Response error: %v", err)
51
}
48
}
52
if resHttp.StatusCode != 200 && resHttp.StatusCode != 204 {
49
if resHttp.StatusCode != 200 && resHttp.StatusCode != 204 {
53
return fmt.Errorf("Status %d; response=%s", resHttp.StatusCode, resBody)
50
return fmt.Errorf("Status %d; response=%s", resHttp.StatusCode, resBody)
54
}
51
}
55
if res != nil {
52
if res != nil {
56
if err := json.Unmarshal(resBody, res); err != nil {
53
if err := json.Unmarshal(resBody, res); err != nil {
57
return fmt.Errorf("Bad response %s\nerror=%v", resBody, err)
54
return fmt.Errorf("Bad response %s\nerror=%v", resBody, err)
58
}
55
}
59
}
56
}
60
return nil
57
return nil
61
}
58
}
62
59
63
// F executes a function on one given input.
60
// F executes a function on one given input.
64
func (c *Client) F(f, in string) (string, error) {
61
func (c *Client) F(f, in string) (string, error) {
65
var res FResponse
62
var res FResponse
66
err := c.call("POST", "/f", FRequest{FName: f, In: in}, &res)
63
err := c.call("POST", "/f", FRequest{FName: f, In: in}, &res)
67
if err != nil {
64
if err != nil {
68
return "", err
65
return "", err
69
}
66
}
70
if res.Err != "" {
67
if res.Err != "" {
71
return "", fmt.Errorf(res.Err)
68
return "", fmt.Errorf(res.Err)
72
}
69
}
73
return res.Out, nil
70
return res.Out, nil
74
}
71
}
75
72
76
// Messages executes an LLM function using the Messages API.
73
// Messages executes an LLM function using the Messages API.
77
// Set req.Model to one of the Klex LLM function names.
74
// Set req.Model to one of the Klex LLM function names.
78
func (c *Client) Messages(req MessagesRequest) (*MessagesResponse, error) {
75
func (c *Client) Messages(req MessagesRequest) (*MessagesResponse, error) {
79
f := req.Model
76
f := req.Model
80
req.Model = ""
77
req.Model = ""
81
if f == "" {
78
if f == "" {
82
return nil, fmt.Errorf("MessagesRequest.Model is empty")
79
return nil, fmt.Errorf("MessagesRequest.Model is empty")
83
}
80
}
84
in, err := json.Marshal(req)
81
in, err := json.Marshal(req)
85
if err != nil {
82
if err != nil {
86
return nil, fmt.Errorf("Cannot marshal request: %v", err)
83
return nil, fmt.Errorf("Cannot marshal request: %v", err)
87
}
84
}
88
out, err := c.F(f, string(in))
85
out, err := c.F(f, string(in))
89
if err != nil {
86
if err != nil {
90
return nil, err
87
return nil, err
91
}
88
}
92
var res MessagesResponse
89
var res MessagesResponse
93
err = json.Unmarshal([]byte(out), &res)
90
err = json.Unmarshal([]byte(out), &res)
94
if err != nil {
91
if err != nil {
95
// 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.
96
// Let the caller figure this out.
93
// Let the caller figure this out.
97
res.Error = &ErrorResponse{
94
res.Error = &ErrorResponse{
98
Type: "response-json",
95
Type: "response-json",
99
Message: err.Error(),
96
Message: err.Error(),
100
}
97
}
101
res.Content = []ContentBlock{{Type: "text", Text: out}}
98
res.Content = []ContentBlock{{Type: "text", Text: out}}
102
}
99
}
103
return &res, nil
100
return &res, nil
104
}
101
}
105
102
106
// Embed returns semantic embedding vectors for the given text.
103
// Embed returns semantic embedding vectors for the given text.
107
func (c *Client) Embed(req EmbedRequest) ([][]float32, error) {
104
func (c *Client) Embed(req EmbedRequest) ([][]float32, error) {
108
var resp [][]float32
105
var resp [][]float32
109
err := c.call("POST", "/embed/do", req, &resp)
106
err := c.call("POST", "/embed/do", req, &resp)
110
return resp, err
107
return resp, err
111
}
108
}
112
109
113
// NewDataset creates a new dataset or updates an existing one.
110
// NewDataset creates a new dataset or updates an existing one.
114
// This is the simplest way, meant for datasets smaller than ~1GB.
111
// This is the simplest way, meant for datasets smaller than ~1GB.
115
func (c *Client) NewDataset(name string, data map[string]string) error {
112
func (c *Client) NewDataset(name string, data map[string]string) error {
116
// TODO: this loses key names; get rid of this API.
113
// TODO: this loses key names; get rid of this API.
117
req := NewDatasetRequest{Name: name, Data: nil}
114
req := NewDatasetRequest{Name: name, Data: nil}
118
keys := make([]string, 0, len(data))
115
keys := make([]string, 0, len(data))
119
for k := range data {
116
for k := range data {
120
keys = append(keys, k)
117
keys = append(keys, k)
121
}
118
}
122
sort.Strings(keys)
119
sort.Strings(keys)
123
for _, k := range keys {
120
for _, k := range keys {
124
req.Data = append(req.Data, data[k])
121
req.Data = append(req.Data, data[k])
125
}
122
}
126
123
127
var res NewDatasetResponse
124
var res NewDatasetResponse
128
err := c.call("POST", "/datasets/new", req, &res)
125
err := c.call("POST", "/datasets/new", req, &res)
129
if err != nil {
126
if err != nil {
130
return fmt.Errorf("Error POSTing to /datasets/new: %v", err)
127
return fmt.Errorf("Error POSTing to /datasets/new: %v", err)
131
}
128
}
132
if res.Name != name || res.Size != len(data) {
129
if res.Name != name || res.Size != len(data) {
133
pretty, _ := json.MarshalIndent(res, "", " ")
130
pretty, _ := json.MarshalIndent(res, "", " ")
134
return fmt.Errorf("Unexpected response from /datasets/new: %s", pretty)
131
return fmt.Errorf("Unexpected response from /datasets/new: %s", pretty)
135
}
132
}
136
return nil
133
return nil
137
}
134
}
138
135
139
// BeginNewDataset starts a new dataset upload using the v2 API.
136
// BeginNewDataset starts a new dataset upload using the v2 API.
140
// Returns the version key to use in UploadKv() and EndNewDataset().
137
// Returns the version key to use in UploadKv() and EndNewDataset().
141
// Keep the key secret until EndNewDataset() returns successfully.
138
// Keep the key secret until EndNewDataset() returns successfully.
142
func (c *Client) BeginNewDataset(name string) (string, error) {
139
func (c *Client) BeginNewDataset(name string) (string, error) {
143
req := BeginNewDatasetRequest{Name: name}
140
req := BeginNewDatasetRequest{Name: name}
144
var res BeginNewDatasetResponse
141
var res BeginNewDatasetResponse
145
err := c.call("POST", "/datasets/begin_new", req, &res)
142
err := c.call("POST", "/datasets/begin_new", req, &res)
146
if err != nil {
143
if err != nil {
147
return "", fmt.Errorf("Error POSTing to /datasets/begin_new: %v", err)
144
return "", fmt.Errorf("Error POSTing to /datasets/begin_new: %v", err)
148
}
145
}
149
return res.VersionKey, nil
146
return res.VersionKey, nil
150
}
147
}
151
148
152
// UploadKv uploads more key-value pairs of the dataset being created.
149
// UploadKv uploads more key-value pairs of the dataset being created.
153
func (c *Client) UploadKV(versionKey string, records []KV) error {
150
func (c *Client) UploadKV(versionKey string, records []KV) error {
154
req := UploadKVRequest{VersionKey: versionKey, Records: records}
151
req := UploadKVRequest{VersionKey: versionKey, Records: records}
155
err := c.call("POST", "/datasets/upload_kv", req, nil)
152
err := c.call("POST", "/datasets/upload_kv", req, nil)
156
if err != nil {
153
if err != nil {
157
return fmt.Errorf("Error POSTing to /datasets/upload_kv: %v", err)
154
return fmt.Errorf("Error POSTing to /datasets/upload_kv: %v", err)
158
}
155
}
159
return nil
156
return nil
160
}
157
}
161
158
162
// EndNewDataset commits the dataset being created.
159
// EndNewDataset commits the dataset being created.
163
func (c *Client) EndNewDataset(name, version_key string, size int) error {
160
func (c *Client) EndNewDataset(name, version_key string, size int) error {
164
req := EndNewDatasetRequest{Name: name, VersionKey: version_key, Size: size}
161
req := EndNewDatasetRequest{Name: name, VersionKey: version_key, Size: size}
165
err := c.call("POST", "/datasets/end_new", req, nil)
162
err := c.call("POST", "/datasets/end_new", req, nil)
166
if err != nil {
163
if err != nil {
167
return fmt.Errorf("Error POSTing to /datasets/end_new: %v", err)
164
return fmt.Errorf("Error POSTing to /datasets/end_new: %v", err)
168
}
165
}
169
return nil
166
return nil
170
}
167
}
a/embed/main.go
b/embed/main.go
1
package main
1
package main
2
2
3
// This binary converts text into embedding vecors.
3
// This binary converts text into embedding vecors.
4
4
5
//import "encoding/json"
6
import "flag"
5
import "flag"
7
import "fmt"
6
import "fmt"
8
import "io/ioutil"
7
import "io/ioutil"
9
import "log"
8
import "log"
10
import "os"
9
import "os"
11
//import "sync"
12
10
13
import "oscarkilo.com/klex-git/api"
11
import "oscarkilo.com/klex-git/api"
14
import "oscarkilo.com/klex-git/config"
12
import "oscarkilo.com/klex-git/config"
15
//import "oscarkilo.com/klex-git/util"
16
13
17
var model = flag.String("model", "openai:text-embedding-3-small", "")
14
var model = flag.String("model", "openai:text-embedding-3-small", "")
18
var dims = flag.Int("dims", 1536, "Number of vector dimensions to return")
15
var dims = flag.Int("dims", 1536, "Number of vector dimensions to return")
19
var full_path = flag.Bool("full_path", false, "Returns a list of vectors")
16
var full_path = flag.Bool("full_path", false, "Returns a list of vectors")
20
17
21
func main() {
18
func main() {
22
flag.Parse()
19
flag.Parse()
23
20
24
// Find the API keys and configure a Klex client.
21
// Find the API keys and configure a Klex client.
25
config, err := config.ReadConfig()
22
config, err := config.ReadConfig()
26
if err != nil {
23
if err != nil {
27
log.Fatalf("Failed to read config: %v", err)
24
log.Fatalf("Failed to read config: %v", err)
28
}
25
}
29
client := api.NewClient(config.KlexUrl, config.ApiKey)
26
client := api.NewClient(config.KlexUrl, config.ApiKey)
30
if client == nil {
27
if client == nil {
31
log.Fatalf("Failed to create Klex client")
28
log.Fatalf("Failed to create Klex client")
32
}
29
}
33
30
34
// Read stdin as text.
31
// Read stdin as text.
35
sin, err := ioutil.ReadAll(os.Stdin)
32
sin, err := ioutil.ReadAll(os.Stdin)
36
if err != nil {
33
if err != nil {
37
log.Fatalf("Failed to read stdin: %v", err)
34
log.Fatalf("Failed to read stdin: %v", err)
38
}
35
}
39
//text := []string{string(sin)}
40
36
41
vectors, err := client.Embed(api.EmbedRequest{
37
vectors, err := client.Embed(api.EmbedRequest{
42
Text: string(sin),
38
Text: string(sin),
43
Model: *model,
39
Model: *model,
44
Dims: *dims,
40
Dims: *dims,
45
FullPath: *full_path,
41
FullPath: *full_path,
46
})
42
})
47
if err != nil {
43
if err != nil {
48
log.Fatalf("Failed to call Embed: %v", err)
44
log.Fatalf("Failed to call Embed: %v", err)
49
}
45
}
50
46
51
/*
52
if *full_path {
53
text = util.SplitByWord(text[0])
54
}
55
56
f_name := fmt.Sprintf("embed-%s@%d", *model, *dims)
57
vectors := make([][]float32, len(text))
58
wg := sync.WaitGroup{}
59
for i := range text {
60
wg.Add(1)
61
go func(i int) {
62
json_vector, err := client.F(f_name, text[i])
63
if err != nil {
64
log.Fatalf("Failed to call F: %v", err)
65
}
66
err = json.Unmarshal([]byte(json_vector), &vectors[i])
67
if err != nil {
68
log.Fatalf("Failed to parse vector: %v", err)
69
}
70
wg.Done()
71
}(i)
72
}
73
wg.Wait()
74
*/
75
76
for _, vector := range vectors {
47
for _, vector := range vectors {
77
for i, w := range vector {
48
for i, w := range vector {
78
if i > 0 {
49
if i > 0 {
79
fmt.Printf(" ")
50
fmt.Printf(" ")
80
}
51
}
81
fmt.Printf("%g", w)
52
fmt.Printf("%g", w)
82
}
53
}
83
fmt.Printf("\n")
54
fmt.Printf("\n")
84
}
55
}
85
}
56
}
a/util/prompts.go
/dev/null
1
package util
2
3
import "unicode"
4
5
// SplitByWord returns all whitespace-terminated prefixes of 'text'.
6
// The first entry will be the first word and its trailing whitespace.
7
// The last entry will be the whole 'text'.
8
func SplitByWord(text string) []string {
9
var prefixes []string
10
runes := []rune(text)
11
12
const ALL_WHITESPACE = 0
13
const SAW_CHARS = 1
14
const SAW_WHITESPACE_AFTER_CHARS = 2
15
state := ALL_WHITESPACE
16
17
for i, c := range runes {
18
if unicode.IsSpace(c) {
19
if state == SAW_CHARS {
20
state = SAW_WHITESPACE_AFTER_CHARS
21
}
22
} else {
23
if state == SAW_WHITESPACE_AFTER_CHARS {
24
prefixes = append(prefixes, string(runes[:i]))
25
}
26
state = SAW_CHARS
27
}
28
}
29
return append(prefixes, text)
30
}
a/util/prompts_test.go
/dev/null
1
package util
2
3
import "encoding/json"
4
import "testing"
5
6
func same(a, b []string) bool {
7
if len(a) != len(b) {
8
return false
9
}
10
for i := range a {
11
if a[i] != b[i] {
12
return false
13
}
14
}
15
return true
16
}
17
18
func TestSplitByWord(t *testing.T) {
19
// check verifies that golden == SplitByWord(golden[-1]).
20
check := func(golden ...string) {
21
t.Helper()
22
in := golden[len(golden)-1]
23
out := SplitByWord(in)
24
if !same(golden, out) {
25
ijson, _ := json.Marshal(in)
26
gjson, _ := json.MarshalIndent(golden, "", " ")
27
ojson, _ := json.MarshalIndent(out, "", " ")
28
t.Errorf("SplitByWord(%s):\nwant: %s\nhave: %s", ijson, gjson, ojson)
29
}
30
}
31
32
check("")
33
check(" ")
34
check(" \n\t \n")
35
check(
36
"hello ",
37
"hello world",
38
)
39
check(
40
"Once ",
41
"Once upon\t",
42
"Once upon\ta\n ",
43
"Once upon\ta\n time, ",
44
)
45
check(
46
"Snap, ",
47
"Snap, crackle, ",
48
"Snap, crackle, and ",
49
"Snap, crackle, and pop.",
50
)
51
check(
52
" leading ",
53
" leading whitespace",
54
)
55
}