Function Tool 性能优化:超时、并发与资源管理
详解 Function Tool 的性能优化策略:超时控制、并发调用、连接池管理,以及如何避免慢 Tool 影响整个 Agent 的响应。
Function Tool 性能优化:超时、并发与资源管理
一个 Tool 响应慢,整个 Agent 就会变慢。这篇讲三个优化方向:超时控制、并发调用、连接池管理,让 Agent 在生产环境里跑得稳健。
问题:慢 Tool 会拖累整个 Agent
默认情况下,Tool 的 Call() 是同步执行的。如果一个 Tool 需要 10 秒返回,整个 Agent 就要等 10 秒。
用户请求 → Agent → Tool A(3秒)→ Tool B(10秒)→ 返回用户
↓
整个 Agent 在这里卡住
一、超时控制:不让任何一个 Tool 把 Agent 卡死
基础超时实现
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() {
// 实际的工作逻辑
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")
}
}
全局默认超时
可以在创建 Agent 时配置全局超时:
config := &launcher.Config{
AgentLoader: agent.NewSingleLoader(myAgent),
Timeout: 30 * time.Second, // 全局超时 30 秒
}
Tool 级别的超时配置
把超时时间放到 Tool 的配置里:
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()
// ...
}
二、并发调用:让多个 Tool 同时跑
如果一个请求需要调用多个独立的 Tool,可以并发执行节省时间:
串行:Tool A(2秒)+ Tool B(3秒)+ Tool C(4秒)= 9秒
并行:[Tool A(2秒), Tool B(3秒), Tool C(4秒)] = 4秒
ADK Go 的并发 Tool 调用
ADK Go 本身支持同时调用多个 Tool。只需要在 Instruction 里说明可以并行调用:
Instruction: "你可以同时调用多个工具获取信息,不需要等一个完成再调用下一个。"
Model 会自动决定哪些 Tool 可以并行调用。
在 Tool 内部并发处理批量数据
如果一个 Tool 需要处理多个请求(比如批量查书):
func (batchTool) Call(ctx context.Context, input string) (string, error) {
var args struct {
Titles []string `json:"titles"`
}
json.Unmarshal([]byte(input), &args)
// 并发查询
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
}
三、连接池管理:减少 TCP 连接开销
Tool 往往需要调用外部 API。每次调用都新建连接会有建立握手的开销。连接池复用连接:
HTTP 客户端连接池
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()
// ...
}
数据库连接池
如果 Tool 查数据库:
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}
}
四、资源限制:防止 Tool 耗尽系统资源
并发数限制
用信号量限制同时执行的并发数:
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")
}
// 执行实际逻辑
}
内存限制
Go 的 runtime 可以监控 goroutine 数量:
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
}
五、监控与调试:知道 Tool 跑得怎么样
日志 Tool 执行时间
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
}
指标上报
接入 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
}
下一步
性能优化做完了,接下来看 MCP——Model Context Protocol,这是 ADK Go 连接外部服务的主流方式,可以把任何暴露了 MCP 接口的服务接入 Agent。
← Function Tool 编写基础 | MCP Server 接入实战 →
想跟着学更多 Go ADK 实战?关注「全栈之巅-梦兽编程」公众号,每周更新 Go / AI 编程实战干货。
