summaryrefslogtreecommitdiff
path: root/examples
diff options
context:
space:
mode:
Diffstat (limited to 'examples')
-rw-r--r--examples/simple-agent-go/go.mod7
-rw-r--r--examples/simple-agent-go/main.go152
-rw-r--r--examples/simple-agent.lua103
3 files changed, 262 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
+}
diff --git a/examples/simple-agent.lua b/examples/simple-agent.lua
new file mode 100644
index 0000000..baf00b5
--- /dev/null
+++ b/examples/simple-agent.lua
@@ -0,0 +1,103 @@
+#!/usr/bin/env lua
+-- A tiny standalone libpanto-lua app: one prompt in, streamed text out.
+--
+-- Run with Lua 5.4 after building/installing libpanto-lua, for example:
+-- LUA_CPATH='./libpanto-lua/zig-out/lib/?.so;;' lua examples/simple-agent.lua "list the files"
+--
+-- Required environment:
+-- PANTO_API_STYLE=openai_chat|anthropic_messages
+-- PANTO_API_KEY=...
+-- PANTO_BASE_URL=...
+-- PANTO_MODEL=...
+
+local panto = require("panto")
+
+local function getenv(name, default)
+ local value = os.getenv(name)
+ if value == nil or value == "" then return default end
+ return value
+end
+
+local function require_env(name)
+ local value = getenv(name)
+ if value == nil then error("missing required env var: " .. name, 0) end
+ return value
+end
+
+local function shell_quote(s)
+ return "'" .. tostring(s):gsub("'", "'\\''") .. "'"
+end
+
+local function read_all(pipe)
+ local chunks = {}
+ while true do
+ local chunk = pipe:read(8192)
+ if chunk == nil then break end
+ chunks[#chunks + 1] = chunk
+ end
+ return table.concat(chunks)
+end
+
+local function run_bash(command)
+ local wrapped = "bash -lc " .. shell_quote(command) .. " 2>&1; printf '\\n__panto_exit_code:%s\\n' $?"
+ local pipe = assert(io.popen(wrapped, "r"))
+ local output = read_all(pipe)
+ pipe:close()
+
+ local body, code = output:match("^(.*)\n__panto_exit_code:(%-?%d+)\n$")
+ return string.format("exit_code=%s\n%s", code or "unknown", body or output)
+end
+
+local prompt = table.concat(arg, " ")
+if prompt == "" then
+ io.stderr:write("usage: lua examples/simple-agent.lua <prompt>\n")
+ os.exit(2)
+end
+
+local agent = panto.agent {
+ api_style = require_env("PANTO_API_STYLE"),
+ api_key = require_env("PANTO_API_KEY"),
+ base_url = require_env("PANTO_BASE_URL"),
+ model = require_env("PANTO_MODEL"),
+ max_tokens = tonumber(getenv("PANTO_MAX_TOKENS", "4096")),
+}
+
+agent:set_system_prompt(table.concat({
+ "You are a very small coding agent.",
+ "Use the bash tool when you need to inspect or change the local workspace.",
+ "Keep final answers concise.",
+}, "\n"))
+
+agent:register_tool {
+ name = "bash",
+ description = "Run a shell command with bash -lc and return combined stdout/stderr plus the exit code.",
+ schema = {
+ type = "object",
+ properties = {
+ command = {
+ type = "string",
+ description = "The command to run.",
+ },
+ },
+ required = { "command" },
+ },
+ handler = function(input)
+ if type(input.command) ~= "string" or input.command == "" then
+ error("bash.command must be a non-empty string")
+ end
+ return run_bash(input.command)
+ end,
+}
+
+for ev in agent:run(prompt):events() do
+ if ev.type == "content_delta" then
+ io.write(ev.delta)
+ io.flush()
+ elseif ev.type == "tool_dispatch_start" then
+ io.stderr:write(string.format("\n[bash: running %d tool call(s)]\n", ev.count))
+ elseif ev.type == "tool_dispatch_complete" then
+ io.stderr:write("[bash: done]\n")
+ end
+end
+
+io.write("\n")