summaryrefslogtreecommitdiff
path: root/examples/simple-agent-go/main.go
diff options
context:
space:
mode:
authort <t@tjp.lol>2026-06-11 13:25:15 -0600
committert <t@tjp.lol>2026-06-11 13:26:12 -0600
commit3dcbf15bdb90e4c90e7225b82945a6862a7a063f (patch)
tree86de1b9853cd1a0506637918d71a64483488321d /examples/simple-agent-go/main.go
parent6d725a9e35c3086f61cb6579d2840f5a09be450a (diff)
some go bindings fixes and an example app
Diffstat (limited to 'examples/simple-agent-go/main.go')
-rw-r--r--examples/simple-agent-go/main.go152
1 files changed, 152 insertions, 0 deletions
diff --git a/examples/simple-agent-go/main.go b/examples/simple-agent-go/main.go
new file mode 100644
index 0000000..c733cd7
--- /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/pantograph.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(prompt)
+ if err != nil {
+ panic(err)
+ }
+ defer stream.Close()
+
+ for ev := range stream.Iter() {
+ switch ev.Tag {
+ case panto.EventContentDelta:
+ fmt.Print(ev.ContentDelta.Delta)
+ os.Stdout.Sync()
+ case panto.EventToolDispatchStart:
+ fmt.Fprintf(os.Stderr, "\n[bash: running %d tool call(s)]\n", ev.ToolDispatchStart.Count)
+ case panto.EventToolDispatchComplete:
+ 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
+}