Help us shape the future of Go! Take the 2026 Go Developer Survey

functiontool

package
v1.41.2 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: May 6, 2026 License: MIT Imports: 4 Imported by: 0

README

FunctionTool - Type-Safe Tool Creation for Hector v2

FunctionTool provides a convenient way to create tools from typed Go functions, following ADK-Go patterns. It reduces boilerplate, provides compile-time type safety, and automatically generates JSON schemas from struct tags.

Overview

FunctionTool is syntactic sugar over CallableTool - it generates a CallableTool implementation from a typed function, similar to:

  • Go (explicit): functiontool.New(cfg, func)
  • Python (implicit): tools=[func] (auto-wrapped)
  • Java (explicit): FunctionTool.create(class, method)

When to Use FunctionTool

✅ Use FunctionTool for:

  • Simple, stateless tools
  • ≤ 5 parameters
  • No streaming output
  • Static schema
  • Straightforward error handling

❌ Use Direct CallableTool for:

  • Streaming tools (CallStreaming)
  • Complex internal state
  • Dynamic schemas
  • HITL workflows
  • Complex error patterns

Basic Usage

package main

import (
    "github.com/verikod/hector/pkg/tool"
    "github.com/verikod/hector/pkg/tool/functiontool"
)

// Define args struct with jsonschema tags
type GetWeatherArgs struct {
    City  string `json:"city" jsonschema:"required,description=City name"`
    Units string `json:"units,omitempty" jsonschema:"description=Temperature units,default=celsius,enum=celsius|fahrenheit"`
}

func main() {
    // Create tool from function
    weatherTool, err := functiontool.New(
        functiontool.Config{
            Name:        "get_weather",
            Description: "Get current weather for a city",
        },
        func(ctx tool.Context, args GetWeatherArgs) (map[string]any, error) {
            // Implementation
            return map[string]any{
                "city":      args.City,
                "temp":      22,
                "condition": "sunny",
                "units":     args.Units,
            }, nil
        },
    )
    
    // Use in agent
    agent, _ := llmagent.New(llmagent.Config{
        Tools: []tool.Tool{weatherTool},
    })
}

Struct Tag Reference

JSON Tags
  • json:"name" - Parameter name (required)
  • json:",omitempty" - Optional parameter (not required)
JSONSchema Tags
  • jsonschema:"required" - Mark as required explicitly
  • jsonschema:"description=..." - Parameter description for LLM
  • jsonschema:"default=..." - Default value
  • jsonschema:"enum=val1|val2" - Allowed values
  • jsonschema:"minimum=N,maximum=M" - Numeric constraints
  • jsonschema:"minLength=N,maxLength=M" - String length constraints

Examples

Simple Parameters
type CalculateArgs struct {
    A int `json:"a" jsonschema:"required,description=First number"`
    B int `json:"b" jsonschema:"required,description=Second number"`
}

calcTool, _ := functiontool.New(
    functiontool.Config{
        Name:        "calculate",
        Description: "Add two numbers",
    },
    func(ctx tool.Context, args CalculateArgs) (map[string]any, error) {
        return map[string]any{"result": args.A + args.B}, nil
    },
)
Complex Parameters
type SearchArgs struct {
    Query     string   `json:"query" jsonschema:"required,description=Search query"`
    Languages []string `json:"languages,omitempty" jsonschema:"description=Language filters"`
    MaxCount  int      `json:"max_count,omitempty" jsonschema:"description=Max results,default=10,minimum=1,maximum=100"`
    Type      string   `json:"type,omitempty" jsonschema:"description=Search type,enum=semantic|keyword"`
}

searchTool, _ := functiontool.New(
    functiontool.Config{
        Name:        "search",
        Description: "Search documents with filters",
    },
    func(ctx tool.Context, args SearchArgs) (map[string]any, error) {
        // Implementation with array and enum parameters
        return map[string]any{
            "results": []string{"doc1", "doc2"},
            "count":   2,
        }, nil
    },
)
Custom Validation
type FileArgs struct {
    Path    string `json:"path" jsonschema:"required,description=File path"`
    Content string `json:"content" jsonschema:"required,description=File content"`
}

