Building Agents
The ToolLoopAgent provides a structured way to encapsulate LLM configuration, tools, and behavior into reusable components. It handles the agent loop for you, allowing the LLM to call tools multiple times in sequence to accomplish complex tasks. Define agents once and use them across your application.
Why Use ToolLoopAgent?
When building AI applications, you often need to:
- Reuse configurations - Same model settings, tools, and prompts across different parts of your application
- Maintain consistency - Ensure the same behavior and capabilities throughout your codebase
- Simplify API routes - Reduce boilerplate in your endpoints
- Type safety - Get full compile-time type checking for your agent's configuration
The ToolLoopAgent provides a single place to define your agent's behavior.
Creating an Agent
Define an agent by calling NewToolLoopAgent with your desired configuration:
package main
import (
"os"
"github.com/digitallysavvy/go-ai/pkg/agent"
"github.com/digitallysavvy/go-ai/pkg/provider/types"
"github.com/digitallysavvy/go-ai/pkg/providers/openai"
)
func main() {
provider := openai.New(openai.Config{
APIKey: os.Getenv("OPENAI_API_KEY"),
})
model, _ := provider.LanguageModel("gpt-4")
myAgent := agent.NewToolLoopAgent(agent.AgentConfig{
Model: model,
System: "You are a helpful assistant.",
Tools: []types.Tool{
// Your tools here
},
})
_ = myAgent
}
Configuration Options
The AgentConfig struct accepts all the same settings as GenerateText and StreamText. Configure:
Model and System Instructions
package main
import (
"os"
"github.com/digitallysavvy/go-ai/pkg/agent"
"github.com/digitallysavvy/go-ai/pkg/providers/openai"
)
func main() {
provider := openai.New(openai.Config{
APIKey: os.Getenv("OPENAI_API_KEY"),
})
model, _ := provider.LanguageModel("gpt-4")
myAgent := agent.NewToolLoopAgent(agent.AgentConfig{
Model: model,
System: "You are an expert software engineer.",
})
_ = myAgent
}
Tools
Provide tools that the agent can use to accomplish tasks:
package main
import (
"context"
"fmt"
"os"
"github.com/digitallysavvy/go-ai/pkg/agent"
"github.com/digitallysavvy/go-ai/pkg/provider/types"
"github.com/digitallysavvy/go-ai/pkg/providers/openai"
)
func main() {
provider := openai.New(openai.Config{
APIKey: os.Getenv("OPENAI_API_KEY"),
})
model, _ := provider.LanguageModel("gpt-4")
codeAgent := agent.NewToolLoopAgent(agent.AgentConfig{
Model: model,
Tools: []types.Tool{
{
Name: "runCode",
Description: "Execute Python code",
Parameters: map[string]interface{}{
"type": "object",
"properties": map[string]interface{}{
"code": map[string]interface{}{
"type": "string",
"description": "The Python code to execute",
},
},
"required": []string{"code"},
},
Execute: func(ctx context.Context, input map[string]interface{}, opts types.ToolExecutionOptions) (interface{}, error) {
code := input["code"].(string)
// Execute code and return result
return map[string]interface{}{
"output": fmt.Sprintf("Executed: %s", code),
}, nil
},
},
},
})
_ = codeAgent
}
Loop Control
By default, agents stop after 20 completed steps (StopWhen: []ai.StopCondition{ai.IsStepCount(20)}). In each step, the model either generates text or calls a tool. If it generates text, the agent completes. If it calls a tool, the SDK executes that tool.
To let agents call multiple tools in sequence, configure StopWhen with ai.IsStepCount. After each tool execution, the agent triggers a new generation where the model can call another tool or generate text:
package main
import (
"os"
"github.com/digitallysavvy/go-ai/pkg/agent"
"github.com/digitallysavvy/go-ai/pkg/ai"
"github.com/digitallysavvy/go-ai/pkg/providers/openai"
)
func main() {
provider := openai.New(openai.Config{
APIKey: os.Getenv("OPENAI_API_KEY"),
})
model, _ := provider.LanguageModel("gpt-4")
myAgent := agent.NewToolLoopAgent(agent.AgentConfig{
Model: model,
StopWhen: []ai.StopCondition{ai.IsStepCount(20)}, // Allow up to 20 steps
})
_ = myAgent
}
Each step represents one generation (which results in either text or a tool call). The loop continues until:
- A finish reason other than
FinishReasonToolCallsis returned, or - A tool execution fails, or
- A tool call needs approval (if
ToolApprovalRequiredis true), or - The step limit configured with
StopWhenis reached
Learn more about loop control.
Generation Settings
Control temperature, max tokens, and other generation parameters:
package main
import (
"os"
"github.com/digitallysavvy/go-ai/pkg/agent"
"github.com/digitallysavvy/go-ai/pkg/providers/anthropic"
)
func main() {
provider := anthropic.New(anthropic.Config{
APIKey: os.Getenv("ANTHROPIC_API_KEY"),
})
model, _ := provider.LanguageModel("claude-sonnet-4-5")
temperature := 0.7
maxTokens := 2000
myAgent := agent.NewToolLoopAgent(agent.AgentConfig{
Model: model,
System: "You are a creative writer.",
Temperature: &temperature,
MaxTokens: &maxTokens,
})
_ = myAgent
}
Define Agent Behavior with System Instructions
System instructions define your agent's behavior, personality, and constraints. They set the context for all interactions and guide how the agent responds to user queries and uses tools.
Basic System Instructions
Set the agent's role and expertise:
myAgent := agent.NewToolLoopAgent(agent.AgentConfig{
Model: model,
System: "You are an expert data analyst. You provide clear insights from complex data.",
})
Detailed Behavioral Instructions
Provide specific guidelines for agent behavior:
codeReviewAgent := agent.NewToolLoopAgent(agent.AgentConfig{
Model: model,
System: `You are a senior software engineer conducting code reviews.
Your approach:
- Focus on security vulnerabilities first
- Identify performance bottlenecks
- Suggest improvements for readability and maintainability
- Be constructive and educational in your feedback
- Always explain why something is an issue and how to fix it`,
})
Constrain Agent Behavior
Set boundaries and ensure consistent behavior:
customerSupportAgent := agent.NewToolLoopAgent(agent.AgentConfig{
Model: model,
System: `You are a customer support specialist for an e-commerce platform.
Rules:
- Never make promises about refunds without checking the policy
- Always be empathetic and professional
- If you don't know something, say so and offer to escalate
- Keep responses concise and actionable
- Never share internal company information`,
Tools: []types.Tool{
checkOrderStatusTool,
lookupPolicyTool,
createTicketTool,
},
})
Tool Usage Instructions
Guide how the agent should use available tools:
researchAgent := agent.NewToolLoopAgent(agent.AgentConfig{
Model: model,
System: `You are a research assistant with access to search and document tools.
When researching:
1. Always start with a broad search to understand the topic
2. Use document analysis for detailed information
3. Cross-reference multiple sources before drawing conclusions
4. Cite your sources when presenting information
5. If information conflicts, present both viewpoints`,
Tools: []types.Tool{
webSearchTool,
analyzeDocumentTool,
extractQuotesTool,
},
})
Format and Style Instructions
Control the output format and communication style:
technicalWriterAgent := agent.NewToolLoopAgent(agent.AgentConfig{
Model: model,
System: `You are a technical documentation writer.
Writing style:
- Use clear, simple language
- Avoid jargon unless necessary
- Structure information with headers and bullet points
- Include code examples where relevant
- Write in second person ("you" instead of "the user")
Always format responses in Markdown.`,
})
Using an Agent
Once defined, you can use your agent with its execution methods:
Execute with Simple Prompt
Use Execute() for one-time text generation with a simple prompt:
package main
import (
"context"
"fmt"
"log"
"os"
"github.com/digitallysavvy/go-ai/pkg/agent"
"github.com/digitallysavvy/go-ai/pkg/providers/openai"
)
func main() {
ctx := context.Background()
provider := openai.New(openai.Config{
APIKey: os.Getenv("OPENAI_API_KEY"),
})
model, _ := provider.LanguageModel("gpt-4")
myAgent := agent.NewToolLoopAgent(agent.AgentConfig{
Model: model,
System: "You are a helpful assistant.",
})
result, err := myAgent.Execute(ctx, "What is the weather like?")
if err != nil {
log.Fatal(err)
}
fmt.Println(result.Text)
}
Execute with Message History
Use ExecuteWithMessages() for conversations with history:
package main
import (
"context"
"fmt"
"log"
"os"
"github.com/digitallysavvy/go-ai/pkg/agent"
"github.com/digitallysavvy/go-ai/pkg/provider/types"
"github.com/digitallysavvy/go-ai/pkg/providers/openai"
)
func main() {
ctx := context.Background()
provider := openai.New(openai.Config{
APIKey: os.Getenv("OPENAI_API_KEY"),
})
model, _ := provider.LanguageModel("gpt-4")
myAgent := agent.NewToolLoopAgent(agent.AgentConfig{
Model: model,
System: "You are a helpful assistant.",
})
// Create conversation history
messages := []types.Message{
{
Role: types.RoleUser,
Content: []types.ContentPart{
types.TextContent{Text: "What is 2+2?"},
},
},
{
Role: types.RoleAssistant,
Content: []types.ContentPart{
types.TextContent{Text: "2+2 equals 4."},
},
},
{
Role: types.RoleUser,
Content: []types.ContentPart{
types.TextContent{Text: "What about 3+3?"},
},
},
}
result, err := myAgent.ExecuteWithMessages(ctx, messages)
if err != nil {
log.Fatal(err)
}
fmt.Println(result.Text)
}
Monitoring Agent Execution with Callbacks
Monitor and debug agent behavior with lifecycle callbacks. Callbacks fire at key points during agent execution, providing visibility into the agent's decision-making process and allowing you to implement custom logic like logging, monitoring, or budget controls.
Available Callbacks
- OnStepStart - Called before each agent step begins
- OnStepFinish - Called after each step completes (most useful for monitoring)
- OnToolCall - Called when a tool is about to be executed
- OnToolResult - Called after a tool execution completes
- OnFinish - Called when the entire agent execution completes
Basic Callback Example
package main
import (
"context"
"fmt"
"log"
"os"
"github.com/digitallysavvy/go-ai/pkg/agent"
"github.com/digitallysavvy/go-ai/pkg/provider/types"
"github.com/digitallysavvy/go-ai/pkg/providers/anthropic"
)
func main() {
ctx := context.Background()
provider := anthropic.New(anthropic.Config{
APIKey: os.Getenv("ANTHROPIC_API_KEY"),
})
model, _ := provider.LanguageModel("claude-sonnet-4-5")
myAgent := agent.NewToolLoopAgent(agent.AgentConfig{
Model: model,
System: "You are a helpful research assistant.",
MaxSteps: 15,
OnStepStart: func(stepNum int) {
fmt.Printf("Starting step %d\n", stepNum)
},
OnStepFinish: func(step types.StepResult) {
fmt.Printf("Step %d complete: %s\n", step.StepNumber, step.Text)
if step.Usage.GetTotalTokens() > 0 {
fmt.Printf(" Tokens used: %d\n", step.Usage.GetTotalTokens())
}
},
OnToolCall: func(toolCall types.ToolCall) {
fmt.Printf("Calling tool: %s\n", toolCall.ToolName)
fmt.Printf(" Arguments: %v\n", toolCall.Arguments)
},
OnToolResult: func(toolResult types.ToolResult) {
if toolResult.Error != nil {
fmt.Printf("Tool %s failed: %v\n", toolResult.ToolName, toolResult.Error)
} else {
fmt.Printf("Tool %s returned: %v\n", toolResult.ToolName, toolResult.Result)
}
},
OnFinish: func(result *agent.AgentResult) {
fmt.Printf("Agent complete after %d steps\n", len(result.Steps))
if result.Usage.GetTotalTokens() > 0 {
fmt.Printf("Total tokens used: %d\n", result.Usage.GetTotalTokens())
}
},
})
result, err := myAgent.Execute(ctx, "Research the latest developments in quantum computing")
if err != nil {
log.Fatal(err)
}
fmt.Println("\n=== Final Answer ===")
fmt.Println(result.Text)
}
OnStepFinish Callback
The OnStepFinish callback is called after each agent step completes. It provides detailed information about what happened during the step, making it the most useful callback for monitoring, logging, and implementing custom control logic.
StepResult Structure
The StepResult passed to OnStepFinish contains:
StepNumber(int) - The step number (0-indexed)Text(string) - Text generated in this stepToolCalls([]ToolCall) - Tool calls made in this stepToolResults([]ToolResult) - Results from tool executionsFinishReason(FinishReason) - Why this step ended ("stop", "tool-calls", "length", etc.)RawFinishReason(string) - Raw finish reason from the providerUsage(Usage) - Token usage for this stepWarnings([]Warning) - Warnings generated during this stepResponseMessages([]Message) - The assistant message generated in this step
Common Use Cases
Logging and Debugging:
OnStepFinish: func(step types.StepResult) {
log.Printf("[Step %d] Generated: %s", step.StepNumber, step.Text)
log.Printf("[Step %d] Tool calls: %d, Tokens: %d",
step.StepNumber,
len(step.ToolCalls),
getTokens(step.Usage.TotalTokens))
}
Token Usage Monitoring:
totalTokens := int64(0)
OnStepFinish: func(step types.StepResult) {
if step.Usage.GetTotalTokens() > 0 {
totalTokens += step.Usage.GetTotalTokens()
}
fmt.Printf("Step %d: %d tokens (total: %d)\n",
step.StepNumber,
getTokens(step.Usage.TotalTokens),
totalTokens)
if totalTokens > 10000 {
log.Println("WARNING: Approaching token limit")
}
}
Warning Detection:
OnStepFinish: func(step types.StepResult) {
if len(step.Warnings) > 0 {
for _, warning := range step.Warnings {
log.Printf("WARNING [%s]: %s", warning.Type, warning.Message)
}
}
}
Pattern Analysis:
OnStepFinish: func(step types.StepResult) {
// Detect inefficient tool usage
if len(step.ToolCalls) > 3 {
log.Printf("Multiple tool calls in step %d - consider optimization",
step.StepNumber)
}
// Track tool usage patterns
for _, tc := range step.ToolCalls {
metrics.RecordToolUsage(tc.ToolName)
}
}
See the OnStepFinish examples for complete working examples.
Helper function for safely accessing token counts:
func getTokens(tokens *int64) int64 {
if tokens == nil {
return 0
}
return *tokens
}
Tool Approval and Human-in-the-Loop
Require human approval before executing tools:
package main
import (
"bufio"
"context"
"fmt"
"log"
"os"
"strings"
"github.com/digitallysavvy/go-ai/pkg/agent"
"github.com/digitallysavvy/go-ai/pkg/provider/types"
"github.com/digitallysavvy/go-ai/pkg/providers/openai"
)
func main() {
ctx := context.Background()
provider := openai.New(openai.Config{
APIKey: os.Getenv("OPENAI_API_KEY"),
})
model, _ := provider.LanguageModel("gpt-4")
// Create agent with tool approval
myAgent := agent.NewToolLoopAgent(agent.AgentConfig{
Model: model,
System: "You are a helpful assistant with access to various tools.",
Tools: []types.Tool{
{
Name: "deleteFile",
Description: "Delete a file from the filesystem",
Parameters: map[string]interface{}{
"type": "object",
"properties": map[string]interface{}{
"path": map[string]interface{}{
"type": "string",
"description": "The file path to delete",
},
},
"required": []string{"path"},
},
Execute: func(ctx context.Context, input map[string]interface{}, opts types.ToolExecutionOptions) (interface{}, error) {
path := input["path"].(string)
err := os.Remove(path)
if err != nil {
return nil, err
}
return map[string]interface{}{
"success": true,
"message": fmt.Sprintf("Deleted file: %s", path),
}, nil
},
},
},
MaxSteps: 10,
ToolApprovalRequired: true,
ToolApprover: func(toolCall types.ToolCall) bool {
fmt.Printf("\n=== Tool Approval Required ===\n")
fmt.Printf("Tool: %s\n", toolCall.ToolName)
fmt.Printf("Arguments: %v\n", toolCall.Arguments)
fmt.Print("Approve this tool call? (yes/no): ")
reader := bufio.NewReader(os.Stdin)
response, _ := reader.ReadString('\n')
response = strings.TrimSpace(strings.ToLower(response))
approved := response == "yes" || response == "y"
if approved {
fmt.Println("✓ Tool call approved")
} else {
fmt.Println("✗ Tool call rejected")
}
return approved
},
})
result, err := myAgent.Execute(ctx, "Please clean up the temp directory")
if err != nil {
log.Fatal(err)
}
fmt.Println("\n=== Final Answer ===")
fmt.Println(result.Text)
}
Updating Agent Configuration
Modify agent configuration at runtime:
package main
import (
"context"
"fmt"
"log"
"os"
"github.com/digitallysavvy/go-ai/pkg/agent"
"github.com/digitallysavvy/go-ai/pkg/provider/types"
"github.com/digitallysavvy/go-ai/pkg/providers/openai"
)
func main() {
ctx := context.Background()
provider := openai.New(openai.Config{
APIKey: os.Getenv("OPENAI_API_KEY"),
})
model, _ := provider.LanguageModel("gpt-4")
myAgent := agent.NewToolLoopAgent(agent.AgentConfig{
Model: model,
System: "You are a helpful assistant.",
Tools: []types.Tool{},
})
// Update system prompt
myAgent.SetSystem("You are now an expert mathematician.")
// Add a tool
myAgent.AddTool(types.Tool{
Name: "calculate",
Description: "Perform a calculation",
Parameters: map[string]interface{}{
"type": "object",
"properties": map[string]interface{}{
"expression": map[string]interface{}{
"type": "string",
"description": "The mathematical expression to evaluate",
},
},
"required": []string{"expression"},
},
Execute: func(ctx context.Context, input map[string]interface{}, opts types.ToolExecutionOptions) (interface{}, error) {
expr := input["expression"].(string)
// Evaluate expression (simplified example)
return map[string]interface{}{
"expression": expr,
"result": 42,
}, nil
},
})
// Update max steps
myAgent.SetMaxSteps(20)
// Remove a tool
myAgent.RemoveTool("calculate")
result, err := myAgent.Execute(ctx, "Solve a complex problem")
if err != nil {
log.Fatal(err)
}
fmt.Println(result.Text)
}
Accessing Agent Results
The AgentResult provides detailed information about the agent's execution:
package main
import (
"context"
"fmt"
"log"
"os"
"github.com/digitallysavvy/go-ai/pkg/agent"
"github.com/digitallysavvy/go-ai/pkg/providers/anthropic"
)
func main() {
ctx := context.Background()
provider := anthropic.New(anthropic.Config{
APIKey: os.Getenv("ANTHROPIC_API_KEY"),
})
model, _ := provider.LanguageModel("claude-sonnet-4-5")
myAgent := agent.NewToolLoopAgent(agent.AgentConfig{
Model: model,
System: "You are a helpful assistant.",
MaxSteps: 15,
})
result, err := myAgent.Execute(ctx, "What's the weather in Paris and how many hours until sunset?")
if err != nil {
log.Fatal(err)
}
// Access final text
fmt.Println("Final Answer:", result.Text)
// Access all steps taken
fmt.Printf("\nSteps taken: %d\n", len(result.Steps))
for _, step := range result.Steps {
fmt.Printf(" Step %d: %s\n", step.StepNumber, step.Text)
if len(step.ToolCalls) > 0 {
fmt.Printf(" Tool calls: %d\n", len(step.ToolCalls))
}
}
// Access tool results
fmt.Printf("\nTools used: %d\n", len(result.ToolResults))
for _, tr := range result.ToolResults {
if tr.Error != nil {
fmt.Printf(" %s: Error - %v\n", tr.ToolName, tr.Error)
} else {
fmt.Printf(" %s: Success\n", tr.ToolName)
}
}
// Access usage information
fmt.Printf("\nToken Usage:\n")
if result.Usage.InputTokens != nil {
fmt.Printf(" Input tokens: %d\n", *result.Usage.InputTokens)
}
if result.Usage.OutputTokens != nil {
fmt.Printf(" Output tokens: %d\n", *result.Usage.OutputTokens)
}
if result.Usage.GetTotalTokens() > 0 {
fmt.Printf(" Total tokens: %d\n", result.Usage.GetTotalTokens())
}
// Check finish reason
fmt.Printf("\nFinish Reason: %s\n", result.FinishReason)
// Check warnings
if len(result.Warnings) > 0 {
fmt.Printf("\nWarnings: %d\n", len(result.Warnings))
for _, warning := range result.Warnings {
fmt.Printf(" %s: %s\n", warning.Type, warning.Message)
}
}
}
Complete Example: Research Agent
Here's a complete example of a research agent with tools, callbacks, and proper error handling:
package main
import (
"context"
"fmt"
"io"
"log"
"net/http"
"os"
"github.com/digitallysavvy/go-ai/pkg/agent"
"github.com/digitallysavvy/go-ai/pkg/provider/types"
"github.com/digitallysavvy/go-ai/pkg/providers/anthropic"
)
func main() {
ctx := context.Background()
// Create provider and model
provider := anthropic.New(anthropic.Config{
APIKey: os.Getenv("ANTHROPIC_API_KEY"),
})
model, err := provider.LanguageModel("claude-sonnet-4-5")
if err != nil {
log.Fatal(err)
}
// Create research agent with tools
researchAgent := agent.NewToolLoopAgent(agent.AgentConfig{
Model: model,
System: `You are a research assistant. Use the available tools to find and analyze information.
Guidelines:
- Start with broad searches to understand the topic
- Use webpage fetching for detailed information
- Synthesize information from multiple sources
- Cite your sources in your final answer`,
Tools: []types.Tool{
{
Name: "search",
Description: "Search the web for information",
Parameters: map[string]interface{}{
"type": "object",
"properties": map[string]interface{}{
"query": map[string]interface{}{
"type": "string",
"description": "The search query",
},
},
"required": []string{"query"},
},
Execute: func(ctx context.Context, input map[string]interface{}, opts types.ToolExecutionOptions) (interface{}, error) {
query := input["query"].(string)
fmt.Printf("[Search] Query: %s\n", query)
// Actual implementation would call a search API
return map[string]interface{}{
"results": []map[string]interface{}{
{
"title": "Example Result 1",
"url": "https://example.com/1",
"snippet": "Sample search result...",
},
},
}, nil
},
},
{
Name: "fetchWebpage",
Description: "Fetch and return the content of a webpage",
Parameters: map[string]interface{}{
"type": "object",
"properties": map[string]interface{}{
"url": map[string]interface{}{
"type": "string",
"description": "The URL to fetch",
},
},
"required": []string{"url"},
},
Execute: func(ctx context.Context, input map[string]interface{}, opts types.ToolExecutionOptions) (interface{}, error) {
url := input["url"].(string)
fmt.Printf("[Fetch] URL: %s\n", url)
resp, err := http.Get(url)
if err != nil {
return nil, fmt.Errorf("failed to fetch URL: %w", err)
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("failed to read response: %w", err)
}
// Actual implementation would parse and clean HTML
return map[string]interface{}{
"url": url,
"content": string(body),
}, nil
},
},
},
MaxSteps: 15,
// Add callbacks for monitoring
OnStepStart: func(stepNum int) {
fmt.Printf("\n[Agent] Starting step %d\n", stepNum)
},
OnStepFinish: func(step types.StepResult) {
if step.Text != "" {
fmt.Printf("[Agent] Step %d: %s\n", step.StepNumber, step.Text)
}
},
OnToolCall: func(toolCall types.ToolCall) {
fmt.Printf("[Agent] Calling tool: %s\n", toolCall.ToolName)
},
OnFinish: func(result *agent.AgentResult) {
fmt.Printf("\n[Agent] Completed in %d steps\n", len(result.Steps))
if result.Usage.GetTotalTokens() > 0 {
fmt.Printf("[Agent] Total tokens: %d\n", result.Usage.GetTotalTokens())
}
},
})
// Execute agent
result, err := researchAgent.Execute(
ctx,
"Research the latest developments in quantum computing and provide a summary with key points",
)
if err != nil {
log.Fatalf("Agent execution failed: %v", err)
}
// Display results
fmt.Println("\n=== Final Answer ===")
fmt.Println(result.Text)
fmt.Println("\n=== Agent Statistics ===")
fmt.Printf("Steps: %d\n", len(result.Steps))
fmt.Printf("Tools used: %d\n", len(result.ToolResults))
if result.Usage.GetTotalTokens() > 0 {
fmt.Printf("Total tokens: %d\n", result.Usage.GetTotalTokens())
}
fmt.Printf("Finish reason: %s\n", result.FinishReason)
}
Best Practices
1. Clear System Instructions
Provide clear, specific instructions about the agent's role and behavior:
// Good - Clear and specific
agent.AgentConfig{
System: `You are a code review assistant. Focus on security, performance, and maintainability.
Rules:
- Identify potential bugs and security issues
- Suggest specific improvements with code examples
- Explain why changes are needed
- Be constructive and educational`,
}
// Bad - Too vague
agent.AgentConfig{
System: "You help with code",
}
2. Appropriate MaxSteps
Set MaxSteps based on task complexity:
// Simple tasks - fewer steps
agent.AgentConfig{
MaxSteps: 5, // Quick Q&A, simple calculations
}
// Complex tasks - more steps
agent.AgentConfig{
MaxSteps: 20, // Research, multi-step analysis
}
3. Tool Descriptions
Write clear tool descriptions that help the model understand when to use each tool:
// Good - Clear purpose and usage
types.Tool{
Name: "getWeather",
Description: "Get current weather for a specific city. Use when user asks about weather conditions, temperature, or forecast.",
}
// Bad - Unclear
types.Tool{
Name: "getWeather",
Description: "Weather",
}
4. Error Handling in Tools
Always handle errors properly in tool execution functions:
types.Tool{
Name: "fetchData",
Description: "Fetch data from API",
Execute: func(ctx context.Context, input map[string]interface{}, opts types.ToolExecutionOptions) (interface{}, error) {
// Good - Proper error handling
data, err := fetchFromAPI(ctx, input)
if err != nil {
return nil, fmt.Errorf("failed to fetch data: %w", err)
}
return data, nil
},
}
5. Use Callbacks for Monitoring
Add callbacks in development to understand agent behavior:
agent.AgentConfig{
OnToolCall: func(toolCall types.ToolCall) {
log.Printf("Tool: %s, Args: %v", toolCall.ToolName, toolCall.Arguments)
},
OnToolResult: func(toolResult types.ToolResult) {
if toolResult.Error != nil {
log.Printf("Tool %s failed: %v", toolResult.ToolName, toolResult.Error)
}
},
}
6. Context Usage
Always pass and respect context for cancellation and timeouts:
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Minute)
defer cancel()
result, err := myAgent.Execute(ctx, prompt)
if err != nil {
if ctx.Err() == context.DeadlineExceeded {
log.Println("Agent execution timed out")
}
return err
}
7. Tool Approval for Dangerous Operations
Require approval for operations that modify state or access sensitive data:
agent.AgentConfig{
ToolApprovalRequired: true,
ToolApprover: func(toolCall types.ToolCall) bool {
// Implement your approval logic
return userApproves(toolCall)
},
}
Next Steps
Now that you understand building agents, you can:
- Explore workflow patterns for structured patterns using core functions
- Learn about loop control for advanced execution control
- See configuring call options for fine-tuning behavior