用Go打造多轮工具调用AI Agent:从代码到实战
在大模型时代,单纯的文本生成已无法满足复杂需求,能调用工具的AI Agent才是主流。本文将基于Go语言和go-openai库,带你从零构建一个支持天气查询、数学计算、时区时间查询的多轮工具调用Agent,完整覆盖工具定义、参数校验、递归调用全流程。
核心原理:AI Agent为何能调用工具?
AI Agent实现工具调用的核心逻辑,本质是“模型决策+工具执行+上下文管理”的闭环:
- 模型决策:通过向大模型传递工具定义(名称、参数、功能),让模型自主判断是否需要调用工具,以及调用哪个工具。
- 工具执行:解析模型返回的工具调用指令,调用对应的本地/第三方工具,获取执行结果。
- 上下文管理:将工具执行结果回传给模型,作为下一轮决策的依据,实现多轮调用的连贯性。
本文的Agent正是基于此逻辑,通过递归调用实现多步骤工具串联(如“查天气→算温差平均值→查时区时间”)。
项目结构:模块化设计拆解
整个项目按功能划分为6个核心模块,每个模块职责单一,便于维护和扩展:
| 模块 | 核心功能 | 关键代码文件 |
|---|
| 工具参数结构体 | 定义各工具的输入参数格式,用于解析模型返回的调用指令 | WeatherParams/CalcParams/TimeParams |
| 工具实现逻辑 | 封装具体工具的业务逻辑(模拟第三方API调用) | getWeather/calculate/getCurrentTime |
| 工具定义(核心) | 用jsonschema标准化工具参数,传递给大模型 | getOpenAITools() |
| 工具调用执行器 | 解析模型指令,分发并执行对应工具 | executeToolCall() |
| 递归多轮调用 | 管理上下文,控制调用流程,处理模型终止逻辑 | recursiveAgent() |
| 入口函数 | 初始化客户端,构造测试用例,启动Agent | main() |
关键实现:从工具定义到多轮调用
1. 工具参数标准化:用jsonschema约束输入
大模型调用工具的前提,是明确“工具需要什么参数”。通过go-openai/jsonschema库,我们可以标准化参数定义,让模型更精准地生成调用指令。
以天气查询工具为例,参数定义需包含3个核心信息:
- 参数类型:明确是字符串、数字还是对象。
- 参数描述:告知模型参数的含义和格式(如“城市名称,仅支持国内主流城市”)。
- 必传字段:标记哪些参数是必须的,避免模型漏传。
1
2
3
4
5
6
7
8
9
10
11
| // 天气工具参数定义(jsonschema)
weatherCityParam := jsonschema.Definition{
Type: jsonschema.String, // 参数类型:字符串
Description: "城市名称(如北京、上海,仅支持国内主流城市)", // 参数描述
}
// 天气工具整体参数Schema
weatherParamsSchema := jsonschema.Definition{
Type: jsonschema.Object,
Properties: map[string]jsonschema.Definition{"city": weatherCityParam},
Required: []string{"city"}, // 必传字段:city
}
|
同理,数学计算工具需定义expr(表达式)参数,时间查询工具需定义timezone(时区)参数,最终通过getOpenAITools()函数组装成工具列表,传递给大模型。
2. 工具逻辑封装:模拟真实API调用
工具实现需考虑3个关键点:超时控制、业务逻辑、异常处理,确保工具调用稳定可靠。
以天气查询工具getWeather()为例:
- 超时控制:用
context.WithTimeout设置3秒超时,避免工具调用阻塞。 - 业务逻辑:模拟网络延迟(500ms),用预定义的
weatherMap返回天气数据。 - 异常处理:未查询到城市时,返回明确的支持城市列表,提升用户体验。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
| func getWeather(ctx context.Context, city string) (string, error) {
// 3秒超时控制
ctx, cancel := context.WithTimeout(ctx, 3*time.Second)
defer cancel()
// 模拟网络延迟
select {
case <-ctx.Done():
return "", fmt.Errorf("查询超时:%v", ctx.Err())
case <-time.After(500 * time.Millisecond):
}
// 模拟天气数据
weatherMap := map[string]string{
"北京": "晴,20-28℃,微风",
"上海": "多云,18-25℃,东北风3级",
// 更多城市...
}
if weather, ok := weatherMap[city]; ok {
return fmt.Sprintf("[天气工具结果] 城市:%s → %s", city, weather), nil
}
return fmt.Sprintf("[天气工具结果] 未查询到「%s」的天气数据(支持城市:北京、上海、广州、深圳、杭州)", city), nil
}
|