fileTool, _ := functiontool.NewWithValidation(
    functiontool.Config{
        Name:        "create_file",
        Description: "Create a new file",
    },
    func(ctx tool.Context, args FileArgs) (map[string]any, error) {
        // Implementation
        return map[string]any{"path": args.Path}, nil
    },
    func(args FileArgs) error {
        // Custom validation beyond what tags can express
        if strings.Contains(args.Path, "..") {
            return fmt.Errorf("path traversal not allowed")
        }
        if len(args.Content) > 1000000 {
            return fmt.Errorf("content too large")
        }
        return nil
    },
)

Architecture

FunctionTool generates a CallableTool implementation:

┌─────────────────────────┐
│   tool.Tool Interface   │
└───────────┬─────────────┘
            │
┌───────────▼─────────────┐
│ tool.CallableTool       │
│  - Call()               │
│  - Schema()             │
└───────────┬─────────────┘
            │
┌───────────▼─────────────┐
│ functionTool[Args]      │  ← Generated by functiontool.New()
│  - Type-safe args       │
│  - Auto schema          │
│  - Map → Struct         │
└─────────────────────────┘

Type Safety

FunctionTool provides compile-time type safety through generics:

// Define typed args
type MyArgs struct {
    Value string `json:"value"`
}

// Function receives typed struct (not map[string]any)
func myFunction(ctx tool.Context, args MyArgs) (map[string]any, error) {
    // args.Value is string (compile-time checked)
    return map[string]any{"result": args.Value}, nil
}

// Type parameter inferred from function signature
tool, _ := functiontool.New(cfg, myFunction)

Schema Generation

Schemas are automatically generated using the invopop/jsonschema library:

type Args struct {
    Name string `json:"name" jsonschema:"required,description=User name"`
    Age  int    `json:"age,omitempty" jsonschema:"minimum=0,maximum=150"`
}

// Auto-generates:
// {
//   "type": "object",
//   "properties": {
//     "name": {"type": "string", "description": "User name"},
//     "age": {"type": "integer", "minimum": 0, "maximum": 150}
//   },
//   "required": ["name"]
// }

Error Handling

Errors are propagated from your function:

func risky(ctx tool.Context, args MyArgs) (map[string]any, error) {
    if args.Value == "" {
        return nil, fmt.Errorf("value is required")  // LLM sees this error
    }
    return map[string]any{"ok": true}, nil
}

Comparison with Direct CallableTool

FunctionTool (120 lines → 40 lines):

type ReadFileArgs struct {
    Path string `json:"path" jsonschema:"required,description=File path"`
}

readTool, _ := functiontool.New(
    functiontool.Config{Name: "read_file", Description: "Read file"},
    func(ctx tool.Context, args ReadFileArgs) (map[string]any, error) {
        content, _ := os.ReadFile(args.Path)
        return map[string]any{"content": string(content)}, nil
    },
)

Direct CallableTool (full control):

type ReadFileTool struct {
    config Config
}

func (t *ReadFileTool) Name() string { return "read_file" }
func (t *ReadFileTool) Description() string { return "Read file" }
func (t *ReadFileTool) IsLongRunning() bool { return false }
func (t *ReadFileTool) Schema() map[string]any {
    return map[string]any{
        "type": "object",
        "properties": map[string]any{
            "path": map[string]any{"type": "string", "description": "File path"},
        },
        "required": []string{"path"},
    }
}
func (t *ReadFileTool) Call(ctx tool.Context, args map[string]any) (map[string]any, error) {
    path := args["path"].(string)
    content, _ := os.ReadFile(path)
    return map[string]any{"content": string(content)}, nil
}

