code.oscarkilo.com/klex-git

Hash:
47fbf3b9b3d38d991c526556e01ac8345877dbe9
Author:
Igor Naverniouk <[email protected]>
Date:
Thu Nov 20 15:09:20 2025 -0800
Message:
doesn't work yet; not sure why
diff --git a/exemplary/main.go b/exemplary/main.go
index 2210cc1..008bbff 100644
--- a/exemplary/main.go
+++ b/exemplary/main.go
@@ -1,10 +1,13 @@
package main

-import "errors"
+import "encoding/base64"
+import "encoding/json"
import "flag"
-import "fmt"
+import "io/ioutil"
import "log"
+import "net/http"
import "os"
+import "path"
import "sort"
import "strings"

@@ -14,22 +17,19 @@ import "oscarkilo.com/klex-git/config"
var dir = flag.String("dir", ".", "Directory to scan and write to.")
var model = flag.String("model", "google:gemini-3-pro", "")
var format = flag.String("format", "text", "text|json|jsonindent")
-
-// chain propagates an error up the stack.
-func chain(err error, msg string, args ...any) error {
- return errors.Join(fmt.Errorf(msg, args...), err)
-}
+var dry_run = flag.Bool("dry_run", false, "")

type Case struct {
Name string
Before []string
After string
+ Request *api.MessagesRequest
}

