diff --git a/exemplary/README.md b/exemplary/README.md
new file mode 100644
index 0000000..2a32690
--- /dev/null
+++ b/exemplary/README.md
@@ -0,0 +1,18 @@
+### Exemplary
+
+...is an AI tool for transforming documents based on a small set of
+examples. Fittingly, let's demonstrate its use with an example.
+
+Take a look at the files in the `example` folder.
+
+- `system_prompt.txt` explains the task, which is to summarize incoming mail.
+- `2025-10-10.txt` is an example of a personal letter.
+- `2025-10-10.out` is the desired output for that letter.
+- `2025-11-11.txt` is another letter, whose output will be generated.
+
+To generate `2025-11-11.out`, run the following command in the
+`example/` directory.
+
+```bash
+go run oscarkilo.com/klex-git/exemplary
+```
diff --git a/exemplary/example/2025-10-10.out b/exemplary/example/2025-10-10.out
new file mode 100644
index 0000000..ea01d0a
--- /dev/null
+++ b/exemplary/example/2025-10-10.out
@@ -0,0 +1,4 @@
+Date: 2025-10-10
+Sender: Upton O'Goode
+Category: personal
+Intent: a silly wedding invitation
diff --git a/exemplary/example/2025-10-10.txt b/exemplary/example/2025-10-10.txt
new file mode 100644
index 0000000..eee93c6
--- /dev/null
+++ b/exemplary/example/2025-10-10.txt
@@ -0,0 +1,12 @@
+Dear bro,
+
+I hereby cordially invite you to my wedding ceremony at Chucky Cheese
+on January 1. Beach attire required. Bring the porcupine. No tubas,
+obviously.
+
+Outer space intentinally left blank.
+
+RSVP maybe.
+
+Irregardless,
+Upton O'Goode
diff --git a/exemplary/example/2025-11-11.txt b/exemplary/example/2025-11-11.txt
new file mode 100644
index 0000000..a5047f8
--- /dev/null
+++ b/exemplary/example/2025-11-11.txt
@@ -0,0 +1,8 @@
+Dear patient,
+
+Your primary care provider is switching as of December 1, to an AI-powered
+virtual health assistant. Thank you for taking part in this experiment. Please
+send bug reports to
[email protected].
+
+Have a great day,
+The Future Medical Team
diff --git a/exemplary/example/system_prompt.txt b/exemplary/example/system_prompt.txt
new file mode 100644
index 0000000..f0218df
--- /dev/null
+++ b/exemplary/example/system_prompt.txt
@@ -0,0 +1,9 @@
+Let's analyse and classify my mail. Whenever I receive a letter,
+I open it, scan it, OCR it, and throw it into a folder on my computer.
+I will give you one scanned letter at a time, and you will extract a
+few basic facts from each letter:
+
+Date: yyyy-mm-dd
+Sender: just the name of a person or organization
+Category: bank, credit card, insurance, personal, junk, medical, unknown, etc.
+Intent: a one-line basic summary of why they sent it to me
diff --git a/exemplary/main.go b/exemplary/main.go
new file mode 100644
index 0000000..2210cc1
--- /dev/null
+++ b/exemplary/main.go
@@ -0,0 +1,95 @@
+package main
+
+import "errors"
+import "flag"
+import "fmt"
+import "log"
+import "os"
+import "sort"
+import "strings"
+
+import "oscarkilo.com/klex-git/api"
+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)
+}
+
+type Case struct {
+ Name string
+ Before []string
+ After string
+}
+
+func scanForCases() ([]Case, error) {
+ entries, err := os.ReadDir(*dir)
+ if err != nil {
+ return nil, chain(err, "scanForCases().ReadDir")
+ }
+
+ before := make(map[string][]string)
+ after := make(map[string]string)
+ for _, entry := range entries {
+ if entry.IsDir() {
+ continue
+ }
+ if entry.Name() == "system_prompt.txt" {
+ continue
+ }
+ chunks := strings.Split(entry.Name(), ".")
+ if len(chunks) < 2 {
+ continue
+ }
+ name := strings.Join(chunks[0:len(chunks)-1], ".")
+ suffix := chunks[len(chunks)-1]
+ switch suffix {
+ case "txt", "json", "jpg", "jpeg", "png":
+ before[name] = append(before[name], suffix)
+ case "out":
+ after[name] = suffix
+ }
+ }
+
+ var cases []Case
+ for name, b := range before {
+ cases = append(cases, Case{
+ Name: name,
+ Before: b,
+ After: after[name],
+ })
+ }
+
+ sort.Slice(cases, func(i, j int) bool {
+ a, b := cases[i], cases[j]
+ if a.After != "" && b.After == "" {
+ return true
+ }
+ if a.After == "" && b.After != "" {
+ return false
+ }
+ return a.Name < b.Name
+ })
+
+ return cases, nil
+}
+
+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")
+ }
+
+
+}
diff --git a/exemplary/main_test.go b/exemplary/main_test.go
new file mode 100644
index 0000000..94464b0
--- /dev/null
+++ b/exemplary/main_test.go
@@ -0,0 +1,40 @@
+package main
+
+import "encoding/json"
+import "testing"
+
+func CheckEq(t *testing.T, golden, actual []Case) {
+ t.Helper()
+ g, err := json.MarshalIndent(golden, "", " ")
+ if err != nil {
+ t.Fatalf("json.MarshalIndent(golden) failed: %+v", err)
+ }
+ a, err := json.MarshalIndent(actual, "", " ")
+ if err != nil {
+ t.Fatalf("json.MarshalIndent(actual) failed: %+v", err)
+ }
+ if string(g) != string(a) {
+ t.Errorf("mismatch:\ngolden:\n%s\nactual:\n%s\n", g, a)
+ }
+}
+
+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",
+ Before: []string{"txt"},
+ After: "out",
+ },
+ Case{
+ Name: "2025-11-11",
+ Before: []string{"txt"},
+ },
+ }
+ CheckEq(t, golden, cases)
+}
1
### Exemplary
2
3
...is an AI tool for transforming documents based on a small set of
4
examples. Fittingly, let's demonstrate its use with an example.
5
6
Take a look at the files in the `example` folder.
7
8
- `system_prompt.txt` explains the task, which is to summarize incoming mail.
9
- `2025-10-10.txt` is an example of a personal letter.
10
- `2025-10-10.out` is the desired output for that letter.
11
- `2025-11-11.txt` is another letter, whose output will be generated.
12
13
To generate `2025-11-11.out`, run the following command in the
14
`example/` directory.
15
16
```bash
17
go run oscarkilo.com/klex-git/exemplary
18
```
1
Date: 2025-10-10
2
Sender: Upton O'Goode
3
Category: personal
4
Intent: a silly wedding invitation
1
Dear bro,
2
3
I hereby cordially invite you to my wedding ceremony at Chucky Cheese
4
on January 1. Beach attire required. Bring the porcupine. No tubas,
5
obviously.
6
7
Outer space intentinally left blank.
8
9
RSVP maybe.
10
11
Irregardless,
12
Upton O'Goode
1
Dear patient,
2
3
Your primary care provider is switching as of December 1, to an AI-powered
4
virtual health assistant. Thank you for taking part in this experiment. Please
5
6
7
Have a great day,
8
The Future Medical Team
1
Let's analyse and classify my mail. Whenever I receive a letter,
2
I open it, scan it, OCR it, and throw it into a folder on my computer.
3
I will give you one scanned letter at a time, and you will extract a
4
few basic facts from each letter:
5
6
Date: yyyy-mm-dd
7
Sender: just the name of a person or organization
8
Category: bank, credit card, insurance, personal, junk, medical, unknown, etc.
9
Intent: a one-line basic summary of why they sent it to me
1
package main
2
3
import "errors"
4
import "flag"
5
import "fmt"
6
import "log"
7
import "os"
8
import "sort"
9
import "strings"
10
11
import "oscarkilo.com/klex-git/api"
12
import "oscarkilo.com/klex-git/config"
13
14
var dir = flag.String("dir", ".", "Directory to scan and write to.")
15
var model = flag.String("model", "google:gemini-3-pro", "")
16
var format = flag.String("format", "text", "text|json|jsonindent")
17
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
23
type Case struct {
24
Name string
25
Before []string
26
After string
27
}
28
29
func scanForCases() ([]Case, error) {
30
entries, err := os.ReadDir(*dir)
31
if err != nil {
32
return nil, chain(err, "scanForCases().ReadDir")
33
}
34
35
before := make(map[string][]string)
36
after := make(map[string]string)
37
for _, entry := range entries {
38
if entry.IsDir() {
39
continue
40
}
41
if entry.Name() == "system_prompt.txt" {
42
continue
43
}
44
chunks := strings.Split(entry.Name(), ".")
45
if len(chunks) < 2 {
46
continue
47
}
48
name := strings.Join(chunks[0:len(chunks)-1], ".")
49
suffix := chunks[len(chunks)-1]
50
switch suffix {
51
case "txt", "json", "jpg", "jpeg", "png":
52
before[name] = append(before[name], suffix)
53
case "out":
54
after[name] = suffix
55
}
56
}
57
58
var cases []Case
59
for name, b := range before {
60
cases = append(cases, Case{
61
Name: name,
62
Before: b,
63
After: after[name],
64
})
65
}
66
67
sort.Slice(cases, func(i, j int) bool {
68
a, b := cases[i], cases[j]
69
if a.After != "" && b.After == "" {
70
return true
71
}
72
if a.After == "" && b.After != "" {
73
return false
74
}
75
return a.Name < b.Name
76
})
77
78
return cases, nil
79
}
80
81
func main() {
82
flag.Parse()
83
84
// Find the API keys and configure a Klex client.
85
config, err := config.ReadConfig()
86
if err != nil {
87
log.Fatalf("Failed to read config: %v", err)
88
}
89
client := api.NewClient(config.KlexUrl, config.ApiKey)
90
if client == nil {
91
log.Fatalf("Failed to create Klex client")
92
}
93
94
95
}
1
package main
2
3
import "encoding/json"
4
import "testing"
5
6
func CheckEq(t *testing.T, golden, actual []Case) {
7
t.Helper()
8
g, err := json.MarshalIndent(golden, "", " ")
9
if err != nil {
10
t.Fatalf("json.MarshalIndent(golden) failed: %+v", err)
11
}
12
a, err := json.MarshalIndent(actual, "", " ")
13
if err != nil {
14
t.Fatalf("json.MarshalIndent(actual) failed: %+v", err)
15
}
16
if string(g) != string(a) {
17
t.Errorf("mismatch:\ngolden:\n%s\nactual:\n%s\n", g, a)
18
}
19
}
20
21
func TestScanForCases(t *testing.T) {
22
// Get the path to the example dir next to this main_test.go file.
23
*dir = "./example"
24
cases, err := scanForCases()
25
if err != nil {
26
t.Fatalf("scanForCases() failed: %v", err)
27
}
28
golden := []Case{
29
Case{
30
Name: "2025-10-10",
31
Before: []string{"txt"},
32
After: "out",
33
},
34
Case{
35
Name: "2025-11-11",
36
Before: []string{"txt"},
37
},
38
}
39
CheckEq(t, golden, cases)
40
}