Tools are your own custom built-in functions that LLMs can use to perform specific actions in your application. This guide explains how to create and use tools with gollem.
To create a tool, you need to implement the Tool interface:
type Tool interface {
Spec() ToolSpec
Run(ctx context.Context, args map[string]any) (map[string]any, error)
}Here's an example of a simple tool:
type HelloTool struct{}
func (t *HelloTool) Spec() gollem.ToolSpec {
return gollem.ToolSpec{
Name: "hello",
Description: "Returns a greeting",
Parameters: map[string]*gollem.Parameter{
"name": {
Type: gollem.TypeString,
Description: "Name of the person to greet",
},
},
Required: []string{"name"},
}
}
func (t *HelloTool) Run(ctx context.Context, args map[string]any) (map[string]any, error) {
return map[string]any{
"message": fmt.Sprintf("Hello, %s!", args["name"]),
}, nil
}The ToolSpec defines the tool's interface:
Name: Unique identifier for the toolDescription: Human-readable description of what the tool doesParameters: Map of parameter names to their specificationsRequired: List of required parameter names (For Object type)
Each parameter specification includes:
Type: Parameter type (string, number, boolean, etc.)Description: Human-readable descriptionTitle: Optional user-friendly name for the parameterRequired: Optional boolean indicating if the parameter is requiredRequiredFields: List of required field names when Type is ObjectEnum: Optional list of allowed valuesProperties: Map of properties when Type is ObjectItems: Specification for array items when Type is ArrayMinimum/Maximum: Number constraintsMinLength/MaxLength: String length constraintsPattern: Regular expression pattern for string validationMinItems/MaxItems: Array size constraintsDefault: Default value for the parameter
Caution
Note that not all parameters are supported by every LLM, as parameter support varies between different LLM providers.
To use tools with your agent:
agent := gollem.New(client,
gollem.WithTools(&HelloTool{}),
)
// Execute with automatic session management
err := agent.Execute(ctx, "Say hello to Alice")
if err != nil {
panic(err)
}You can add multiple tools:
agent := gollem.New(client,
gollem.WithTools(&HelloTool{}, &CalculatorTool{}, &WeatherTool{}),
gollem.WithContentBlockMiddleware(func(next gollem.ContentBlockHandler) gollem.ContentBlockHandler {
return func(ctx context.Context, req *gollem.ContentRequest) (*gollem.ContentResponse, error) {
resp, err := next(ctx, req)
if err == nil && len(resp.Texts) > 0 {
for _, text := range resp.Texts {
fmt.Printf("🤖 %s\n", text)
}
}
return resp, err
}
}),
gollem.WithToolMiddleware(func(next gollem.ToolHandler) gollem.ToolHandler {
return func(ctx context.Context, req *gollem.ToolExecRequest) (*gollem.ToolExecResponse, error) {
fmt.Printf("⚡ Executing: %s\n", req.Tool.Name)
return next(ctx, req)
}
}),
)
// Execute multiple interactions with tools
err := agent.Execute(ctx, "Say hello to Bob and then calculate 15 + 27")
if err != nil {
panic(err)
}gollem provides comprehensive middleware for monitoring tool execution:
agent := gollem.New(client,
gollem.WithTools(&HelloTool{}, &CalculatorTool{}),
// Monitor and control tool execution
gollem.WithToolMiddleware(func(next gollem.ToolHandler) gollem.ToolHandler {
return func(ctx context.Context, req *gollem.ToolExecRequest) (*gollem.ToolExecResponse, error) {
fmt.Printf("⚡ Executing tool: %s with args: %v\n", req.Tool.Name, req.Tool.Arguments)
// Implement access control
if !isToolAllowed(req.Tool.Name) {
return &gollem.ToolExecResponse{
Error: fmt.Errorf("tool %s not allowed", req.Tool.Name),
}, nil
}
// Execute tool
resp, err := next(ctx, req)
// Handle response
if resp.Error != nil {
fmt.Printf("❌ Tool %s failed: %v\n", req.Tool.Name, resp.Error)
// Decide whether to continue or abort
if isCriticalTool(req.Tool.Name) {
return resp, err // Abort execution
}
} else {
fmt.Printf("✅ Tool %s completed: %v\n", req.Tool.Name, resp.Result)
}
return resp, err
}
}),
)SubAgents allow a parent agent to delegate tasks to specialized child agents. SubAgents implement the Tool interface, so they can be invoked by the LLM just like regular tools.
In default mode, the subagent accepts a single query parameter:
// Create a SubAgent with a factory function
// The factory is called each time the SubAgent is invoked,
// ensuring independent session state for each call
// The factory returns (*Agent, error) to handle creation failures
reviewSubagent := gollem.NewSubAgent(
"code_reviewer",
"Reviews code for best practices and potential issues",
func() (*gollem.Agent, error) {
return gollem.New(reviewClient,
gollem.WithSystemPrompt("You are an expert code reviewer."),
), nil
},
)
// Add to parent agent
parentAgent := gollem.New(client,
gollem.WithSubAgents(reviewSubagent),
)
// LLM can now call: code_reviewer(query: "Review this function for security issues...")For more structured inputs, use PromptTemplate to define custom parameters:
// Create a prompt template with custom parameters
template, err := gollem.NewPromptTemplate(
`Analyze the following code focusing on {{.focus}}:
{{.code}}
Provide detailed feedback.`,
map[string]*gollem.Parameter{
"code": {Type: gollem.TypeString, Description: "Code to analyze", Required: true},
"focus": {Type: gollem.TypeString, Description: "Focus area (security, performance, etc.)", Required: true},
},
)
if err != nil {
panic(err)
}
// Create subagent with custom template using a factory function
analyzer := gollem.NewSubAgent(
"code_analyzer",
"Analyzes code with specified focus area",
func() (*gollem.Agent, error) {
return gollem.New(analyzerClient,
gollem.WithSystemPrompt("You are a code analysis expert."),
), nil
},
gollem.WithPromptTemplate(template),
)
// LLM can now call: code_analyzer(code: "func main()...", focus: "security")You can test your templates independently using the Render method:
// Create template
template, err := gollem.NewPromptTemplate(
"Hello, {{.name}}! Your task is: {{.task}}",
map[string]*gollem.Parameter{
"name": {Type: gollem.TypeString, Description: "User name", Required: true},
"task": {Type: gollem.TypeString, Description: "Task description", Required: true},
},
)
if err != nil {
panic(err)
}
// Test rendering
result, err := template.Render(map[string]any{
"name": "Alice",
"task": "Review the code",
})
// result: "Hello, Alice! Your task is: Review the code"
// Inspect parameters
params := template.Parameters()
fmt.Printf("Required: %v\n", params["name"].Required) // trueUse DefaultPromptTemplate() to get the standard query-based template:
template := gollem.DefaultPromptTemplate()
// Equivalent to: NewPromptTemplate("{{.query}}", map[string]*Parameter{"query": {...}})SubAgents can have their own subagents for complex hierarchical workflows:
// Level 3: Specialized subagents
securitySubagent := gollem.NewSubAgent(
"security_check",
"Security analysis",
func() (*gollem.Agent, error) {
return gollem.New(client, gollem.WithSystemPrompt("Security expert")), nil
},
)
perfSubagent := gollem.NewSubAgent(
"perf_check",
"Performance analysis",
func() (*gollem.Agent, error) {
return gollem.New(client, gollem.WithSystemPrompt("Performance expert")), nil
},
)
// Level 2: Code review agent with specialized subagents
reviewSubagent := gollem.NewSubAgent(
"code_review",
"Comprehensive code review",
func() (*gollem.Agent, error) {
return gollem.New(client,
gollem.WithSystemPrompt("Code reviewer"),
gollem.WithSubAgents(securitySubagent, perfSubagent),
), nil
},
)
// Level 1: Main agent
mainAgent := gollem.New(client,
gollem.WithSubAgents(reviewSubagent),
)SubAgent middleware allows you to inject context or modify arguments before template rendering. This is useful for adding runtime information (timestamps, user data, environment info) that the LLM doesn't know about.
// Inject context information
subagent := gollem.NewSubAgent(
"context_aware_analyzer",
"Analyzes requests with user context",
func() (*gollem.Agent, error) {
return gollem.New(analyzerClient,
gollem.WithSystemPrompt("You are an analyzer with user context awareness."),
), nil
},
gollem.WithPromptTemplate(prompt),
gollem.WithSubAgentMiddleware(func(next gollem.SubAgentHandler) gollem.SubAgentHandler {
return func(ctx context.Context, args map[string]any) (map[string]any, error) {
// Add context that LLM doesn't provide
user := getUserFromContext(ctx)
args["current_time"] = time.Now().Format(time.RFC3339)
args["user_name"] = user.Name
args["user_role"] = user.Role
return next(ctx, args)
}
}),
)The injected context variables can be used in the template but are not exposed in the ToolSpec (the LLM doesn't see them as parameters):
prompt, _ := gollem.NewPromptTemplate(
`Current time: {{.current_time}}
User: {{.user_name}} ({{.user_role}})
Analyze the following request: {{.query}}`,
map[string]*gollem.Parameter{
// Only 'query' is visible to the LLM
"query": {Type: gollem.TypeString, Description: "User query", Required: true},
},
)Multiple middlewares can be chained. They execute in the order they are added:
subagent := gollem.NewSubAgent(
"monitored_analyzer",
"Analyzed with logging",
func() (*gollem.Agent, error) {
return gollem.New(client,
gollem.WithSystemPrompt("You are an analyzer."),
), nil
},
gollem.WithPromptTemplate(prompt),
// First middleware: logging
gollem.WithSubAgentMiddleware(func(next gollem.SubAgentHandler) gollem.SubAgentHandler {
return func(ctx context.Context, args map[string]any) (map[string]any, error) {
log.Printf("SubAgent called with args: %v", args)
result, err := next(ctx, args)
log.Printf("SubAgent completed")
return result, err
}
}),
// Second middleware: context injection
gollem.WithSubAgentMiddleware(func(next gollem.SubAgentHandler) gollem.SubAgentHandler {
return func(ctx context.Context, args map[string]any) (map[string]any, error) {
args["timestamp"] = time.Now().Unix()
return next(ctx, args)
}
}),
)- Session information: Inject user ID, session ID, timestamps
- Environment context: Current file path, working directory, project settings
- External data: Database lookups, API responses, cached data
- Argument transformation: Mask sensitive data, normalize inputs
- Logging and monitoring: Track invocations, measure latency
SubAgent factory functions now return (*Agent, error) to handle creation failures gracefully. This allows proper error propagation and recovery strategies.
subagent := gollem.NewSubAgent(
"configurable_agent",
"Agent that requires valid configuration",
func() (*gollem.Agent, error) {
config, err := loadConfig()
if err != nil {
return nil, fmt.Errorf("failed to load config: %w", err)
}
client, err := createClient(config)
if err != nil {
return nil, fmt.Errorf("failed to create client: %w", err)
}
return gollem.New(client,
gollem.WithSystemPrompt(config.SystemPrompt),
), nil
},
)Middlewares can detect and handle factory errors using errors.Is() with gollem.ErrSubAgentFactory:
subagent := gollem.NewSubAgent(
"resilient_agent",
"Agent with error recovery",
func() (*gollem.Agent, error) {
return createAgent() // May fail
},
gollem.WithSubAgentMiddleware(func(next gollem.SubAgentHandler) gollem.SubAgentHandler {
return func(ctx context.Context, args map[string]any) (map[string]any, error) {
result, err := next(ctx, args)
if err != nil && errors.Is(err, gollem.ErrSubAgentFactory) {
// Factory error detected - provide fallback
log.Error("Factory failed", "error", err)
return map[string]any{
"response": "Service temporarily unavailable, using fallback",
"status": "fallback",
}, nil
}
return result, err
}
}),
)Middlewares can implement retry logic for transient failures:
subagent := gollem.NewSubAgent(
"retry_agent",
"Agent with automatic retry",
func() (*gollem.Agent, error) {
return createAgentWithRetry() // May fail transiently
},
gollem.WithSubAgentMiddleware(func(next gollem.SubAgentHandler) gollem.SubAgentHandler {
return func(ctx context.Context, args map[string]any) (map[string]any, error) {
result, err := next(ctx, args)
if err != nil && errors.Is(err, gollem.ErrSubAgentFactory) {
// Retry once on factory error
log.Info("Retrying after factory error")
return next(ctx, args)
}
return result, err
}
}),
)gollem.ErrSubAgentFactory: Sentinel error wrapping all factory-related failures- Factory function returned an error
- Factory function returned
nilagent - Use
errors.Is(err, gollem.ErrSubAgentFactory)to detect
- Learn about MCP server integration for external tool integration
- Check out practical examples of tool usage
- Review the getting started guide for basic usage
- Understand history management for conversation context
- Explore the complete documentation