跳到主要内容

进阶:工具调用

工具调用(Tool Calling / Function Calling)提供给大模型强大而且灵活的获取外部数据和执行操作的能力。

本章将详细介绍如何实现自定义工具。

工具调用流程:

工具调用需要应用与模型进行多轮对话。包含一下几个步骤:

  1. 向模型发送请求,包含可调用的工具
  2. 接收模型返回的工具调用
  3. 使用工具调用提供的输入,在应用程序执行代码
  4. 将执行结果作为第二次请求发送给模型
  5. 接收模型的最终响应(或更多工具调用)

如图所示:

Agent
Agent
大模型
大模型
1. 工具定义:getWeather
1. 工具定义:getWeather
杭州今天天气怎么样
杭州今天天气怎么样
2. 工具调用
2. 工具调用
getWeather("杭州")
getWeather("杭州")
执行 getWeather("杭州")
执行 getWeather("杭州")
{"temperature": 30}
{"temperature": 30}
4. 第二次请求
4. 第二次请求
{"temperature":30}
{"temperature":30}
5. 最终响应
5. 最终响应
"杭州目前气温三十度"
"杭州目前气温三十度"
3
3
Text is not SVG - cannot display

实现

定义工具

打开 cmd/llm.go 文件,添加以下代码。

首先定义天气查询函数:

llm.go
func GetWeather(location string) string {

weatherData := map[string]interface{}{
"location": location,
"temperature": 30,
"unit": "Celsius",
"forecast": "Sunny",
"humidity": 35,
}

result, _ := json.Marshal(weatherData)
return string(result)
}

描述工具的名称、功能和参数格式:

// 定义天气查询工具
var weatherDefinition = openai.FunctionDefinition{
Name: "get_weather",
Description: "Get the current weather in a given location",
Parameters: json.RawMessage(`{
"type": "object",
"properties": {
"location": {
"type": "string",
"description": "The city to get the weather for, e.g. 杭州"
}
},
"required": ["location"]
}`),
}

字段说明:
  • Name: 工具的名称,用于识别该调用哪个工具。
  • Description: 工具的功能描述,LLM 会根据描述决定是否调用该工具。
  • Parameters: 使用 JSON schema 定义参数的名称,类型,以及描述。

定义参数结构体,用于解析 LLM 返回的参数:

llm.go
// WeatherRequest 天气查询请求参数
type WeatherRequest struct {
Location string `json:"location"`
}

将工具添加到请求中

在发送给 LLM 的请求中添加工具定义:

llm.go
request := openai.ChatCompletionRequest{
Model: model,
Messages: h.messages,
Temperature: 0.7,
Stream: true,
Tools: []openai.Tool{
... // 其他工具
{
Type: openai.ToolTypeFunction,
Function: &weatherDefinition,
},
},
}

处理工具调用

在大模型返回的响应中,会包含工具调用名称和参数。由于我们使用流模式,参数会分多个片段发送。

为什么需要收集参数?

在流式模式下,工具调用可能分多次发送:

  1. 第一次:{"location":
  2. 第二次:"杭州"
  3. 第三次:}

需要将所有片段拼接成完整的 JSON:{"location":"杭州"}

初始化参数收集器:

在流处理循环开始前,初始化一个 Map 用于收集参数:

llm.go
// 用 Map 收集工具调用参数(支持多个并发工具调用)
toolCallsMap := make(map[int]*openai.ToolCall)

// 处理流式响应
for {
response, err := stream.Recv()
// ... 错误处理
}

识别并收集天气工具调用:

保存工具调用,并收集参数:

llm.go
// 添加天气工具的参数收集
if toolCall.Function.Name == "get_weather" {
// 首次接收到该工具调用
toolCallsMap[*toolCall.Index] = &toolCall
}

if toolCall.Function.Name == "" {
// 参数片段,追加到对应的工具调用
toolCallsMap[*toolCall.Index].Function.Arguments += toolCall.Function.Arguments
}

执行工具再次请求

处理工具调用:

当流结束时,执行收集到的所有工具调用:

if isFinished {
// 检查是否有工具调用
if len(toolCallsMap) > 0 {
// 1. 构建 Assistant 消息
var assistantToolCalls []openai.ToolCall
for _, toolCall := range toolCallsMap {
assistantToolCalls = append(assistantToolCalls, *toolCall)
}

assistantMsg := openai.ChatCompletionMessage{
Role: openai.ChatMessageRoleAssistant,
Content: fullResponse,
ToolCalls: assistantToolCalls,
}
h.messages = append(h.messages, assistantMsg)

// 2. 遍历,可能有多个工具调用
for _, toolCall := range toolCallsMap {
var req WeatherRequest

// 解析参数
if err := json.Unmarshal([]byte(toolCall.Function.Arguments), &req); err == nil {
// 调用实际的天气查询函数
weather := GetWeather(req.Location)

// 将工具结果添加到对话历史
h.messages = append(h.messages, openai.ChatCompletionMessage{
Role: openai.ChatMessageRoleTool,
Content: weather,
ToolCallID: toolCall.ID,
})
}
}

// 3. 清空已处理的工具调用
toolCallsMap = make(map[int]*openai.ToolCall)

// 4. 更新请求消息(包含工具结果)
request.Messages = h.messages

// 5. 再次请求 LLM(让它基于工具结果生成回复)
stream, err = h.client.CreateChatCompletionStream(h.ctx, request)
if err != nil {
h.logger.WithError(err).Error("Error creating chat completion stream")
}
continue // 继续处理新的流
}
break // 没有工具调用,结束循环
}

测试效果

运行程序

确保 RustPBX 已启动,然后运行更新后的客户端:

cd cmd
go run . \
--endpoint ws://127.0.0.1:8080 \
--tts aliyun --speaker longyumi_v2 \
--asr aliyun \
--openai-key your_dashscope_api_key \
--model qwen-plus \
--openai-endpoint https://dashscope.aliyuncs.com/compatible-mode/v1 \
--greeting "你好,有什么可以帮你的吗"

测试对话

尝试以下问题:

问题预期行为
"杭州今天天气怎么样?"✅ 调用 get_weather("杭州")
"北京天气"✅ 调用 get_weather("北京")
"你好"❌ 不调用工具,直接回复
"1+1等于几?"❌ 不调用工具,LLM 直接计算

音频回复示例:

完整代码

完整的实现代码可以在这里查看: https://gist.github.com/yeoleobun/4b5707f2c23ac587b18d365019147a9a

总结

通过本章,你学会了:

  1. 什么是工具调用以及为什么需要它
  2. 如何定义工具函数和规格
  3. 如何在流式模式下收集工具参数
  4. 如何执行工具并将结果返回给 LLM

工具调用让你的 AI Agent 从"聊天机器人"升级为"能做事的助手",是构建实用 AI 应用的关键技术!

下一步