code.oscarkilo.com/klex-git

Hash:
31080781a56bff822045655643084c860bafcc8c
Author:
Igor Naverniouk <[email protected]>
Date:
Wed Apr 23 10:23:31 2025 -0700
Message:
images in ./one
diff --git a/one/README.md b/one/README.md
index bbf48d3..48087ff 100644
--- a/one/README.md
+++ b/one/README.md
@@ -54,3 +54,12 @@ echo "{}" | go run oscarkilo.com/klex-git/one \
-prompt_file=prompt.txt
rm prompt.txt system.txt
```
+
+### Analyzing images
+
+```bash
+echo "{}" | go run oscarkilo.com/klex-git/one \
+ -model=gpt-4o \
+ -image_file=<(curl -s https://upload.wikimedia.org/wikipedia/commons/thumb/3/35/Tux.svg/1024px-Tux.svg.png) \
+ -prompt_file=<(echo "What is this guy's name?")
+```
diff --git a/one/main.go b/one/main.go
index fbf5046..d7a7a62 100644
--- a/one/main.go
+++ b/one/main.go
@@ -2,12 +2,15 @@ package main

// This binary runs one LLM inference on one input.

+import "encoding/base64"
import "flag"
import "fmt"
import "encoding/json"
import "io/ioutil"
import "log"
+import "mime"
import "os"
+import "path/filepath"
import "strings"

import "oscarkilo.com/klex-git/api"
@@ -16,8 +19,19 @@ import "oscarkilo.com/klex-git/config"
var model = flag.String("model", "", "overrides .Model, if non-empty")
var system = flag.String("system_file", "", "overrides .System, if non-empty")
var prompt = flag.String("prompt_file", "", "appends to .Messages")
+var image = flag.String("image_file", "", "attaches an image to the prompt")
var format = flag.String("format", "text", "text|json|jsonindent")

+// guessMimeType returns the MIME type inferred from a file path.
+func guessMimeType(fpath string) string {
+ ext := filepath.Ext(fpath) // extracts extension, including the dot
+ mimeType := mime.TypeByExtension(ext)
+ if mimeType == "" {
+ mimeType = "application/octet-stream" // fallback MIME type
+ }
+ return mimeType
+}
+
func main() {
flag.Parse()

@@ -56,17 +70,38 @@ func main() {
}
req.System = string(s)
}
+ if *image != "" && *prompt == "" {
+ log.Fatalf("--image_file requires a non-empty --prompt_file, too")
+ }
if *prompt != "" {
+ msg := api.ChatMessage{Role: "user"}
+ if *image != "" {
+ mime_type := guessMimeType(*image)
+ switch mime_type {
+ case "image/jpeg", "image/png", "image/gif", "image/webp":
+ default:
+ log.Fatal("Unsupported image type: %s", mime_type)
+ }
+ i, err := ioutil.ReadFile(*image)
+ if err != nil {
+ log.Fatalf("Failed to read --image_file %s: %v", *image, err)
+ }
+ msg.Content = append(msg.Content, api.ContentBlock{
+ Type: "image",
+ Source: &api.ContentSource{
+ Type: "base64",
+ MediaType: mime_type,
+ Data: base64.StdEncoding.EncodeToString(i),
+ },
+ })
+ }
p, err := ioutil.ReadFile(*prompt)
if err != nil {
log.Fatalf("Failed to read --prompt_file %s: %v", *prompt, err)
}
- req.Messages = append(req.Messages, api.ChatMessage{
- Role: "user",
- Content: []api.ContentBlock{{
- Type: "text",
- Text: string(p),
- }},
+ msg.Content = append(msg.Content, api.ContentBlock{
+ Type: "text",
+ Text: string(p),
})
}

a/one/README.md
b/one/README.md
1
## one: runs an LLM on one input
1
## one: runs an LLM on one input
2
2
3
Before running this script, create a `klex.json` file in the
3
Before running this script, create a `klex.json` file in the
4
root of your git repo, with these contents:
4
root of your git repo, with these contents:
5
5
6
```json
6
```json
7
{
7
{
8
"project_name": "**name**",
8
"project_name": "**name**",
9
"owner_username": "**your oscarkilo.com username**",
9
"owner_username": "**your oscarkilo.com username**",
10
"reader_username": "**your oscarkilo.com username**",
10
"reader_username": "**your oscarkilo.com username**",
11
"datasets_dir": "**optional**",
11
"datasets_dir": "**optional**",
12
"klex_url": "https://las.oscarkilo.com/klex",
12
"klex_url": "https://las.oscarkilo.com/klex",
13
"api_key_file": "klex.key"
13
"api_key_file": "klex.key"
14
}
14
}
15
```
15
```
16
16
17
Then put [your Klex API key](https://oscarkilo.com/login/profile) into
17
Then put [your Klex API key](https://oscarkilo.com/login/profile) into
18
the `klex.key` file.
18
the `klex.key` file.
19
19
20
Then run this command once:
20
Then run this command once:
21
21
22
```bash
22
```bash
23
go get oscarkilo.com/klex-git
23
go get oscarkilo.com/klex-git
24
```
24
```
25
25
26
Now you're ready for the examples below.
26
Now you're ready for the examples below.
27
27
28
### Hello World example
28
### Hello World example
29
29
30
```bash
30
```bash
31
go run oscarkilo.com/klex-git/one <<EOF
31
go run oscarkilo.com/klex-git/one <<EOF
32
{
32
{
33
"model": "Any big LLM",
33
"model": "Any big LLM",
34
"messages": [{
34
"messages": [{
35
"role": "user",
35
"role": "user",
36
"content": [{
36
"content": [{
37
"type": "text",
37
"type": "text",
38
"text": "Which band played Hotel California in Lebowski?"
38
"text": "Which band played Hotel California in Lebowski?"
39
}]
39
}]
40
}]
40
}]
41
}
41
}
42
42
43
EOF
43
EOF
44
```
44
```
45
45
46
### Reading prompts from files
46
### Reading prompts from files
47
47
48
```bash
48
```bash
49
echo "Tell me it's going to rain tomorrow." > prompt.txt
49
echo "Tell me it's going to rain tomorrow." > prompt.txt
50
echo "You're a folksy pirate; speak like it." > system.txt
50
echo "You're a folksy pirate; speak like it." > system.txt
51
echo "{}" | go run oscarkilo.com/klex-git/one \
51
echo "{}" | go run oscarkilo.com/klex-git/one \
52
-model=llama3 \
52
-model=llama3 \
53
-system_file=system.txt \
53
-system_file=system.txt \
54
-prompt_file=prompt.txt
54
-prompt_file=prompt.txt
55
rm prompt.txt system.txt
55
rm prompt.txt system.txt
56
```
56
```
57
58
### Analyzing images
59
60
```bash
61
echo "{}" | go run oscarkilo.com/klex-git/one \
62
-model=gpt-4o \
63
-image_file=<(curl -s https://upload.wikimedia.org/wikipedia/commons/thumb/3/35/Tux.svg/1024px-Tux.svg.png) \
64
-prompt_file=<(echo "What is this guy's name?")
65
```
a/one/main.go
b/one/main.go
1
package main
1
package main
2
2
3
// This binary runs one LLM inference on one input.
3
// This binary runs one LLM inference on one input.
4
4
5
import "encoding/base64"
5
import "flag"
6
import "flag"
6
import "fmt"
7
import "fmt"
7
import "encoding/json"
8
import "encoding/json"
8
import "io/ioutil"
9
import "io/ioutil"
9
import "log"
10
import "log"
11
import "mime"
10
import "os"
12
import "os"
13
import "path/filepath"
11
import "strings"
14
import "strings"
12
15
13
import "oscarkilo.com/klex-git/api"
16
import "oscarkilo.com/klex-git/api"
14
import "oscarkilo.com/klex-git/config"
17
import "oscarkilo.com/klex-git/config"
15
18
16
var model = flag.String("model", "", "overrides .Model, if non-empty")
19
var model = flag.String("model", "", "overrides .Model, if non-empty")
17
var system = flag.String("system_file", "", "overrides .System, if non-empty")
20
var system = flag.String("system_file", "", "overrides .System, if non-empty")
18
var prompt = flag.String("prompt_file", "", "appends to .Messages")
21
var prompt = flag.String("prompt_file", "", "appends to .Messages")
22
var image = flag.String("image_file", "", "attaches an image to the prompt")
19
var format = flag.String("format", "text", "text|json|jsonindent")
23
var format = flag.String("format", "text", "text|json|jsonindent")
20
24
25
// guessMimeType returns the MIME type inferred from a file path.
26
func guessMimeType(fpath string) string {
27
ext := filepath.Ext(fpath) // extracts extension, including the dot
28
mimeType := mime.TypeByExtension(ext)
29
if mimeType == "" {
30
mimeType = "application/octet-stream" // fallback MIME type
31
}
32
return mimeType
33
}
34
21
func main() {
35
func main() {
22
flag.Parse()
36
flag.Parse()
23
37
24
// Find the API keys and configure a Klex client.
38
// Find the API keys and configure a Klex client.
25
config, err := config.ReadConfig()
39
config, err := config.ReadConfig()
26
if err != nil {
40
if err != nil {
27
log.Fatalf("Failed to read config: %v", err)
41
log.Fatalf("Failed to read config: %v", err)
28
}
42
}
29
client := api.NewClient(config.KlexUrl, config.ApiKey)
43
client := api.NewClient(config.KlexUrl, config.ApiKey)
30
if client == nil {
44
if client == nil {
31
log.Fatalf("Failed to create Klex client")
45
log.Fatalf("Failed to create Klex client")
32
}
46
}
33
47
34
// Parse stdin as a MessagesRequest object, allowing empty input.
48
// Parse stdin as a MessagesRequest object, allowing empty input.
35
sin, err := ioutil.ReadAll(os.Stdin)
49
sin, err := ioutil.ReadAll(os.Stdin)
36
if err != nil {
50
if err != nil {
37
log.Fatalf("Failed to read stdin: %v", err)
51
log.Fatalf("Failed to read stdin: %v", err)
38
}
52
}
39
if len(sin) == 0 {
53
if len(sin) == 0 {
40
sin = []byte("{}")
54
sin = []byte("{}")
41
}
55
}
42
var req api.MessagesRequest
56
var req api.MessagesRequest
43
err = json.Unmarshal(sin, &req)
57
err = json.Unmarshal(sin, &req)
44
if err != nil {
58
if err != nil {
45
log.Fatalf("Failed to parse a MessagesRequest from stdin: %v", err)
59
log.Fatalf("Failed to parse a MessagesRequest from stdin: %v", err)
46
}
60
}
47
61
48
// Use flags to override parts of the request.
62
// Use flags to override parts of the request.
49
if *model != "" {
63
if *model != "" {
50
req.Model = *model
64
req.Model = *model
51
}
65
}
52
if *system != "" {
66
if *system != "" {
53
s, err := ioutil.ReadFile(*system)
67
s, err := ioutil.ReadFile(*system)
54
if err != nil {
68
if err != nil {
55
log.Fatalf("Failed to read --system_file %s: %v", *system, err)
69
log.Fatalf("Failed to read --system_file %s: %v", *system, err)
56
}
70
}
57
req.System = string(s)
71
req.System = string(s)
58
}
72
}
73
if *image != "" && *prompt == "" {
74
log.Fatalf("--image_file requires a non-empty --prompt_file, too")
75
}
59
if *prompt != "" {
76
if *prompt != "" {
77
msg := api.ChatMessage{Role: "user"}
78
if *image != "" {
79
mime_type := guessMimeType(*image)
80
switch mime_type {
81
case "image/jpeg", "image/png", "image/gif", "image/webp":
82
default:
83
log.Fatal("Unsupported image type: %s", mime_type)
84
}
85
i, err := ioutil.ReadFile(*image)
86
if err != nil {
87
log.Fatalf("Failed to read --image_file %s: %v", *image, err)
88
}
89
msg.Content = append(msg.Content, api.ContentBlock{
90
Type: "image",
91
Source: &api.ContentSource{
92
Type: "base64",
93
MediaType: mime_type,
94
Data: base64.StdEncoding.EncodeToString(i),
95
},
96
})
97
}
60
p, err := ioutil.ReadFile(*prompt)
98
p, err := ioutil.ReadFile(*prompt)
61
if err != nil {
99
if err != nil {
62
log.Fatalf("Failed to read --prompt_file %s: %v", *prompt, err)
100
log.Fatalf("Failed to read --prompt_file %s: %v", *prompt, err)
63
}
101
}
64
req.Messages = append(req.Messages, api.ChatMessage{
102
msg.Content = append(msg.Content, api.ContentBlock{
65
Role: "user",
103
Type: "text",
66
Content: []api.ContentBlock{{
104
Text: string(p),
67
Type: "text",
68
Text: string(p),
69
}},
70
})
105
})
71
}
106
}
72
107
73
// Get LLM output from Klex.
108
// Get LLM output from Klex.
74
res, err := client.Messages(req)
109
res, err := client.Messages(req)
75
if err != nil {
110
if err != nil {
76
log.Fatalf("Klex f() failure: %v", err)
111
log.Fatalf("Klex f() failure: %v", err)
77
}
112
}
78
113
79
// Print according to the --format flag.
114
// Print according to the --format flag.
80
out, err := formatResponse(res)
115
out, err := formatResponse(res)
81
if err != nil {
116
if err != nil {
82
log.Fatalf("Failed to format response: %v", err)
117
log.Fatalf("Failed to format response: %v", err)
83
}
118
}
84
fmt.Print(out)
119
fmt.Print(out)
85
}
120
}
86
121
87
func formatResponse(res *api.MessagesResponse) (string, error) {
122
func formatResponse(res *api.MessagesResponse) (string, error) {
88
switch *format {
123
switch *format {
89
case "text":
124
case "text":
90
var content []string
125
var content []string
91
for _, c := range res.Content {
126
for _, c := range res.Content {
92
if c.Type == "text" {
127
if c.Type == "text" {
93
content = append(content, c.Text + "\n")
128
content = append(content, c.Text + "\n")
94
}
129
}
95
}
130
}
96
return strings.Join(content, "\n"), nil
131
return strings.Join(content, "\n"), nil
97
case "json":
132
case "json":
98
buf, err := json.Marshal(res)
133
buf, err := json.Marshal(res)
99
return string(buf), err
134
return string(buf), err
100
case "jsonindent":
135
case "jsonindent":
101
buf, err := json.MarshalIndent(res, "", " ")
136
buf, err := json.MarshalIndent(res, "", " ")
102
return string(buf), err
137
return string(buf), err
103
default:
138
default:
104
return "", fmt.Errorf("Unsupported --format=%s", *format)
139
return "", fmt.Errorf("Unsupported --format=%s", *format)
105
}
140
}
106
}
141
}