AgenticGoKit is currently in Beta. APIs may change before the stable v1.0 release.
Skip to content

Custom Handlers Example ​

Implement custom business logic with full control over agent execution.


Overview ​

Custom handlers allow you to:

  • Implement custom business logic
  • Access LLM, tools, and memory directly
  • Build complex processing pipelines
  • Override default agent behavior

Complete Code ​

go
package main

import (
    "context"
    "fmt"
    "log"
    "strings"
    "github.com/agenticgokit/agenticgokit/v1beta"
)

func main() {
    // Create agent with custom handler
    agent, err := v1beta.NewBuilder("CustomAgent").
        WithLLM("openai", "gpt-4").
        WithHandler(customHandler).
        Build()
    if err != nil {
        log.Fatal(err)
    }

    // Execute
    result, err := agent.Run(context.Background(), "What is Go?")
    if err != nil {
        log.Fatal(err)
    }

    fmt.Println(result.Content)
}

// Custom handler with business logic
func customHandler(ctx context.Context, input string, capabilities *v1beta.Capabilities) (string, error) {
    // Pre-processing
    input = strings.TrimSpace(input)
    input = strings.ToLower(input)

    // Check cache first
    if cached, found := checkCache(input); found {
        return cached, nil
    }

    // Call LLM with custom system prompt
    response, err := capabilities.LLM(
        "You are an expert Go programmer. Be concise and technical.",
        input,
    )
    if err != nil {
        return "", err
    }

    // Post-processing
    response = formatResponse(response)

    // Store in cache
    storeCache(input, response)

    return response, nil
}

func checkCache(key string) (string, bool) {
    // Implementation
    return "", false
}

func storeCache(key, value string) {
    // Implementation
}

func formatResponse(text string) string {
    // Add formatting
    return "**Response:**\n" + text
}

Handler Signature ​

go
type HandlerFunc func(ctx context.Context, input string, capabilities *v1beta.Capabilities) (string, error)

Capabilities Object ​

go
type Capabilities struct {
    // Call LLM with custom prompts
    LLM func(system, user string) (string, error)
    
    // Access registered tools
    Tools ToolManager
    
    // Access memory
    Memory Memory
}

Handler Patterns ​

Simple Handler ​

go
func simpleHandler(ctx context.Context, input string, caps *v1beta.Capabilities) (string, error) {
    // Direct LLM call
    return caps.LLM("You are helpful", input)
}

Multi-Step Handler ​

go
func multiStepHandler(ctx context.Context, input string, caps *v1beta.Capabilities) (string, error) {
    // Step 1: Analyze input
    analysis, err := caps.LLM("Analyze the query", input)
    if err != nil {
        return "", err
    }

    // Step 2: Generate response based on analysis
    response, err := caps.LLM(
        "Generate a response based on this analysis: "+analysis,
        input,
    )
    if err != nil {
        return "", err
    }

    return response, nil
}

Tool-Using Handler ​

go
func toolHandler(ctx context.Context, input string, caps *v1beta.Capabilities) (string, error) {
    // Determine if tools are needed
    needsTool, err := caps.LLM("Does this need external data? Reply YES or NO", input)
    if err != nil {
        return "", err
    }

    if strings.Contains(strings.ToUpper(needsTool), "YES") {
        // Call tool
        toolResult, err := caps.Tools.Call("web_search", map[string]interface{}{
            "query": input,
        })
        if err != nil {
            return "", err
        }

        // Generate response with tool result
        return caps.LLM(
            "Answer based on this data: "+toolResult.Content,
            input,
        )
    }

    // No tool needed
    return caps.LLM("You are helpful", input)
}

Memory-Aware Handler ​

go
func memoryHandler(ctx context.Context, input string, caps *v1beta.Capabilities) (string, error) {
    // Search memory for relevant context
    memories, err := caps.Memory.Search(ctx, input, 3)
    if err != nil {
        return "", err
    }

    // Build context from memories
    context := "Relevant information:\n"
    for _, mem := range memories {
        context += fmt.Sprintf("- %s\n", mem.Content)
    }

    // Generate response with context
    response, err := caps.LLM(context, input)
    if err != nil {
        return "", err
    }

    // Store new memory
    caps.Memory.Store(ctx, input+": "+response)

    return response, nil
}

