Add go-llm v2: redesigned API for simpler LLM abstraction
v2 is a new Go module (v2/) with a dramatically simpler API: - Unified Message type (no more Input marker interface) - Define[T] for ergonomic tool creation with standard context.Context - Chat session with automatic tool-call loop (agent loop) - Streaming via pull-based StreamReader - MCP one-call connect (MCPStdioServer, MCPHTTPServer, MCPSSEServer) - Middleware support (logging, retry, timeout, usage tracking) - Decoupled JSON Schema (map[string]any, no provider coupling) - Sample tools: WebSearch, Browser, Exec, ReadFile, WriteFile, HTTP - Providers: OpenAI, Anthropic, Google (all with streaming) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
101
v2/tools/exec.go
Normal file
101
v2/tools/exec.go
Normal file
@@ -0,0 +1,101 @@
|
||||
package tools
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os/exec"
|
||||
"runtime"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
llm "gitea.stevedudenhoeffer.com/steve/go-llm/v2"
|
||||
)
|
||||
|
||||
// ExecParams defines parameters for the exec tool.
|
||||
type ExecParams struct {
|
||||
Command string `json:"command" description:"The shell command to execute"`
|
||||
}
|
||||
|
||||
// ExecOption configures the exec tool.
|
||||
type ExecOption func(*execConfig)
|
||||
|
||||
type execConfig struct {
|
||||
allowedCommands []string
|
||||
workDir string
|
||||
timeout time.Duration
|
||||
}
|
||||
|
||||
// WithAllowedCommands restricts which commands can be executed.
|
||||
// If empty, all commands are allowed.
|
||||
func WithAllowedCommands(cmds []string) ExecOption {
|
||||
return func(c *execConfig) { c.allowedCommands = cmds }
|
||||
}
|
||||
|
||||
// WithWorkDir sets the working directory for command execution.
|
||||
func WithWorkDir(dir string) ExecOption {
|
||||
return func(c *execConfig) { c.workDir = dir }
|
||||
}
|
||||
|
||||
// WithExecTimeout sets the maximum execution time.
|
||||
func WithExecTimeout(d time.Duration) ExecOption {
|
||||
return func(c *execConfig) { c.timeout = d }
|
||||
}
|
||||
|
||||
// Exec creates a shell command execution tool.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// tools := llm.NewToolBox(
|
||||
// tools.Exec(tools.WithAllowedCommands([]string{"ls", "cat", "grep"})),
|
||||
// )
|
||||
func Exec(opts ...ExecOption) llm.Tool {
|
||||
cfg := &execConfig{
|
||||
timeout: 30 * time.Second,
|
||||
}
|
||||
for _, opt := range opts {
|
||||
opt(cfg)
|
||||
}
|
||||
|
||||
return llm.Define[ExecParams]("exec", "Execute a shell command and return its output",
|
||||
func(ctx context.Context, p ExecParams) (string, error) {
|
||||
// Check allowed commands
|
||||
if len(cfg.allowedCommands) > 0 {
|
||||
parts := strings.Fields(p.Command)
|
||||
if len(parts) == 0 {
|
||||
return "", fmt.Errorf("empty command")
|
||||
}
|
||||
allowed := false
|
||||
for _, cmd := range cfg.allowedCommands {
|
||||
if parts[0] == cmd {
|
||||
allowed = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !allowed {
|
||||
return "", fmt.Errorf("command %q is not in the allowed list", parts[0])
|
||||
}
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(ctx, cfg.timeout)
|
||||
defer cancel()
|
||||
|
||||
var cmd *exec.Cmd
|
||||
if runtime.GOOS == "windows" {
|
||||
cmd = exec.CommandContext(ctx, "cmd", "/C", p.Command)
|
||||
} else {
|
||||
cmd = exec.CommandContext(ctx, "sh", "-c", p.Command)
|
||||
}
|
||||
|
||||
if cfg.workDir != "" {
|
||||
cmd.Dir = cfg.workDir
|
||||
}
|
||||
|
||||
output, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
return fmt.Sprintf("Error: %s\nOutput: %s", err.Error(), string(output)), nil
|
||||
}
|
||||
|
||||
return string(output), nil
|
||||
},
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user