Skip to content

Commit ce58fd2

Browse files
authored
Enhance command interface (#2)
* Generic reasoner for commands (single and sequence) * Support Python command * Support Golan * Support language commands via <codeblock>
1 parent c65af77 commit ce58fd2

File tree

15 files changed

+671
-19
lines changed

15 files changed

+671
-19
lines changed

README.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ In this library, an agent is defined as a side-effect function `ƒ: A ⟼ B`, wh
4949
- [Quick example](#quick-example)
5050
- [Agent Architecture](#agent-architecture)
5151
- [Commands \& Tools](#commands--tools)
52+
- [Supported commands](#supported-commands)
5253
- [Chaining agents](#chaining-agents)
5354
- [How To Contribute](#how-to-contribute)
5455
- [commit message](#commit-message)
@@ -192,6 +193,10 @@ When constructing a prompt, it is essential to include a section that "advertise
192193

193194
The [script example](./examples/script/script.go) demonstrates a simple agent that utilizes `bash` to generate and modify files on the local filesystem.
194195

196+
#### Supported commands
197+
* `bash` execute bash script or single command
198+
* `golang` execute golang code block
199+
* `python` execute python code block
195200

196201
### Chaining agents
197202

command/bash.go

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ package command
1111
import (
1212
"bytes"
1313
"fmt"
14+
"os"
1415
"os/exec"
1516

1617
"github.com/kshard/chatter"
@@ -24,15 +25,40 @@ const BASH = "bash"
2425
func Bash(os, dir string) thinker.Cmd {
2526
return thinker.Cmd{
2627
Cmd: BASH,
27-
Short: fmt.Sprintf("executes shell command, strictly adhere shell command syntaxt to %s", os),
28-
Syntax: "bash <command>",
28+
Short: fmt.Sprintf("executes shell command, strictly adhere shell command syntaxt to %s. Enclose the bash commands in <codeblock> tags.", os),
29+
Syntax: "bash <codeblock>source code</codeblock>",
2930
Run: bash(os, dir),
3031
}
3132
}
3233

34+
func shfile(dir, code string) (string, error) {
35+
fd, err := os.CreateTemp(dir, "job-*.sh")
36+
if err != nil {
37+
return "", err
38+
}
39+
defer fd.Close()
40+
41+
_, err = fd.WriteString(code)
42+
if err != nil {
43+
return "", err
44+
}
45+
46+
return fd.Name(), nil
47+
}
48+
3349
func bash(os string, dir string) func(chatter.Reply) (float64, thinker.CmdOut, error) {
3450
return func(command chatter.Reply) (float64, thinker.CmdOut, error) {
35-
cmd := exec.Command("bash", "-c", command.Text)
51+
code, err := CodeBlock(BASH, command.Text)
52+
if err != nil {
53+
return 0.00, thinker.CmdOut{Cmd: BASH}, err
54+
}
55+
56+
file, err := shfile(dir, code)
57+
if err != nil {
58+
return 0.00, thinker.CmdOut{Cmd: BASH}, err
59+
}
60+
61+
cmd := exec.Command("bash", file)
3662
var stdout bytes.Buffer
3763
var stderr bytes.Buffer
3864
cmd.Dir = dir

command/bash_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,8 @@ import (
1616
)
1717

1818
func TestBash(t *testing.T) {
19-
cmd := Bash("", "")
20-
conf, out, err := cmd.Run(chatter.Reply{Text: "ls"})
19+
cmd := Bash("", "/tmp")
20+
conf, out, err := cmd.Run(chatter.Reply{Text: "<codeblock>ls</codeblock>"})
2121

2222
it.Then(t).Should(
2323
it.Nil(err),

command/codeblock.go

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
//
2+
// Copyright (C) 2025 Dmitry Kolesnikov
3+
//
4+
// This file may be modified and distributed under the terms
5+
// of the MIT license. See the LICENSE file for details.
6+
// https://github.com/kshard/thinker
7+
//
8+
9+
package command
10+
11+
import (
12+
"fmt"
13+
"strings"
14+
15+
"github.com/kshard/thinker"
16+
)
17+
18+
func CodeBlock(tool string, str string) (string, error) {
19+
const btag = "<codeblock>"
20+
const etag = "</codeblock>"
21+
22+
b := strings.Index(str, btag)
23+
e := strings.Index(str, etag)
24+
if b == -1 || e == -1 || b >= e {
25+
err := thinker.Feedback(
26+
fmt.Sprintf("The TOOL:%s has failed, improve the response based on feedback:", tool),
27+
28+
fmt.Sprintf(`Strictly adhere %s code formatting and enclose the %s code in <codeblock> tags`, tool, tool),
29+
)
30+
return "", err
31+
}
32+
33+
code := str[b+len(btag) : e]
34+
return code, nil
35+
}

command/codeblock_test.go

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
//
2+
// Copyright (C) 2025 Dmitry Kolesnikov
3+
//
4+
// This file may be modified and distributed under the terms
5+
// of the MIT license. See the LICENSE file for details.
6+
// https://github.com/kshard/thinker
7+
//
8+
9+
package command
10+
11+
import (
12+
"testing"
13+
14+
"github.com/fogfish/it/v2"
15+
)
16+
17+
func TestCodeBlock(t *testing.T) {
18+
t.Run("Success", func(t *testing.T) {
19+
for in, ex := range map[string]string{
20+
"<codeblock>a</codeblock>": "a",
21+
"xxx<codeblock>a</codeblock>": "a",
22+
"<codeblock>a</codeblock>xxx": "a",
23+
"xxx<codeblock>a</codeblock>xxx": "a",
24+
} {
25+
code, err := CodeBlock(BASH, in)
26+
it.Then(t).Should(
27+
it.Nil(err),
28+
it.Equal(code, ex),
29+
)
30+
}
31+
})
32+
33+
t.Run("Failed", func(t *testing.T) {
34+
for _, in := range []string{
35+
"<codeblock>a",
36+
"a</codeblock>",
37+
"</codeblock>a<codeblock>",
38+
"xxxcodeblock>a</codeblock>",
39+
"<codeblock>a<codeblock>xxx",
40+
"xxx<codeblock>acodeblockxxx",
41+
} {
42+
_, err := CodeBlock(BASH, in)
43+
it.Then(t).ShouldNot(
44+
it.Nil(err),
45+
)
46+
}
47+
})
48+
}

command/golang.go

Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
//
2+
// Copyright (C) 2025 Dmitry Kolesnikov
3+
//
4+
// This file may be modified and distributed under the terms
5+
// of the MIT license. See the LICENSE file for details.
6+
// https://github.com/kshard/thinker
7+
//
8+
9+
package command
10+
11+
import (
12+
"bytes"
13+
"fmt"
14+
"os"
15+
"os/exec"
16+
"path/filepath"
17+
18+
"github.com/kshard/chatter"
19+
"github.com/kshard/thinker"
20+
)
21+
22+
// A unique name for bash (the shell)
23+
const GOLANG = "go"
24+
25+
// Create new Golang command, defining goroot
26+
func Golang(gopath string) thinker.Cmd {
27+
return thinker.Cmd{
28+
Cmd: GOLANG,
29+
Short: "Use golang to execute scripts (package main) that help you complete your task. Enclose the golang code in <codeblock> tags.",
30+
Syntax: `go <codeblock>source code</codeblock>`,
31+
Run: golang(gopath),
32+
}
33+
}
34+
35+
func goSourceCode(gopath string) string {
36+
return filepath.Join(gopath, "src", "github.com", "kshard", "jobs")
37+
}
38+
39+
func goSetup(gopath string) error {
40+
gojob := goSourceCode(gopath)
41+
if err := os.MkdirAll(gojob, 0755); err != nil {
42+
return err
43+
}
44+
45+
gomod := filepath.Join(gojob, "go.mod")
46+
47+
if _, err := os.Stat(gomod); err != nil {
48+
setup := exec.Command("go", "mod", "init")
49+
setup.Dir = gojob
50+
setup.Env = []string{
51+
"GOPATH=" + gopath,
52+
}
53+
54+
_, err := setup.Output()
55+
if err != nil {
56+
return err
57+
}
58+
}
59+
60+
return nil
61+
}
62+
63+
func goDeps(gopath string) error {
64+
gojob := goSourceCode(gopath)
65+
66+
deps := exec.Command("go", "mod", "tidy")
67+
deps.Dir = gojob
68+
deps.Env = []string{
69+
"GOPATH=" + gopath,
70+
}
71+
72+
_, err := deps.Output()
73+
if err != nil {
74+
return err
75+
}
76+
77+
return nil
78+
}
79+
80+
func gofile(gopath, code string) (string, error) {
81+
gojob := goSourceCode(gopath)
82+
dir, err := os.MkdirTemp(gojob, "job*")
83+
if err != nil {
84+
return "", err
85+
}
86+
87+
fd, err := os.Create(filepath.Join(dir, "main.go"))
88+
if err != nil {
89+
return "", err
90+
}
91+
defer fd.Close()
92+
93+
_, err = fd.WriteString(code)
94+
if err != nil {
95+
return "", err
96+
}
97+
98+
return filepath.Join(filepath.Base(dir), "main.go"), nil
99+
}
100+
101+
func golang(gopath string) func(chatter.Reply) (float64, thinker.CmdOut, error) {
102+
return func(command chatter.Reply) (float64, thinker.CmdOut, error) {
103+
if err := goSetup(gopath); err != nil {
104+
return 0.0, thinker.CmdOut{}, err
105+
}
106+
107+
code, err := CodeBlock(GOLANG, command.Text)
108+
if err != nil {
109+
return 0.00, thinker.CmdOut{Cmd: GOLANG}, err
110+
}
111+
112+
file, err := gofile(gopath, code)
113+
if err != nil {
114+
return 0.00, thinker.CmdOut{Cmd: GOLANG}, err
115+
}
116+
117+
if err := goDeps(gopath); err != nil {
118+
return 0.0, thinker.CmdOut{}, err
119+
}
120+
121+
cmd := exec.Command("go", "run", file)
122+
var stdout bytes.Buffer
123+
var stderr bytes.Buffer
124+
cmd.Dir = goSourceCode(gopath)
125+
cmd.Env = []string{
126+
"GOPATH=" + gopath,
127+
"GOCACHE=" + filepath.Join(gopath, "cache"),
128+
}
129+
cmd.Stdout = &stdout
130+
cmd.Stderr = &stderr
131+
132+
if err := cmd.Run(); err != nil {
133+
err = thinker.Feedback(
134+
fmt.Sprintf("The TOOL:%s has failed, improve the response based on feedback:", GOLANG),
135+
136+
"Execution of golang program is failed with the error: "+err.Error(),
137+
"The error output is "+stderr.String(),
138+
)
139+
return 0.05, thinker.CmdOut{Cmd: GOLANG}, err
140+
}
141+
142+
return 1.0, thinker.CmdOut{Cmd: GOLANG, Output: stdout.String()}, nil
143+
}
144+
}

command/golang_test.go

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
//
2+
// Copyright (C) 2025 Dmitry Kolesnikov
3+
//
4+
// This file may be modified and distributed under the terms
5+
// of the MIT license. See the LICENSE file for details.
6+
// https://github.com/kshard/thinker
7+
//
8+
9+
package command
10+
11+
import (
12+
"testing"
13+
14+
"github.com/fogfish/it/v2"
15+
"github.com/kshard/chatter"
16+
)
17+
18+
func TestGolang(t *testing.T) {
19+
cmd := Golang("/tmp")
20+
conf, out, err := cmd.Run(chatter.Reply{Text: `<codeblock>
21+
package main
22+
23+
import "fmt"
24+
25+
func main() {
26+
fmt.Println("response")
27+
}
28+
</codeblock>
29+
`})
30+
31+
it.Then(t).Should(
32+
it.Nil(err),
33+
it.Equal(conf, 1.0),
34+
it.Equal(out.Cmd, cmd.Cmd),
35+
it.String(out.Output).Contain("response"),
36+
)
37+
}

0 commit comments

Comments
 (0)