Skip to content

Commit d1f79b9

Browse files
committed
refactor: an agent that can use tools to find information in Go codebases
1 parent 8840643 commit d1f79b9

15 files changed

Lines changed: 696 additions & 125 deletions

File tree

agents/agents.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,9 @@ import (
1111
"errors"
1212
"fmt"
1313

14-
"github.com/bit8bytes/gogantic/agents/tools"
1514
"github.com/bit8bytes/gogantic/inputs/roles"
1615
"github.com/bit8bytes/gogantic/llms"
16+
"github.com/bit8bytes/gogantic/tools"
1717
)
1818

1919
type llm interface {

agents/react.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,9 @@ Respond with a JSON object on each turn with these fields:
5050
- "thought": your reasoning about what to do next
5151
- "action": the exact tool name to call (empty string when giving final answer)
5252
- "action_input": the input to pass to the tool (empty string when giving final answer)
53-
- "final_answer": your final answer (empty string when calling a tool)
53+
- "final_answer": your final answer to the user — MUST be non-empty when you are done; empty string ONLY when calling a tool
54+
55+
When you have enough information to answer, set "action" and "action_input" to "" and put a detailed answer based on your observations — MUST be non-empty when done; be thorough and include all relevant findings.
5456
5557
Think step by step. Do not hallucinate.`, toolDescriptions.String(), jsonInstructions),
5658
},

agents/tools/tools.go

Lines changed: 0 additions & 9 deletions
This file was deleted.

examples/agents/ollama/grep/grep.go

Lines changed: 0 additions & 44 deletions
This file was deleted.

examples/agents/ollama/grep/main.go

Lines changed: 0 additions & 70 deletions
This file was deleted.

examples/agents/ollama/main.go

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
package main
2+
3+
import (
4+
"context"
5+
"database/sql"
6+
"fmt"
7+
"time"
8+
9+
"github.com/bit8bytes/gogantic/agents"
10+
"github.com/bit8bytes/gogantic/llms/ollama"
11+
"github.com/bit8bytes/gogantic/runner"
12+
"github.com/bit8bytes/gogantic/stores/moderncsqlite"
13+
"github.com/bit8bytes/gogantic/tools"
14+
)
15+
16+
func setupDatabase(ctx context.Context) (*sql.DB, error) {
17+
db, err := sql.Open("sqlite", "agent.db")
18+
if err != nil {
19+
return nil, err
20+
}
21+
22+
if err := db.PingContext(ctx); err != nil {
23+
return nil, err
24+
}
25+
26+
return db, nil
27+
}
28+
29+
func migrateDatabase(ctx context.Context, db *sql.DB) error {
30+
_, err := db.ExecContext(ctx, `
31+
CREATE TABLE IF NOT EXISTS messages (
32+
id INTEGER PRIMARY KEY AUTOINCREMENT,
33+
role TEXT NOT NULL,
34+
content TEXT NOT NULL,
35+
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
36+
);`)
37+
return err
38+
}
39+
40+
func main() {
41+
ctx, cancel := context.WithTimeout(context.Background(), time.Second*120)
42+
defer cancel()
43+
44+
// Omitting errors for example sake.
45+
db, _ := setupDatabase(ctx)
46+
defer db.Close()
47+
48+
_ = migrateDatabase(ctx, db)
49+
50+
storage, _ := moderncsqlite.New(ctx, db)
51+
defer storage.Close()
52+
53+
model := ollama.New(ollama.Model{
54+
Model: "gemma3:12b",
55+
Options: ollama.Options{NumCtx: 4096},
56+
Stream: false,
57+
Format: ollama.JSON,
58+
})
59+
60+
// These tools are specifically designed for Golang.
61+
tools := []agents.Tool{
62+
tools.ListGoPackages{},
63+
tools.ParseGoFile{},
64+
tools.FindIdent{},
65+
tools.FindCalls{},
66+
tools.FindImporters{},
67+
tools.FindImplementors{},
68+
}
69+
70+
agent, err := agents.NewReAct(ctx, model, tools, storage)
71+
if err != nil {
72+
panic(err)
73+
}
74+
75+
task := `
76+
Find all types in this codebase that implement the parser interface.
77+
For each type, show which package and file it is defined in.`
78+
if err := agent.Task(ctx, task); err != nil {
79+
panic(err)
80+
}
81+
82+
r := runner.New(agent, true)
83+
if err := r.Run(ctx); err != nil {
84+
panic(err)
85+
}
86+
87+
finalAnswer, err := agent.Answer()
88+
if err != nil {
89+
panic(err)
90+
}
91+
fmt.Println(finalAnswer)
92+
}

llms/ollama/ollama_types.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,10 @@ import (
44
"github.com/bit8bytes/gogantic/llms"
55
)
66

7+
const (
8+
JSON = "json"
9+
)
10+
711
type Model struct {
812
Model string `json:"model"`
913
Endpoint string `json:"endpoint"`

tools/ast.go

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
// NOTE: This file was generated by AI and needs to be evaluated and refactored before production use.
2+
package tools
3+
4+
import (
5+
"go/ast"
6+
"go/parser"
7+
"go/token"
8+
"io/fs"
9+
"path/filepath"
10+
"strings"
11+
)
12+
13+
func walkGoFiles(dir string, fset *token.FileSet) ([]*ast.File, error) {
14+
var files []*ast.File
15+
err := filepath.WalkDir(dir, func(path string, d fs.DirEntry, err error) error {
16+
if err != nil || d.IsDir() || filepath.Ext(path) != ".go" {
17+
return nil
18+
}
19+
f, parseErr := parser.ParseFile(fset, path, nil, 0)
20+
if parseErr != nil {
21+
return nil
22+
}
23+
files = append(files, f)
24+
return nil
25+
})
26+
return files, err
27+
}
28+
29+
func twoLines(s string) (first, second string, ok bool) {
30+
parts := strings.SplitN(strings.TrimSpace(s), "\n", 2)
31+
if len(parts) != 2 {
32+
return "", "", false
33+
}
34+
return strings.TrimSpace(parts[0]), strings.TrimSpace(parts[1]), true
35+
}

tools/findCalls.go

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
// NOTE: This file was generated by AI and needs to be evaluated and refactored before production use.
2+
package tools
3+
4+
import (
5+
"context"
6+
"fmt"
7+
"go/ast"
8+
"go/token"
9+
"strings"
10+
)
11+
12+
type FindCalls struct{}
13+
14+
func (t FindCalls) Name() string { return "FindCalls" }
15+
16+
func (t FindCalls) Description() string {
17+
return `Find all call sites of a function by name in a Go codebase.
18+
Matches both direct calls (f()) and method calls (obj.f()).
19+
Input: two lines — directory path, then function name (e.g. 'Println').`
20+
}
21+
22+
func (t FindCalls) Execute(ctx context.Context, input Input) (Output, error) {
23+
dir, funcName, ok := twoLines(input.Content)
24+
if !ok {
25+
return Output{Content: "Error: provide directory and function name on separate lines"}, nil
26+
}
27+
28+
fset := token.NewFileSet()
29+
files, err := walkGoFiles(dir, fset)
30+
if err != nil {
31+
return Output{Content: "Error: " + err.Error()}, nil
32+
}
33+
34+
var b strings.Builder
35+
found := 0
36+
for _, f := range files {
37+
ast.Inspect(f, func(n ast.Node) bool {
38+
call, ok := n.(*ast.CallExpr)
39+
if !ok {
40+
return true
41+
}
42+
if callFuncName(call.Fun) == funcName {
43+
pos := fset.Position(call.Pos())
44+
fmt.Fprintf(&b, "%s:%d\n", pos.Filename, pos.Line)
45+
found++
46+
}
47+
return true
48+
})
49+
}
50+
51+
if found == 0 {
52+
return Output{Content: "not found"}, nil
53+
}
54+
return Output{Content: b.String()}, nil
55+
}
56+
57+
func callFuncName(expr ast.Expr) string {
58+
switch e := expr.(type) {
59+
case *ast.Ident:
60+
return e.Name
61+
case *ast.SelectorExpr:
62+
return e.Sel.Name
63+
default:
64+
return ""
65+
}
66+
}

0 commit comments

Comments
 (0)