# Introduction Give agents external capabilities with function calling URL: https://agentkit.tech/docs/tools/tools.mdx **Tools** extend agents beyond text generation, enabling them to interact with APIs, databases, file systems, and more. AgentKit automatically handles function calling and parameter validation. ## Quick Start ```go // Define input structure with JSON schema tags type CalculatorInput struct { Operation string `json:"operation" jsonschema:"description=The operation to perform,enum=add,enum=subtract,enum=multiply,enum=divide"` A float64 `json:"a" jsonschema:"description=First number"` B float64 `json:"b" jsonschema:"description=Second number"` } // Implement tool handler func calculateHandler(ctx common.RunContext, params CalculatorInput) (string, error) { switch params.Operation { case "add": return fmt.Sprintf("%.2f", params.A + params.B), nil case "subtract": return fmt.Sprintf("%.2f", params.A - params.B), nil case "multiply": return fmt.Sprintf("%.2f", params.A * params.B), nil case "divide": if params.B == 0 { return "", errors.New("division by zero") } return fmt.Sprintf("%.2f", params.A / params.B), nil } return "", errors.New("unknown operation") } // Create tool calcTool := tool.NewFunctionTool( "calculator", "Perform basic math operations", calculateHandler, ) // Add to agent agent := agent.New(). SetModel(model). AddTool(calcTool) ``` ## Schema Definition AgentKit uses automatic schema generation from Go structs with JSON schema tags: ```go // Define input structure with JSON schema annotations type ToolInput struct { Name string `json:"name" jsonschema:"description=User's name"` Age int `json:"age" jsonschema:"description=User's age"` Active bool `json:"active" jsonschema:"description=Whether user is active"` Tags []string `json:"tags" jsonschema:"description=List of tags"` Metadata map[string]interface{} `json:"metadata" jsonschema:"description=Additional metadata"` } // With constraints using enum values type TaskInput struct { Priority string `json:"priority" jsonschema:"description=Task priority,enum=low,enum=medium,enum=high"` Count int `json:"count" jsonschema:"description=Number of items,minimum=1,maximum=100"` Email string `json:"email" jsonschema:"description=User email,pattern=^[^@]+@[^@]+\\.[^@]+$"` } // Required vs optional fields type UserInput struct { Name string `json:"name" jsonschema:"description=Required name"` Description *string `json:"description,omitempty" jsonschema:"description=Optional description"` } ``` ## Using Tools with Agents ```go // Create an agent with multiple tools weatherTool := createWeatherTool() calcTool := createCalculatorTool() agent := agent.New(). SetModel(model). SetSystemPrompt("You are a helpful assistant with access to weather and calculator tools."). AddTool(weatherTool). AddTool(calcTool) // Agent automatically uses tools when needed session := session.New(agent) response, _ := session.Run(ctx, []agent.ChatMessage{agent.NewUserMessage("What's the weather in Tokyo and what's 25 * 4?")}, nil) // Agent will call both tools and provide a combined response ``` ## Common Tool Patterns ### API Tool Integrate with external APIs: ```go type WeatherInput struct { Location string `json:"location" jsonschema:"description=City name or coordinates"` Units string `json:"units" jsonschema:"description=Temperature units,enum=celsius,enum=fahrenheit,default=celsius"` } func weatherHandler(ctx common.RunContext, params WeatherInput) (string, error) { units := params.Units if units == "" { units = "celsius" } // Call external weather API weather, err := callWeatherAPI(params.Location, units) if err != nil { return "", fmt.Errorf("weather API error: %w", err) } response := WeatherResponse{ Location: params.Location, Temperature: weather.Temperature, Condition: weather.Condition, Units: units, } result, _ := json.Marshal(response) return string(result), nil } func createWeatherTool() tool.Tool { return tool.NewFunctionTool( "get_weather", "Get current weather for a location", weatherHandler, ) } type WeatherResponse struct { Location string `json:"location"` Temperature float64 `json:"temperature"` Condition string `json:"condition"` Units string `json:"units"` } ``` ### Database Tool Query databases safely: ```go type DatabaseQueryInput struct { NameFilter *string `json:"name_filter,omitempty" jsonschema:"description=Filter users by name pattern"` Limit int `json:"limit" jsonschema:"description=Maximum number of results,minimum=1,maximum=100,default=10"` } func databaseHandler(ctx common.RunContext, params DatabaseQueryInput, db *sql.DB) (string, error) { limit := params.Limit if limit == 0 { limit = 10 } query := "SELECT id, name, email FROM users" var sqlParams []interface{} if params.NameFilter != nil && *params.NameFilter != "" { query += " WHERE name LIKE ?" sqlParams = append(sqlParams, "%"+*params.NameFilter+"%") } query += " LIMIT ?" sqlParams = append(sqlParams, limit) rows, err := db.Query(query, sqlParams...) if err != nil { return "", fmt.Errorf("query failed: %w", err) } defer rows.Close() var users []User for rows.Next() { var user User err := rows.Scan(&user.ID, &user.Name, &user.Email) if err != nil { return "", err } users = append(users, user) } result := QueryResult{ Users: users, Count: len(users), } jsonResult, _ := json.Marshal(result) return string(jsonResult), nil } func createDatabaseTool(db *sql.DB) tool.Tool { return tool.NewFunctionTool( "query_users", "Query user database", func(ctx common.RunContext, params DatabaseQueryInput) (string, error) { return databaseHandler(ctx, params, db) }, ) } type User struct { ID int `json:"id"` Name string `json:"name"` Email string `json:"email"` } type QueryResult struct { Users []User `json:"users"` Count int `json:"count"` } ``` ### File System Tool Read and write files securely: ```go type FileInput struct { Filepath string `json:"filepath" jsonschema:"description=Path to file to read"` } func fileHandler(ctx common.RunContext, params FileInput) (string, error) { // Security: prevent directory traversal if strings.Contains(params.Filepath, "..") { return "", errors.New("directory traversal not allowed") } content, err := os.ReadFile(params.Filepath) if err != nil { return "", fmt.Errorf("failed to read file: %w", err) } result := FileContent{ Path: params.Filepath, Content: string(content), Size: len(content), } jsonResult, _ := json.Marshal(result) return string(jsonResult), nil } func createFileTool() tool.Tool { return tool.NewFunctionTool( "read_file", "Read contents of a text file", fileHandler, ) } type FileContent struct { Path string `json:"path"` Content string `json:"content"` Size int `json:"size"` } ``` ## Advanced Features ### Tool Validation Add custom validation logic: ```go type ValidatedTool struct { tool.Tool validator func(args tool.Args) error } func WithValidation(baseTool tool.Tool, validator func(args tool.Args) error) *ValidatedTool { return &ValidatedTool{ Tool: baseTool, validator: validator, } } func (v *ValidatedTool) Execute(args tool.Args) (interface{}, error) { // Run custom validation if err := v.validator(args); err != nil { return nil, fmt.Errorf("validation failed: %w", err) } return v.Tool.Execute(args) } // Usage weatherTool := createWeatherTool() validatedTool := WithValidation(weatherTool, func(args tool.Args) error { location := args.String("location") if len(location) < 2 { return errors.New("location must be at least 2 characters") } return nil }) ``` ### Tool Middleware Add logging, metrics, and other cross-cutting concerns: ```go type LoggingTool struct { tool.Tool logger *slog.Logger } func WithLogging(baseTool tool.Tool, logger *slog.Logger) *LoggingTool { return &LoggingTool{ Tool: baseTool, logger: logger, } } func (l *LoggingTool) Execute(args tool.Args) (interface{}, error) { l.logger.Info("executing tool", "name", l.Name(), "args", args) start := time.Now() result, err := l.Tool.Execute(args) duration := time.Since(start) if err != nil { l.logger.Error("tool failed", "name", l.Name(), "duration", duration, "error", err) } else { l.logger.Info("tool completed", "name", l.Name(), "duration", duration) } return result, err } ``` ### Tool Collections Organize related tools: ```go type ToolSet struct { tools []tool.Tool } func NewToolSet() *ToolSet { return &ToolSet{} } func (ts *ToolSet) Add(tools ...tool.Tool) { ts.tools = append(ts.tools, tools...) } func (ts *ToolSet) Tools() []tool.Tool { return ts.tools } // Usage toolSet := NewToolSet() toolSet.Add( createWeatherTool(), createCalculatorTool(), createDatabaseTool(db), ) agent := agent.New(). SetModel(model) // Add all tools from the set for _, tool := range toolSet.Tools() { agent = agent.AddTool(tool) } ``` ## Best Practices ### Schema Design Write clear, descriptive schemas: ```go // ✅ Good - Clear and specific type GoodInput struct { Location string `json:"location" jsonschema:"description=City name (e.g. 'New York') or coordinates (e.g. '40.7128,-74.0060')"` Priority string `json:"priority" jsonschema:"description=Task priority level,enum=low,enum=medium,enum=high,enum=urgent"` Format string `json:"format" jsonschema:"description=Output format,enum=json,enum=xml,enum=csv,default=json"` } // ❌ Avoid - Vague descriptions type BadInput struct { Location string `json:"location" jsonschema:"description=location"` Priority string `json:"priority" jsonschema:"description=priority"` } ``` ### Error Handling Provide helpful error messages: ```go type RobustInput struct { Data string `json:"data" jsonschema:"description=Input data to process"` } func robustHandler(ctx common.RunContext, params RobustInput) (string, error) { // Validate input if err := validateInput(params); err != nil { return "", fmt.Errorf("invalid input: %w", err) } // Perform operation with retries var result string var err error for i := 0; i < 3; i++ { result, err = performOperation(params) if err == nil { break } // Exponential backoff time.Sleep(time.Second * time.Duration(1<