Real-World Examples ​

Validation Handler ​

go
func validationHandler(ctx context.Context, input string, caps *v1beta.Capabilities) (string, error) {
    // Validate input
    if len(input) < 10 {
        return "", fmt.Errorf("input too short")
    }

    if containsOffensiveContent(input) {
        return "I cannot process offensive content", nil
    }

    // Process valid input
    return caps.LLM("You are helpful", input)
}

Rate-Limited Handler ​

go
var (
    rateLimiter = time.NewTicker(time.Second)
    requestChan = make(chan request, 100)
)

type request struct {
    input   string
    caps    *v1beta.Capabilities
    respCh  chan response
}

type response struct {
    content string
    err     error
}

func init() {
    go func() {
        for {
            <-rateLimiter.C
            req := <-requestChan
            resp, err := req.caps.LLM("You are helpful", req.input)
            req.respCh <- response{resp, err}
        }
    }()
}

func rateLimitedHandler(ctx context.Context, input string, caps *v1beta.Capabilities) (string, error) {
    respCh := make(chan response)
    requestChan <- request{input, caps, respCh}
    
    resp := <-respCh
    return resp.content, resp.err
}

Fallback Handler ​

go
func fallbackHandler(ctx context.Context, input string, caps *v1beta.Capabilities) (string, error) {
    // Try primary LLM
    response, err := caps.LLM("You are helpful", input)
    if err == nil {
        return response, nil
    }

    // Log failure
    log.Printf("Primary LLM failed: %v, trying fallback", err)

    // Try fallback (simpler response)
    return generateFallbackResponse(input), nil
}

func generateFallbackResponse(input string) string {
    return "I'm having trouble processing your request. Please try again later."
}

Logging Handler ​

go
func loggingHandler(ctx context.Context, input string, caps *v1beta.Capabilities) (string, error) {
    start := time.Now()
    
    // Log request
    log.Printf("Request: %s", input)

    // Process
    response, err := caps.LLM("You are helpful", input)
    
    duration := time.Since(start)
    
    // Log response
    if err != nil {
        log.Printf("Error after %v: %v", duration, err)
        return "", err
    }
    
    log.Printf("Success in %v, response length: %d", duration, len(response))
    
    return response, nil
}

Advanced Patterns ​

Chained Handlers ​

go
func chainHandlers(handlers ...v1beta.HandlerFunc) v1beta.HandlerFunc {
    return func(ctx context.Context, input string, caps *v1beta.Capabilities) (string, error) {
        currentInput := input
        
        for _, handler := range handlers {
            output, err := handler(ctx, currentInput, caps)
            if err != nil {
                return "", err
            }
            currentInput = output
        }
        
        return currentInput, nil
    }
}

// Usage
agent, _ := v1beta.NewBuilder("Agent").
    WithLLM("openai", "gpt-4").
    WithHandler(chainHandlers(
        validationHandler,
        preprocessHandler,
        mainHandler,
        postprocessHandler,
    )).
    Build()

Conditional Handler ​

go
func conditionalHandler(ctx context.Context, input string, caps *v1beta.Capabilities) (string, error) {
    // Route based on input type
    if isQuestion(input) {
        return handleQuestion(ctx, input, caps)
    } else if isCommand(input) {
        return handleCommand(ctx, input, caps)
    } else {
        return handleStatement(ctx, input, caps)
    }
}

Error Handling ​

go
func robustHandler(ctx context.Context, input string, caps *v1beta.Capabilities) (string, error) {
    // Input validation
    if input == "" {
        return "", v1beta.NewAgentError(
            v1beta.ErrCodeInvalidInput,
            "empty input",
            nil,
        )
    }

    // Try with retries
    var lastErr error
    for i := 0; i < 3; i++ {
        response, err := caps.LLM("You are helpful", input)
        if err == nil {
            return response, nil
        }
        
        lastErr = err
        time.Sleep(time.Second * time.Duration(i+1))
    }

    return "", fmt.Errorf("handler failed after retries: %w", lastErr)
}

Running the Example ​

bash
go get github.com/agenticgokit/agenticgokit/v1beta
export OPENAI_API_KEY="sk-..."
go run main.go

Next Steps ​


Released under the Apache 2.0 License.