Function Tool Performance Optimization: Timeout, Concurrency, and Resource Management
Table of Contents
- Function Tool Performance Optimization: Timeout, Concurrency, and Resource Management
- Problem: Slow Tools Drag Down Entire Agent
- 1. Timeout Control: Don’t Let Any Tool Block Agent
- 2. Concurrent Calls: Run Multiple Tools Simultaneously
- 3. Connection Pool Management: Reduce TCP Connection Overhead
- 4. Resource Limits: Prevent Tool from Exhausting System Resources
- 5. Monitoring and Debugging: Know How Tools Are Performing
- Next Steps
Function Tool Performance Optimization: Timeout, Concurrency, and Resource Management
A Tool with slow response makes the entire Agent slow. This article covers three optimization directions: timeout control, concurrent calls, connection pool management, making your Agent run robustly in real production environments.
Problem: Slow Tools Drag Down Entire Agent
By default, Tool’s Call() is synchronous. If one Tool takes 10 seconds to return, the entire Agent waits 10 seconds.
User request → Agent → Tool A (3s) → Tool B (10s) → Return to user
↓
Entire Agent stuck here
1. Timeout Control: Don’t Let Any Tool Block Agent
Basic Timeout Implementation
func (myTool) Call(ctx context.Context, input string) (string, error) {
ctx, cancel := context.WithTimeout(ctx, 5*time.Second)
defer cancel()
result := make(chan string, 1)
errCh := make(chan error, 1)
go func() {
// Actual work logic
r, err := doHeavyWork()
if err != nil {
errCh <- err
} else {
result <- r
}
}()
select {
case r := <-result:
return r, nil
case err := <-errCh:
return "", err
case <-ctx.Done():
return "", fmt.Errorf("tool execution timeout after 5s")
}
}
Global Default Timeout
Configure global timeout when creating Agent:
config := &launcher.Config{
AgentLoader: agent.NewSingleLoader(myAgent),
Timeout: 30 * time.Second, // Global timeout 30 seconds
}
Tool-Level Timeout Configuration
Put timeout in Tool’s configuration:
type configurableTool struct {
timeout time.Duration
}
func (t configurableTool) Call(ctx context.Context, input string) (string, error) {
ctx, cancel := context.WithTimeout(ctx, t.timeout)
defer cancel()
// ...
}
2. Concurrent Calls: Run Multiple Tools Simultaneously
If a request needs to call multiple independent Tools, concurrent execution saves time:
Serial: Tool A (2s) + Tool B (3s) + Tool C (4s) = 9s
Parallel: [Tool A (2s), Tool B (3s), Tool C (4s)] = 4s
ADK Go’s Concurrent Tool Calls
ADK Go itself supports calling multiple Tools simultaneously. Just describe in Instruction that parallel calls are OK:
Instruction: "You can call multiple tools simultaneously to fetch information, no need to wait for one to complete before calling the next."
Model automatically decides which Tools can be called in parallel.
Concurrency Within Tool for Batch Data
If one Tool needs to handle multiple requests (e.g., batch book queries):
func (batchTool) Call(ctx context.Context, input string) (string, error) {
var args struct {
Titles []string `json:"titles"`
}
json.Unmarshal([]byte(input), &args)
// Concurrent queries
var wg sync.WaitGroup
results := make([]string, len(args.Titles))
for i, title := range args.Titles {
wg.Add(1)
go func(idx int, t string) {
defer wg.Done()
results[idx] = queryBook(t)
}(i, title)
}
wg.Wait()
return strings.Join(results, "\n"), nil
}
3. Connection Pool Management: Reduce TCP Connection Overhead
Tools often need to call external APIs. Creating new connections each call has handshake overhead. Connection pool reuses connections.
HTTP Client Connection Pool
type httpTool struct {
client *http.Client
}
func newHTTPTool() *httpTool {
return &httpTool{
client: &http.Client{
Transport: &http.Transport{
MaxIdleConns: 100,
MaxIdleConnsPerHost: 10,
IdleConnTimeout: 90 * time.Second,
},
Timeout: 10 * time.Second,
},
}
}
func (t *httpTool) Call(ctx context.Context, input string) (string, error) {
req, _ := http.NewRequestWithContext(ctx, "POST", "https://api.example.com/query", strings.NewReader(input))
resp, err := t.client.Do(req)
if err != nil {
return "", err
}
defer resp.Body.Close()
// ...
}
Database Connection Pool
If Tool queries database:
import "database/sql"
import _ "github.com/go-sql-driver/mysql"
type dbTool struct {
db *sql.DB
}
func newDBTool() *dbTool {
db, _ := sql.Open("mysql", "user:password@tcp(localhost:3306)/mydb?parseTime=true")
db.SetMaxOpenConns(25)
db.SetMaxIdleConns(5)
db.SetConnMaxLifetime(5 * time.Minute)
return &dbTool{db: db}
}
4. Resource Limits: Prevent Tool from Exhausting System Resources
Concurrency Limit
Use semaphore to limit concurrent executions:
var (
maxConcurrent = 10
semaphore = make(chan struct{}, maxConcurrent)
)
func (t *myTool) Call(ctx context.Context, input string) (string, error) {
select {
case semaphore <- struct{}{}:
defer func() { <-semaphore }()
case <-ctx.Done():
return "", fmt.Errorf("too many concurrent requests")
}
// Execute actual logic
}
Memory Limit
Go’s runtime can monitor goroutine count:
import "runtime"
func checkResources() error {
var m runtime.MemStats
runtime.ReadMemStats(&m)
if m.NumGoroutine > 10000 {
return fmt.Errorf("too many goroutines: %d", m.NumGoroutine)
}
return nil
}
5. Monitoring and Debugging: Know How Tools Are Performing
Log Tool Execution Time
func (t *myTool) Call(ctx context.Context, input string) (string, error) {
start := time.Now()
result, err := doWork()
duration := time.Since(start)
if err != nil {
log.Printf("Tool %s failed after %v: %v", t.Name(), duration, err)
} else {
log.Printf("Tool %s succeeded in %v", t.Name(), duration)
}
return result, err
}
Metrics Reporting
Integrate Prometheus:
import "github.com/prometheus/client_golang/prometheus"
var toolDuration = prometheus.NewHistogramVec(
prometheus.NewHistogramOpts{
Name: "tool_execution_duration_seconds",
Help: "Tool execution duration",
Buckets: []float64{0.1, 0.5, 1, 3, 5, 10},
},
[]string{"tool_name", "status"},
)
func init() {
prometheus.Register(toolDuration)
}
func (t *myTool) Call(ctx context.Context, input string) (string, error) {
start := time.Now()
result, err := doWork()
toolDuration.WithLabelValues(t.Name(), statusFromError(err)).Observe(time.Since(start).Seconds())
return result, err
}
Next Steps
Performance optimization done. Next, MCP—Model Context Protocol, ADK Go’s mainstream way of connecting external services, can connect any service that exposes MCP interface to Agent.
← Function Tool Basics | MCP Server Integration →
Follow “Mengshou Programming” on WeChat for more Go ADK hands-on tutorials, weekly updates on Go / AI programming 实战干货.