-func scanForCases() ([]Case, error) {
+func scanForCases() []Case {
entries, err := os.ReadDir(*dir)
if err != nil {
- return nil, chain(err, "scanForCases().ReadDir")
+ log.Fatalf("Failed to read dir %s: %v", *dir, err)
}

before := make(map[string][]string)
@@ -57,6 +57,15 @@ func scanForCases() ([]Case, error) {

var cases []Case
for name, b := range before {
+ sort.Slice(b, func(i, j int) bool {
+ if b[i] == "txt" && b[j] != "txt" {
+ return true
+ }
+ if b[i] != "txt" && b[j] == "txt" {
+ return false
+ }
+ return b[i] < b[j]
+ })
cases = append(cases, Case{
Name: name,
Before: b,
@@ -75,7 +84,44 @@ func scanForCases() ([]Case, error) {
return a.Name < b.Name
})

- return cases, nil
+ num_examples := 0
+ for ; num_examples < len(cases); num_examples++ {
+ if cases[num_examples].After == "" {
+ break
+ }
+ }
+ log.Printf(
+ "%s:\n num_examples = %d\n num_inputs = %d",
+ *dir,
+ num_examples,
+ len(cases) - num_examples,
+ )
+
+ return cases
+}
+
+func readFile(fname string) []byte {
+ data, err := ioutil.ReadFile(path.Join(*dir, fname))
+ if err != nil {
+ log.Fatalf("Failed to read file %s: %v", fname, err)
+ }
+ return data
+}
+
+func writeFile(fname string, data []byte) error {
+ return ioutil.WriteFile(path.Join(*dir, fname), data, 0644)
+}
+
+func copyRequest(req api.MessagesRequest) *api.MessagesRequest {
+ bytes, err := json.Marshal(req)
+ if err != nil {
+ log.Fatalf("Failed to copy request: %+v", err)
+ }
+ err = json.Unmarshal(bytes, &req)
+ if err != nil {
+ log.Fatalf("Failed to copy request: %+v", err)
+ }
+ return &req
}

func main() {
@@ -91,5 +137,85 @@ func main() {
log.Fatalf("Failed to create Klex client")
}

-
+ // Create MessagesRequest objects.
+ req := api.MessagesRequest{
+ Model: *model,
+ System: string(readFile("system_prompt.txt")),
+ }
+ cases := scanForCases()
+ for i, c := range cases {
+ user := api.ChatMessage{Role: "user"}
+ text := "Case name: " + c.Name + "\n\n"
+ for _, suffix := range c.Before {
+ switch suffix {
+ case "txt":
+ text += string(readFile(c.Name + ".txt"))
+ case "json":
+ text += "\n\n```json\n"
+ text += string(readFile(c.Name + ".json"))
+ text += "\n```\n"
+ case "jpg", "jpeg", "png", "webp":
+ bytes := readFile(c.Name + "." + suffix)
+ user.Content = append(user.Content, api.ContentBlock{
+ Type: "image",
+ Source: &api.ContentSource{
+ Type: "base64",
+ MediaType: http.DetectContentType(bytes),
+ Data: base64.StdEncoding.EncodeToString(bytes),
+ },
+ })
+ default:
+ log.Fatalf("Unsupported suffix %s in case %s", suffix, c.Name)
+ }
+ }
+ req.Messages = append(req.Messages, user)
+ if c.After != "" {
+ asst := api.ChatMessage{Role: "assistant"}
+ asst.Content = append(asst.Content, api.ContentBlock{
+ Type: "text",
+ Text: string(readFile(c.Name + ".out")),
+ })
+ } else {
+ cases[i].Request = copyRequest(req)
+ req.Messages = req.Messages[:len(req.Messages)-1]
+ }
+ }
+
+ if *dry_run {
+ log.Printf("Dry run; not sending requests.")
+ return
+ }
+
+ // Send 'em.
+ // TODO: parallelize
+ for _, c := range cases {
+ log.Printf("--- %s", c.Name) // DEBUG
+ if c.Request == nil {
+ continue
+ }
+ log.Printf("--- %s: mark 1", c.Name) // DEBUG
+ res, err := client.Messages(*c.Request)
+ if err != nil {
+ log.Printf("Case %s: request failed: %+v", c.Name, err)
+ continue
+ }
+ log.Printf("--- %s: mark 2", c.Name) // DEBUG
+ if len(res.Content) != 1 {
+ log.Printf("Case %s: empty response", c.Name)
+ continue
+ }
+ log.Printf("--- %s: mark 3", c.Name) // DEBUG
+ c0 := res.Content[0]
+ if c0.Type != "text" {
+ log.Printf("Case %s: Content[0].Type = %s", c.Name, c0.Type)
+ continue
+ }
+ log.Printf("--- %s: mark 4", c.Name) // DEBUG
+ fname := path.Join(*dir, c.Name + ".out")
+ if err = writeFile(fname, []byte(c0.Text)); err != nil {
+ log.Printf("Case %s: failed to write %s: %+v", c.Name, fname, err)
+ continue
+ }
+ log.Printf("Case %s: wrote %s", c.Name, fname)
+ }
}
diff --git a/exemplary/main_test.go b/exemplary/main_test.go
index 94464b0..675697d 100644
--- a/exemplary/main_test.go
+++ b/exemplary/main_test.go
@@ -3,6 +3,8 @@ package main
import "encoding/json"
import "testing"

+import "oscarkilo.com/klex-git/api"
+
func CheckEq(t *testing.T, golden, actual []Case) {
t.Helper()
g, err := json.MarshalIndent(golden, "", " ")
@@ -21,10 +23,6 @@ func CheckEq(t *testing.T, golden, actual []Case) {
func TestScanForCases(t *testing.T) {
// Get the path to the example dir next to this main_test.go file.
*dir = "./example"
- cases, err := scanForCases()
- if err != nil {
- t.Fatalf("scanForCases() failed: %v", err)
- }
golden := []Case{
Case{
Name: "2025-10-10",
@@ -36,5 +34,14 @@ func TestScanForCases(t *testing.T) {
Before: []string{"txt"},
},
}
- CheckEq(t, golden, cases)
+ CheckEq(t, golden, scanForCases())
+}
+
+func TestCopyRequest(t *testing.T) {
+ a := &api.MessagesRequest{Model: "foo"}
+ b := copyRequest(*a)
+ a.Model = "bar"
+ if b.Model != "foo" {
+ t.Errorf("%s %s", a.Model, b.Model)
+ }
}
a/exemplary/main.go
b/exemplary/main.go
1
package main
1
package main
2
2
3
import "errors"
3
import "encoding/base64"
4
import "encoding/json"
4
import "flag"
5
import "flag"
5
import "fmt"
6
import "io/ioutil"
6
import "log"
7
import "log"
8
import "net/http"
7
import "os"
9
import "os"
10
import "path"
8
import "sort"
11
import "sort"
9
import "strings"
12
import "strings"
10
13
11
import "oscarkilo.com/klex-git/api"
14
import "oscarkilo.com/klex-git/api"
12
import "oscarkilo.com/klex-git/config"
15
import "oscarkilo.com/klex-git/config"
13
16
14
var dir = flag.String("dir", ".", "Directory to scan and write to.")
17
var dir = flag.String("dir", ".", "Directory to scan and write to.")
15
var model = flag.String("model", "google:gemini-3-pro", "")
18
var model = flag.String("model", "google:gemini-3-pro", "")
16
var format = flag.String("format", "text", "text|json|jsonindent")
19
var format = flag.String("format", "text", "text|json|jsonindent")
17
20
var dry_run = flag.Bool("dry_run", false, "")
18
// chain propagates an error up the stack.
19
func chain(err error, msg string, args ...any) error {
20
return errors.Join(fmt.Errorf(msg, args...), err)
21
}
22
21
23
type Case struct {
22
type Case struct {
24
Name string
23
Name string
25
Before []string
24
Before []string
26
After string
25
After string
26
Request *api.MessagesRequest
27
}
27
}
28
28
29
func scanForCases() ([]Case, error) {
29
func scanForCases() []Case {
30
entries, err := os.ReadDir(*dir)
30
entries, err := os.ReadDir(*dir)
31
if err != nil {
31
if err != nil {
32
return nil, chain(err, "scanForCases().ReadDir")
32
log.Fatalf("Failed to read dir %s: %v", *dir, err)
33
}
33
}
34
34
35
before := make(map[string][]string)
35
before := make(map[string][]string)
36
after := make(map[string]string)
36
after := make(map[string]string)
37
for _, entry := range entries {
37
for _, entry := range entries {
38
if entry.IsDir() {
38
if entry.IsDir() {
39
continue
39
continue
40
}
40
}
41
if entry.Name() == "system_prompt.txt" {
41
if entry.Name() == "system_prompt.txt" {
42
continue
42
continue
43
}
43
}
44
chunks := strings.Split(entry.Name(), ".")
44
chunks := strings.Split(entry.Name(), ".")
45
if len(chunks) < 2 {
45
if len(chunks) < 2 {
46
continue
46
continue
47
}
47
}
48
name := strings.Join(chunks[0:len(chunks)-1], ".")
48
name := strings.Join(chunks[0:len(chunks)-1], ".")
49
suffix := chunks[len(chunks)-1]
49
suffix := chunks[len(chunks)-1]
50
switch suffix {
50
switch suffix {
51
case "txt", "json", "jpg", "jpeg", "png":
51
case "txt", "json", "jpg", "jpeg", "png":
52
before[name] = append(before[name], suffix)
52
before[name] = append(before[name], suffix)
53
case "out":
53
case "out":
54
after[name] = suffix
54
after[name] = suffix
55
}
55
}
56
}
56
}
57
57
58
var cases []Case
58
var cases []Case
59
for name, b := range before {
59
for name, b := range before {
60
sort.Slice(b, func(i, j int) bool {
61
if b[i] == "txt" && b[j] != "txt" {
62
return true
63
}
64
if b[i] != "txt" && b[j] == "txt" {
65
return false
66
}
67
return b[i] < b[j]
68
})
60
cases = append(cases, Case{
69
cases = append(cases, Case{
61
Name: name,
70
Name: name,
62
Before: b,
71
Before: b,
63
After: after[name],
72
After: after[name],
64
})
73
})
65
}
74
}
66
75
67
sort.Slice(cases, func(i, j int) bool {
76
sort.Slice(cases, func(i, j int) bool {
68
a, b := cases[i], cases[j]
77
a, b := cases[i], cases[j]
69
if a.After != "" && b.After == "" {
78
if a.After != "" && b.After == "" {
70
return true
79
return true
71
}
80
}
72
if a.After == "" && b.After != "" {
81
if a.After == "" && b.After != "" {
73
return false
82
return false
74
}
83
}
75
return a.Name < b.Name
84
return a.Name < b.Name
76
})
85
})
77
86
78
return cases, nil
87
num_examples := 0
88
for ; num_examples < len(cases); num_examples++ {
89
if cases[num_examples].After == "" {
90
break
91
}
92
}
93
log.Printf(
94
"%s:\n num_examples = %d\n num_inputs = %d",
95
*dir,
96
num_examples,
97
len(cases) - num_examples,
98
)
99
100
return cases
101
}
102
103
func readFile(fname string) []byte {
104
data, err := ioutil.ReadFile(path.Join(*dir, fname))
105
if err != nil {
106
log.Fatalf("Failed to read file %s: %v", fname, err)
107
}
108
return data
109
}
110
111
func writeFile(fname string, data []byte) error {
112
return ioutil.WriteFile(path.Join(*dir, fname), data, 0644)
113
}
114
115
func copyRequest(req api.MessagesRequest) *api.MessagesRequest {
116
bytes, err := json.Marshal(req)
117
if err != nil {
118
log.Fatalf("Failed to copy request: %+v", err)
119
}
120
err = json.Unmarshal(bytes, &req)
121
if err != nil {
122
log.Fatalf("Failed to copy request: %+v", err)
123
}
124
return &req
79
}
125
}
80
126
81
func main() {
127
func main() {
82
flag.Parse()
128
flag.Parse()
83
129
84
// Find the API keys and configure a Klex client.
130
// Find the API keys and configure a Klex client.
85
config, err := config.ReadConfig()
131
config, err := config.ReadConfig()
86
if err != nil {
132
if err != nil {
87
log.Fatalf("Failed to read config: %v", err)
133
log.Fatalf("Failed to read config: %v", err)
88
}
134
}
89
client := api.NewClient(config.KlexUrl, config.ApiKey)
135
client := api.NewClient(config.KlexUrl, config.ApiKey)
90
if client == nil {
136
if client == nil {
91
log.Fatalf("Failed to create Klex client")
137
log.Fatalf("Failed to create Klex client")
92
}
138
}
93
139
94
140
// Create MessagesRequest objects.
141
req := api.MessagesRequest{
142
Model: *model,
143
System: string(readFile("system_prompt.txt")),
144
}
145
cases := scanForCases()
146
for i, c := range cases {
147
user := api.ChatMessage{Role: "user"}
148
text := "Case name: " + c.Name + "\n\n"
149
for _, suffix := range c.Before {
150
switch suffix {
151
case "txt":
152
text += string(readFile(c.Name + ".txt"))
153
case "json":
154
text += "\n\n```json\n"
155
text += string(readFile(c.Name + ".json"))
156
text += "\n```\n"
157
case "jpg", "jpeg", "png", "webp":
158
bytes := readFile(c.Name + "." + suffix)
159
user.Content = append(user.Content, api.ContentBlock{
160
Type: "image",
161
Source: &api.ContentSource{
162
Type: "base64",
163
MediaType: http.DetectContentType(bytes),
164
Data: base64.StdEncoding.EncodeToString(bytes),
165
},
166
})
167
default:
168
log.Fatalf("Unsupported suffix %s in case %s", suffix, c.Name)
169
}
170
}
171
req.Messages = append(req.Messages, user)
172
if c.After != "" {
173
asst := api.ChatMessage{Role: "assistant"}
174
asst.Content = append(asst.Content, api.ContentBlock{
175
Type: "text",
176
Text: string(readFile(c.Name + ".out")),
177
})
178
} else {
179
cases[i].Request = copyRequest(req)
180
req.Messages = req.Messages[:len(req.Messages)-1]
181
}
182
}
183
184
if *dry_run {
185
log.Printf("Dry run; not sending requests.")
186
return
187
}
188
189
// Send 'em.
190
// TODO: parallelize
191
for _, c := range cases {
192
log.Printf("--- %s", c.Name) // DEBUG
193
if c.Request == nil {
194
continue
195
}
196
log.Printf("--- %s: mark 1", c.Name) // DEBUG
197
res, err := client.Messages(*c.Request)
198
if err != nil {
199
log.Printf("Case %s: request failed: %+v", c.Name, err)
200
continue
201
}
202
log.Printf("--- %s: mark 2", c.Name) // DEBUG
203
if len(res.Content) != 1 {
204
log.Printf("Case %s: empty response", c.Name)
205
continue
206
}
207
log.Printf("--- %s: mark 3", c.Name) // DEBUG
208
c0 := res.Content[0]
209
if c0.Type != "text" {
210
log.Printf("Case %s: Content[0].Type = %s", c.Name, c0.Type)
211
continue
212
}
213
log.Printf("--- %s: mark 4", c.Name) // DEBUG
214
fname := path.Join(*dir, c.Name + ".out")
215
if err = writeFile(fname, []byte(c0.Text)); err != nil {
216
log.Printf("Case %s: failed to write %s: %+v", c.Name, fname, err)
217
continue
218
}
219
log.Printf("Case %s: wrote %s", c.Name, fname)
220
}
95
}
221
}
a/exemplary/main_test.go
b/exemplary/main_test.go
1
package main
1
package main
2
2
3
import "encoding/json"
3
import "encoding/json"
4
import "testing"
4
import "testing"
5
5
6
import "oscarkilo.com/klex-git/api"
7
6
func CheckEq(t *testing.T, golden, actual []Case) {
8
func CheckEq(t *testing.T, golden, actual []Case) {
7
t.Helper()
9
t.Helper()
8
g, err := json.MarshalIndent(golden, "", " ")
10
g, err := json.MarshalIndent(golden, "", " ")
9
if err != nil {
11
if err != nil {
10
t.Fatalf("json.MarshalIndent(golden) failed: %+v", err)
12
t.Fatalf("json.MarshalIndent(golden) failed: %+v", err)
11
}
13
}
12
a, err := json.MarshalIndent(actual, "", " ")
14
a, err := json.MarshalIndent(actual, "", " ")
13
if err != nil {
15
if err != nil {
14
t.Fatalf("json.MarshalIndent(actual) failed: %+v", err)
16
t.Fatalf("json.MarshalIndent(actual) failed: %+v", err)
15
}
17
}
16
if string(g) != string(a) {
18
if string(g) != string(a) {
17
t.Errorf("mismatch:\ngolden:\n%s\nactual:\n%s\n", g, a)
19
t.Errorf("mismatch:\ngolden:\n%s\nactual:\n%s\n", g, a)
18
}
20
}
19
}
21
}
20
22
21
func TestScanForCases(t *testing.T) {
23
func TestScanForCases(t *testing.T) {
22
// Get the path to the example dir next to this main_test.go file.
24
// Get the path to the example dir next to this main_test.go file.
23
*dir = "./example"
25
*dir = "./example"
24
cases, err := scanForCases()
25
if err != nil {
26
t.Fatalf("scanForCases() failed: %v", err)
27
}
28
golden := []Case{
26
golden := []Case{
29
Case{
27
Case{
30
Name: "2025-10-10",
28
Name: "2025-10-10",
31
Before: []string{"txt"},
29
Before: []string{"txt"},
32
After: "out",
30
After: "out",
33
},
31
},
34
Case{
32
Case{
35
Name: "2025-11-11",
33
Name: "2025-11-11",
36
Before: []string{"txt"},
34
Before: []string{"txt"},
37
},
35
},
38
}
36
}
39
CheckEq(t, golden, cases)
37
CheckEq(t, golden, scanForCases())
38
}
39
40
func TestCopyRequest(t *testing.T) {
41
a := &api.MessagesRequest{Model: "foo"}
42
b := copyRequest(*a)
43
a.Model = "bar"
44
if b.Model != "foo" {
45
t.Errorf("%s %s", a.Model, b.Model)
46
}
40
}
47
}