Best Practices

  1. Keep it simple: Use FunctionTool for tools with < 5 parameters
  2. Use descriptive tags: The LLM reads these descriptions
  3. Validate early: Use custom validation for complex constraints
  4. Return structured data: Use map[string]any with clear keys
  5. Document enums: List allowed values in description + enum tag
  6. Test with real LLM: Verify the LLM understands your tool

Testing

func TestMyTool(t *testing.T) {
    tool, err := functiontool.New(cfg, myFunc)
    if err != nil {
        t.Fatal(err)
    }
    
    // Test schema
    schema := tool.(tool.CallableTool).Schema()
    // Assert schema properties...
    
    // Test execution
    result, err := tool.(tool.CallableTool).Call(mockCtx, args)
    // Assert results...
}

ADK-Go Alignment

This implementation follows ADK-Go patterns:

Language Pattern Example
Python Implicit wrapping tools=[my_function]
Go Explicit wrapping functiontool.New(cfg, myFunc)
Java Explicit wrapping FunctionTool.create(class, "method")

All use struct tags / annotations for schema generation.

See Also

Documentation

Overview

Package functiontool provides a convenient way to create tools from typed Go functions. This follows the ADK-Go pattern for FunctionTool, providing compile-time type safety and automatic schema generation from struct tags.

FunctionTool is syntactic sugar over the CallableTool interface - it generates a CallableTool implementation from a typed function, reducing boilerplate and improving type safety.

Basic Usage

type GetWeatherArgs struct {
    City  string `json:"city" jsonschema:"required,description=City name"`
    Units string `json:"units,omitempty" jsonschema:"description=Temperature units,default=celsius,enum=celsius|fahrenheit"`
}

weatherTool, err := functiontool.New(
    functiontool.Config{
        Name:        "get_weather",
        Description: "Get current weather for a city",
    },
    func(ctx tool.Context, args GetWeatherArgs) (map[string]any, error) {
        // Implementation
        return map[string]any{"temp": 22, "condition": "sunny"}, nil
    },
)

When to Use FunctionTool

Use FunctionTool for simple, stateless tools with:

  • ≤ 5 parameters
  • No internal state
  • No streaming output
  • Static schema
  • Straightforward error handling

For complex tools (streaming, dynamic schema, stateful), implement CallableTool directly.

ADK-Go Alignment

This implementation follows ADK-Go patterns:

  • Go (explicit): functiontool.New(cfg, func)
  • Python (implicit): tools=[func] (auto-wrapped)
  • Java (explicit): FunctionTool.create(class, method)

Go uses struct tags for schema generation, similar to Java's @Schema annotation.

Example (Basic)

Example_basic demonstrates basic function tool usage

package main

import (
	"fmt"

	"github.com/verikod/hector/pkg/tool"
	"github.com/verikod/hector/pkg/tool/functiontool"
)

func main() {
	type GetWeatherArgs struct {
		City  string `json:"city" jsonschema:"required,description=City name"`
		Units string `json:"units,omitempty" jsonschema:"description=Temperature units,default=celsius,enum=celsius|fahrenheit"`
	}

	weatherTool, err := functiontool.New(
		functiontool.Config{
			Name:        "get_weather",
			Description: "Get current weather for a city",
		},
		func(ctx tool.Context, args GetWeatherArgs) (map[string]any, error) {
			// Simulate weather API call
			return map[string]any{
				"city":      args.City,
				"temp":      22,
				"condition": "sunny",
				"units":     args.Units,
			}, nil
		},
	)

	if err != nil {
		panic(err)
	}

	fmt.Printf("Tool Name: %s\n", weatherTool.Name())
	fmt.Printf("Is Long Running: %v\n", weatherTool.IsLongRunning())
}
Output:
Tool Name: get_weather
Is Long Running: false
Example (ComplexTypes)

Example_complexTypes demonstrates complex parameter types

package main

