1
# Klex Git utilities
1
# Klex Git utilities
2
2
3
With the help of these tools, you can create a Git repository that relies
3
With the help of these tools, you can create a Git repository that relies
4
on Klex for data processing and AI services.
4
on Klex for data processing and AI services.
5
5
6
## Example use case and tutorial
6
## Example use case and tutorial
7
7
8
1. Create a new Git repository.
8
1. Create a new Git repository.
9
```bash
9
```bash
10
mkdir goodevil
10
mkdir goodevil
11
cd goodevil
11
cd goodevil
12
git init
12
git init
13
```
13
```
14
14
15
2. Create a dataset containing a book split into paragraphs.
15
2. Create a dataset containing a book split into paragraphs.
16
```bash
16
```bash
17
mkdir datasets
17
mkdir datasets
18
cd datasets
18
cd datasets
19
wget https://gutenberg.org/cache/epub/4363/pg4363.txt -O book.txt
19
wget https://gutenberg.org/cache/epub/4363/pg4363.txt -O book.txt
20
mkdir paragraphs
20
mkdir paragraphs
21
for i in {1..296}
21
for i in {1..296}
22
do
22
do
23
awk `/^$i\. /{f=1} f; /^$((i+1))\. / && f{exit}' book.txt > paragraphs/$i.txt
23
awk `/^$i\. /{f=1} f; /^$((i+1))\. / && f{exit}' book.txt > paragraphs/$i.txt
24
done
24
done
25
rm book.txt
25
rm book.txt
26
cd ..
26
cd ..
27
```
27
```
28
At this point, you should have a directory called data/paragraphs/ with 296
28
At this point, you should have a directory called data/paragraphs/ with 296
29
file in it, each containing a paragraph from the book. See 256.txt for an
29
file in it, each containing a paragraph from the book. See 256.txt for an
30
example of an edge case.
30
example of an edge case.
31
31
32
3. Create a JavaScript function that generates LLM prompts.
32
3. Create a JavaScript function that generates LLM prompts.
33
```bash
33
```bash
34
mkdir functions
34
mkdir functions
35
cat <<EOF > functions/tag.js
35
cat <<EOF > functions/tag.js
36
function(txt) {
36
function(txt) {
37
return "Produce a list of useful search words for the following" +
37
return "Produce a list of useful search words for the following" +
38
" paragraph of text. Present the list in lower case, separated by" +
38
" paragraph of text. Present the list in lower case, separated by" +
39
" commas, prefixed with the heading \"Tags: \", and ending in a" +
39
" commas, prefixed with the heading \"Tags: \", and ending in a" +
40
" period. For example, if the paragraph talks primarily about" +
40
" period. For example, if the paragraph talks primarily about" +
41
" the way teachers view their profession, output this line:\n" +
41
" the way teachers view their profession, output this line:\n" +
42
" Tags: teachers, teaching, profession.\n\n" +
42
" Tags: teachers, teaching, profession.\n\n" +
43
" Paragraphs: " + txt.replace(/^\d+[.] +/, "");
43
" Paragraphs: " + txt.replace(/^\d+[.] +/, "");
44
}
44
}
45
EOF
45
EOF
46
```
46
```
47
4. Create a JavaScript pipeline that tags each paragraph.
47
4. Create a JavaScript pipeline that tags each paragraph.
48
```bash
48
```bash
49
mkdir pipelines
49
mkdir pipelines
50
cat <<EOF > pipelines/tag.js
50
cat <<EOF > pipelines/tag.js
51
function(api) {
51
function(api) {
52
return api.dataset("paragraphs")
52
return api.dataset("paragraphs")
53
.map("tag")
53
.map("tag")
54
.map("llama3")
54
.map("llama3")
55
.name("tagged");
55
.name("tagged");
56
}
56
}
57
EOF
57
EOF
58
```
58
```
59
59
60
5. Create a Klex config file.
60
5. Create a Klex config file.
61
```bash
61
```bash
62
cat <<EOF > klex.json
62
cat <<EOF > klex.json
63
{
63
{
64
"project_name": "goodevil",
64
"project_name": "goodevil",
65
"datasets_dir": "datasets",
65
"datasets_dir": "datasets",
66
"functions_dir": "functions",
66
"functions_dir": "functions",
67
"pipelines_dir": "pipelines",
67
"pipelines_dir": "pipelines",
68
"klex_url": "https://dr.oscarkilo.com/klex",
68
"klex_url": "https://dr.oscarkilo.com/klex",
69
"api_key_file": "klex.api_key"
69
"api_key_file": "klex.api_key"
70
}
70
}
71
EOF
71
EOF
72
```
72
```
73
73
74
6. Get an API key [here](https://oscarkilo.com/login/profile) and save it to
74
6. Get an API key [here](https://oscarkilo.com/login/profile) and save it to
75
a file.
75
a file.
76
```bash
76
```bash
77
echo "YOUR_API_KEY" > klex.api_key
77
echo "YOUR_API_KEY" > klex.api_key
78
echo "klex.api_key" > .gitignore
78
echo "klex.api_key" > .gitignore
79
```
79
```
80
80
81
7. Add Klex Git utilities and hooks to your repository.
81
7. Add Klex Git utilities and hooks to your repository.
82
First, clone this repository to the ../klex-git directory:
82
First, clone this repository to the ../klex-git directory:
83
```bash
83
```bash
84
cd ..
84
cd ..
85
git clone https://code.oscarkilo.com/klex-git
85
git clone https://code.oscarkilo.com/klex-git
86
cd goodevil
86
cd goodevil
87
```
87
```
88
Then,
88
Then,
89
```bash
89
```bash
90
cat <<EOF > go.mod
90
cat <<EOF > go.mod
91
module goodevil
91
module goodevil
92
go 1.21
92
go 1.21
93
toolchain go1.21.3
93
toolchain go1.21.3
94
replace oscarkilo.com/klex-git => ../klex-git
94
replace oscarkilo.com/klex-git => ../klex-git
95
EOF
95
EOF
96
go get oscarkilo.com/klex-git
96
go get oscarkilo.com/klex-git
97
go run oscarkilo.com/klex-git/install
97
go run oscarkilo.com/klex-git/install
98
```
98
```
99
8. Commit your changes.
99
8. Commit your changes.
100
```bash
100
```bash
101
git add .
101
git add .
102
git commit -m "First!"
102
git commit -m "First!"
103
```
103
```
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, ths conn:
4
root of your git repo, ths conn:
5
5
6
```
6
```
7
{
8
"project_name": "**name**",
9
"owner_username": "**your oscarkilo.com username**",
10
"reader_username": "**your oscarkilo.com username**",
11
"datasets_dir": "**optional**",
12
"klex_url": "https://las.oscarkilo.com/klex",
13
"api_key_file": "klex.key"
14
}
15
```
16
17
Then put [your Klex API key](https://oscarkilo.com/login/profile) into
18
the `klex.key` file.
19
20
Then run this command once:
21
22
```bash
7
go get oscarkilo.com/klex-git
23
go get oscarkilo.com/klex-git
8
```
24
```
9
25
10
o or example
26
o or example
27
28
### Hello World example
11
29
12
```bash
30
```bash
13
go run oscarkilo.com/klex-git/one <<EOF
31
go run oscarkilo.com/klex-git/one <<EOF
14
{
32
{
15
"model": "Any big LLM",
33
"model": "Any big LLM",
16
"messages": [{
34
"messages": [{
17
"role": "user",
35
"role": "user",
18
"content": [{
36
"content": [{
19
"type": "text",
37
"type": "text",
20
"text": "Wh played Hotel California in Lebowski?"
38
"text": "Wh played Hotel California in Lebowski?"
21
}]
39
}]
22
}]
40
}]
23
}
41
}
42
24
EOF
43
EOF
25
```
44
```
45
46
### Reading prompts from files
47
48
```bash
49
echo "Tell me it's going to rain tomorrow." > prompt.txt
50
echo "You're a folksy pirate; speak like it." > system.txt
51
go run oscarkilo.com/klex-git/one \
52
--model=gpt-4o \
53
--system=system.txt \
54
--prompt=prompt.txt
55
```
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 "flag"
5
import "flag"
6
import "fmt"
6
import "fmt"
7
import "encoding/json"
7
import "encoding/json"
8
import "io/ioutil"
8
import "io/ioutil"
9
import "log"
9
import "log"
10
import "os"
10
import "os"
11
import "strings"
11
import "strings"
12
12
13
import "oscarkilo.com/klex-git/api"
13
import "oscarkilo.com/klex-git/api"
14
import "oscarkilo.com/klex-git/config"
14
import "oscarkilo.com/klex-git/config"
15
15
16
var model = flag.String("model", "", "overrides .Model, if non-empty")
16
var model = flag.String("model", "", "overrides .Model, if non-empty")
17
var system = flag.String("system_file", "", "overrides .System, if non-empty")
17
var system = flag.String("system_file", "", "overrides .System, if non-empty")
18
var prompt = flag.String("prompt_file", "", "appends to .Messages")
18
var prompt = flag.String("prompt_file", "", "appends to .Messages")
19
var format = flag.String("format", "text", "text|json|jsonindent")
19
var format = flag.String("format", "text", "text|json|jsonindent")
20
20
21
func main() {
21
func main() {
22
// Find the API keys and configure a Klex client.
22
config, err := config.ReadConfig()
23
config, err := config.ReadConfig()
23
if err != nil {
24
if err != nil {
24
log.Fatalf("Failed to read config: %v", err)
25
log.Fatalf("Failed to read config: %v", err)
25
}
26
}
26
client := api.NewClient(config.KlexUrl, config.ApiKey)
27
client := api.NewClient(config.KlexUrl, config.ApiKey)
27
if client == nil {
28
if client == nil {
28
log.Fatalf("Failed to create Klex client")
29
log.Fatalf("Failed to create Klex client")
29
}
30
}
30
31
32
// Parse stdin as a MessagesRequest object, allowing empty input.
33
sin, err := ioutil.ReadAll(os.Stdin)
34
if err != nil {
35
log.Fatalf("Failed to read stdin: %v", err)
36
}
37
if len(sin) == 0 {
38
sin = []byte("{}")
39
}
31
var req api.MessagesRequest
40
var req api.MessagesRequest
32
err = json.(sin&req)
41
err = json.(sin&req)
33
if err != nil {
42
if err != nil {
34
log.Fatalf("Failed to parse a MessagesRequest from stdin: %v", err)
43
log.Fatalf("Failed to parse a MessagesRequest from stdin: %v", err)
35
}
44
}
36
45
46
// Use flags to override parts of the request.
37
if *model != "" {
47
if *model != "" {
38
req.Model = *model
48
req.Model = *model
39
}
49
}
40
if *system != "" {
50
if *system != "" {
41
s, err := ioutil.ReadFile(*system)
51
s, err := ioutil.ReadFile(*system)
42
if err != nil {
52
if err != nil {
43
log.Fatalf("Failed to read --system_file %s: %v", *system, err)
53
log.Fatalf("Failed to read --system_file %s: %v", *system, err)
44
}
54
}
45
req.System = string(s)
55
req.System = string(s)
46
}
56
}
47
if *prompt != "" {
57
if *prompt != "" {
48
p, err := ioutil.ReadFile(*prompt)
58
p, err := ioutil.ReadFile(*prompt)
49
if err != nil {
59
if err != nil {
50
log.Fatalf("Failed to read --prompt_file %s: %v", *prompt, err)
60
log.Fatalf("Failed to read --prompt_file %s: %v", *prompt, err)
51
}
61
}
52
req.Messages = append(req.Messages, api.ChatMessage{
62
req.Messages = append(req.Messages, api.ChatMessage{
53
Role: "user",
63
Role: "user",
54
Content: []api.ContentBlock{{
64
Content: []api.ContentBlock{{
55
Type: "text",
65
Type: "text",
56
Text: string(p),
66
Text: string(p),
57
}},
67
}},
58
})
68
})
59
}
69
}
60
70
71
// Get LLM output from Klex.
61
res, err := client.Messages(req)
72
res, err := client.Messages(req)
62
if err != nil {
73
if err != nil {
63
log.Fatalf("Klex f() failure: %v", err)
74
log.Fatalf("Klex f() failure: %v", err)
64
}
75
}
65
76
77
// Print according to the --format flag.
66
out, err := formatResponse(res)
78
out, err := formatResponse(res)
67
if err != nil {
79
if err != nil {
68
log.Fatalf("Failed to format response: %v", err)
80
log.Fatalf("Failed to format response: %v", err)
69
}
81
}
70
fmt.Print(out)
82
fmt.Print(out)
71
}
83
}
72
84
73
func formatResponse(res *api.MessagesResponse) (string, error) {
85
func formatResponse(res *api.MessagesResponse) (string, error) {
74
switch *format {
86
switch *format {
75
case "text":
87
case "text":
76
var content []string
88
var content []string
77
for _, c := range res.Content {
89
for _, c := range res.Content {
78
if c.Type == "text" {
90
if c.Type == "text" {
79
content = append(content, c.Text + "\n")
91
content = append(content, c.Text + "\n")
80
}
92
}
81
}
93
}
82
return strings.Join(content, "\n"), nil
94
return strings.Join(content, "\n"), nil
83
case "json":
95
case "json":
84
buf, err := json.Marshal(res)
96
buf, err := json.Marshal(res)
85
return string(buf), err
97
return string(buf), err
86
case "jsonindent":
98
case "jsonindent":
87
buf, err := json.MarshalIndent(res, "", " ")
99
buf, err := json.MarshalIndent(res, "", " ")
88
return string(buf), err
100
return string(buf), err
89
default:
101
default:
90
return "", fmt.Errorf("Unsupported --format=%s", *format)
102
return "", fmt.Errorf("Unsupported --format=%s", *format)
91
}
103
}
92
}
104
}