diff options
| author | t <t@tjp.lol> | 2026-06-11 13:25:15 -0600 |
|---|---|---|
| committer | t <t@tjp.lol> | 2026-06-11 13:26:12 -0600 |
| commit | 3dcbf15bdb90e4c90e7225b82945a6862a7a063f (patch) | |
| tree | 86de1b9853cd1a0506637918d71a64483488321d /examples/simple-agent-go/main.go | |
| parent | 6d725a9e35c3086f61cb6579d2840f5a09be450a (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.go | 152 |
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), ¶ms); 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 +} |
