summaryrefslogtreecommitdiff
path: root/examples/simple-agent-go
diff options
context:
space:
mode:
Diffstat (limited to 'examples/simple-agent-go')
-rw-r--r--examples/simple-agent-go/go.mod7
-rw-r--r--examples/simple-agent-go/main.go152
2 files changed, 159 insertions, 0 deletions
diff --git a/examples/simple-agent-go/go.mod b/examples/simple-agent-go/go.mod
new file mode 100644
index 0000000..376a3c9
--- /dev/null
+++ b/examples/simple-agent-go/go.mod
@@ -0,0 +1,7 @@
+module simple-agent-go
+
+go 1.24
+
+require code.tjp.lol/libpantograph.git/libpanto-go v0.0.0
+
+replace code.tjp.lol/libpantograph.git/libpanto-go => ../../libpanto-go
diff --git a/examples/simple-agent-go/main.go b/examples/simple-agent-go/main.go
new file mode 100644
index 0000000..8a02142
--- /dev/null
+++ b/examples/simple-agent-go/main.go
@@ -0,0 +1,152 @@
+// A tiny standalone libpanto-go app: one prompt in, streamed text out.
+//
+// Run with:
+//
+// PANTO_API_STYLE=openai_chat|anthropic_messages \
+// PANTO_API_KEY=... \
+// PANTO_BASE_URL=... \
+// PANTO_MODEL=... \
+// go run examples/simple-agent-go/main.go "list the files"
+
+package main
+
+import (
+ "encoding/json"
+ "fmt"
+ "os"
+ "os/exec"
+ "strings"
+
+ panto "code.tjp.lol/libpantograph.git/libpanto-go"
+)
+
+func main() {
+ panto.Init()
+ defer panto.Deinit()
+
+ apiStyle := env("PANTO_API_STYLE", "openai_chat")
+ apiKey := requireEnv("PANTO_API_KEY")
+ baseURL := requireEnv("PANTO_BASE_URL")
+ model := requireEnv("PANTO_MODEL")
+ maxTokens := uint32(4096)
+ if v := os.Getenv("PANTO_MAX_TOKENS"); v != "" {
+ fmt.Sscanf(v, "%d", &maxTokens)
+ }
+
+ prompt := strings.Join(os.Args[1:], " ")
+ if prompt == "" {
+ fmt.Fprintln(os.Stderr, "usage: simple-agent-go <prompt>")
+ os.Exit(2)
+ }
+
+ var cfg panto.Config
+ if strings.HasPrefix(apiStyle, "anthropic") {
+ cfg.Provider = panto.ProviderConfig{
+ AnthropicMessages: &panto.AnthropicMessagesConfig{
+ APIKey: apiKey,
+ BaseURL: baseURL,
+ Model: model,
+ MaxTokens: maxTokens,
+ },
+ }
+ } else {
+ cfg.Provider = panto.ProviderConfig{
+ OpenAIChat: &panto.OpenAIChatConfig{
+ APIKey: apiKey,
+ BaseURL: baseURL,
+ Model: model,
+ MaxTokens: maxTokens,
+ },
+ }
+ }
+
+ agent, err := panto.NewAgent(cfg)
+ if err != nil {
+ panic(err)
+ }
+ defer agent.Close()
+
+ agent.SetSystemPrompt("You are a very small coding agent.\nUse the bash tool when you need to inspect or change the local workspace.\nKeep final answers concise.")
+
+ agent.RegisterTool(panto.Tool{
+ Decl: panto.ToolDecl{
+ Name: "bash",
+ Description: "Run a shell command with bash -lc and return combined stdout/stderr plus the exit code.",
+ SchemaJSON: `{
+ "type": "object",
+ "properties": {
+ "command": {
+ "type": "string",
+ "description": "The command to run."
+ }
+ },
+ "required": ["command"]
+ }`,
+ },
+ Invoke: func(input string) (panto.ResultParts, error) {
+ var params map[string]any
+ if err := json.Unmarshal([]byte(input), &params); err != nil {
+ return nil, fmt.Errorf("invalid input: %w", err)
+ }
+ command, ok := params["command"].(string)
+ if !ok || command == "" {
+ return nil, fmt.Errorf("bash.command must be a non-empty string")
+ }
+
+ out, err := runBash(command)
+ if err != nil {
+ return nil, err
+ }
+ return panto.ResultParts{{Text: out}}, nil
+ },
+ })
+
+ stream, err := agent.Run(panto.UserMessage(panto.TextBlock(prompt)))
+ if err != nil {
+ panic(err)
+ }
+ defer stream.Close()
+
+ for ev := range stream.Iter() {
+ switch ev := ev.(type) {
+ case panto.ContentDeltaEvent:
+ fmt.Print(ev.Delta)
+ os.Stdout.Sync()
+ case panto.ToolDispatchStartEvent:
+ fmt.Fprintf(os.Stderr, "\n[bash: running %d tool call(s)]\n", ev.Count)
+ case panto.ToolDispatchCompleteEvent:
+ fmt.Fprint(os.Stderr, "[bash: done]\n")
+ }
+ }
+ if err := stream.Err(); err != nil {
+ panic(err)
+ }
+ fmt.Println()
+}
+
+func env(key, def string) string {
+ if v := os.Getenv(key); v != "" {
+ return v
+ }
+ return def
+}
+
+func requireEnv(key string) string {
+ v := os.Getenv(key)
+ if v == "" {
+ panic("missing required env var: " + key)
+ }
+ return v
+}
+
+func runBash(command string) (string, error) {
+ cmd := exec.Command("bash", "-lc", command)
+ out, err := cmd.CombinedOutput()
+ status := "unknown"
+ if exitErr, ok := err.(*exec.ExitError); ok {
+ status = fmt.Sprintf("%d", exitErr.ExitCode())
+ } else if err != nil {
+ return "", err
+ }
+ return fmt.Sprintf("exit_code=%s\n%s", status, string(out)), nil
+}