import (
	"fmt"

	"github.com/verikod/hector/pkg/tool"
	"github.com/verikod/hector/pkg/tool/functiontool"
)

func main() {
	type SearchArgs struct {
		Query     string   `json:"query" jsonschema:"required,description=Search query"`
		Languages []string `json:"languages,omitempty" jsonschema:"description=Language filters"`
		MaxCount  int      `json:"max_count,omitempty" jsonschema:"description=Max results,default=10,minimum=1,maximum=100"`
		Type      string   `json:"type,omitempty" jsonschema:"description=Search type,enum=semantic|keyword"`
	}

	searchTool, err := functiontool.New(
		functiontool.Config{
			Name:        "search",
			Description: "Search documents with filters",
		},
		func(ctx tool.Context, args SearchArgs) (map[string]any, error) {
			return map[string]any{
				"query":   args.Query,
				"results": []string{"doc1", "doc2"},
				"count":   2,
			}, nil
		},
	)

	if err != nil {
		panic(err)
	}

	schema := searchTool.Schema()
	fmt.Printf("Schema type: %s\n", schema["type"])
}
Output:
Schema type: object
Example (WithValidation)

Example_withValidation demonstrates custom validation

package main

import (
	"fmt"

	"github.com/verikod/hector/pkg/tool"
	"github.com/verikod/hector/pkg/tool/functiontool"
)

func main() {
	type CreateFileArgs struct {
		Path    string `json:"path" jsonschema:"required,description=File path"`
		Content string `json:"content" jsonschema:"required,description=File content"`
	}

	createFileTool, err := functiontool.NewWithValidation(
		functiontool.Config{
			Name:        "create_file",
			Description: "Create a new file",
		},
		func(ctx tool.Context, args CreateFileArgs) (map[string]any, error) {
			// Simulate file creation
			return map[string]any{
				"path":  args.Path,
				"bytes": len(args.Content),
			}, nil
		},
		func(args CreateFileArgs) error {
			// Custom validation
			if len(args.Content) > 1000000 {
				return fmt.Errorf("content too large: %d bytes", len(args.Content))
			}
			return nil
		},
	)

	if err != nil {
		panic(err)
	}

	fmt.Printf("Tool: %s\n", createFileTool.Name())
}
Output:
Tool: create_file

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

func New

func New[Args any](cfg Config, fn func(tool.Context, Args) (map[string]any, error)) (tool.CallableTool, error)

New creates a CallableTool from a typed function. This is the primary way to create function tools in Hector v2.

The function signature must be:

func(tool.Context, Args) (map[string]any, error)

Where Args is a struct with json and jsonschema tags defining the parameters.

Example:

type SearchArgs struct {
    Query string `json:"query" jsonschema:"required,description=Search query"`
    Limit int    `json:"limit,omitempty" jsonschema:"description=Max results,default=10"`
}

searchTool, err := functiontool.New(
    functiontool.Config{Name: "search", Description: "Search documents"},
    func(ctx tool.Context, args SearchArgs) (map[string]any, error) {
        // Implementation
    },
)

func NewWithValidation

func NewWithValidation[Args any](
	cfg Config,
	fn func(tool.Context, Args) (map[string]any, error),
	validate func(Args) error,
) (tool.CallableTool, error)

NewWithValidation creates a CallableTool with custom argument validation. The validation function is called before the main function, allowing you to implement complex validation logic beyond what struct tags can express.

Example:

functiontool.NewWithValidation(
    cfg,
    myFunction,
    func(args MyArgs) error {
        if strings.Contains(args.Path, "..") {
            return fmt.Errorf("path traversal not allowed")
        }
        return nil
    },
)

Types

type Config

type Config struct {
	// Name is the unique identifier for this tool (required).
	Name string

	// Description explains what the tool does (required).
	// This is shown to the LLM to help it decide when to use the tool.
	Description string
}

Config defines the configuration for a function tool.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL