• 主页
  • 架构
  • 编程语言
  • 数据存储
  • 网络
  • VMware
  • 服务器
  • 组网
  • AI
  • 算法系列
  • 设计模式
  • 读书笔记
  • 思考
  • 工具
  • 其它技术

  • 主页
  • 架构
  • 编程语言
  • 数据存储
  • 网络
  • VMware
  • 服务器
  • 组网
  • AI
  • 算法系列
  • 设计模式
  • 读书笔记
  • 思考
  • 工具
  • 其它技术

模型Function Call

2025-06-14

工具调用(Function Calling)是一种将大模型与外部工具和 API 相连的关键功能,作为自然语言与信息接口之间的“翻译官”,它能够将用户的自然语言请求智能地转化为对特定工具或 API 的调用,从而高效满足用户的特定需求。

简单来说,大模型的知识是固定的,你问他李白有哪些诗它肯定知道,但是你问它现在北京天气怎么样,它就不知道了。但大模型有推理能力,如果你问它北京天气怎么样的同时,还告诉他有哪些API可以使用,它就会选择对应的API。这样我们可以用工程手段调用这些API,然后将API结果再给大模型,由大模型进行总结了。

前置操作

首先我们得开通个大模型,这里我用火山的模型来演示。吐槽一句,我感觉各家的云平台操作起来都听不顺畅的,找个内容得四处翻。

登录:https://www.volcengine.com/

进入控制台:https://console.volcengine.com/ark/region:ark+cn-beijing/overview?briefPage=0&briefType=introduce&projectName=undefined&type=new

开通模型:我选择开通的是豆包-1.5-pro-32k

image-20250614220842895

查看模型细节:

image-20250614221035762

获取API key和使用demo,大家shell里执行export ARK_API_KEY=”**”就能执行我下面的代码了

image-20250614221158269

代码

看代码前,希望大家能先看一下这两个文档

  1. Chat接口的入参和出参:https://www.volcengine.com/docs/82379/1494384?redirect=1
  2. Function Call的调用方式:https://www.volcengine.com/docs/82379/1262342 ,这个文档主要使用的是非流式方式,而且Go的demo很少,强烈建议加个流式的demo
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
package main

import (
"context"
"encoding/json"
"fmt"
"io"
"os"

"github.com/volcengine/volcengine-go-sdk/service/arkruntime"
"github.com/volcengine/volcengine-go-sdk/service/arkruntime/model"
"github.com/volcengine/volcengine-go-sdk/volcengine"
)

type WeatherArgs struct {
Location string `json:"location"`
Unit string `json:"unit,omitempty"` // omitempty 允许 unit 为可选
}

// 目标工具
func getCurrentWeather(location string, unit string) string {
if unit == "" {
unit = "摄氏度" // 默认单位
}
// 此处为示例,返回模拟的天气数据
return fmt.Sprintf("%s今天天气晴朗,温度 25 %s。", location, unit)
}

func MarshStr(data interface{}) string {
d, _ := json.Marshal(data)
return string(d)
}

func main() {
// 从环境变量中获取 API Key,请确保已设置 ARK_API_KEY
apiKey := os.Getenv("ARK_API_KEY")
if apiKey == "" {
fmt.Println("错误:请设置 ARK_API_KEY 环境变量。")
return
}

client := arkruntime.NewClientWithApiKey(apiKey)
ctx := context.Background()

// 初始化消息列表
messages := []*model.ChatCompletionMessage{
{
Role: model.ChatMessageRoleSystem,
Content: &model.ChatCompletionMessageContent{
StringValue: volcengine.String("你是豆包,是由字节跳动开发的 AI 人工智能助手"),
},
},
{
Role: model.ChatMessageRoleUser,
Content: &model.ChatCompletionMessageContent{
StringValue: volcengine.String("先帮我查看一下北京的天气,然后再查看一下上海的天气"),
},
},
}

// 步骤 1: 定义工具
tools := []*model.Tool{
{
Type: model.ToolTypeFunction,
Function: &model.FunctionDefinition{
Name: "get_current_weather",
Description: "获取指定地点的天气信息",
Parameters: map[string]interface{}{
"type": "object",
"properties": map[string]interface{}{
"location": map[string]interface{}{
"type": "string",
"description": "地点的位置信息,例如北京、上海",
},
"unit": map[string]interface{}{
"type": "string",
"enum": []string{
"摄氏度",
"华氏度",
},
"description": "温度单位",
},
},
"required": []string{"location"},
},
},
},
}
count := 0
for {
if count > 5 {
return
}
count++
// 步骤 2: 发起流式模型请求
req := model.CreateChatCompletionRequest{
Model: "doubao-1-5-pro-32k-250115",
Messages: messages,
Tools: tools,
Stream: volcengine.Bool(true),
}

fmt.Println("----- streaming request -----")
stream, err := client.CreateChatCompletionStream(ctx, req)
//fmt.Print(2)
if err != nil {
fmt.Printf("stream chat error: %v\n", err)
return
}
defer stream.Close()
content := ""
toolName := ""
toolArgs := ""
toolCallId := ""
//fmt.Print(11)
for {
recv, err := stream.Recv()
if err == io.EOF {
break
}
if err != nil {
fmt.Printf("Stream chat error: %v\n", err)
return
}

if len(recv.Choices) > 0 {
respMsg := recv.Choices[0].Delta
// data, _ := json.Marshal(recv.Choices[0])
// fmt.Println(string(data))

// fmt.Print("content: "+recv.Choices[0].Delta.Content, "finishreason: "+recv.Choices[0].FinishReason, "\n")

//fmt.Println(recv.Choices[0].Delta.ToolCalls[0], recv.Choices[0].Delta.Content)
//展示模型中间过程的回复内容
if respMsg.Content != "" {
content += respMsg.Content
fmt.Print(respMsg.Content)
}

//处理工具信息
if len(recv.Choices[0].Delta.ToolCalls) > 0 {
toolCall := recv.Choices[0].Delta.ToolCalls[0]
if toolCall.Function.Name != "" {
toolName = toolCall.Function.Name
}
if toolCall.Function.Arguments != "" {
toolArgs += toolCall.Function.Arguments
}
if toolCall.ID != "" {
toolCallId = toolCall.ID
}
}
//处理非fc的结束
if recv.Choices[0].FinishReason != "" && recv.Choices[0].FinishReason != model.FinishReasonToolCalls {
messages = append(messages, &model.ChatCompletionMessage{
Role: model.ChatMessageRoleAssistant,
Content: &model.ChatCompletionMessageContent{
StringValue: volcengine.String(content),
},
},
)
fmt.Println("正常finish", MarshStr(messages))
return //真正结束
}
//处理fc的结束
if recv.Choices[0].FinishReason == model.FinishReasonToolCalls && len(toolName) > 0 {
// 将模型的回复(包含工具调用请求)添加到消息历史中
messages = append(messages, &model.ChatCompletionMessage{
Role: model.ChatMessageRoleAssistant,
Content: &model.ChatCompletionMessageContent{
StringValue: volcengine.String(content),
},
ToolCalls: []*model.ToolCall{
{
ID: toolCallId,
Function: model.FunctionCall{
Name: toolName,
Arguments: toolArgs,
},
Type: model.ToolTypeFunction,
},
},
})

fmt.Printf("\n模型尝试调用工具: %s, ID: %s", toolName, toolCallId)
fmt.Println(" 参数:", toolArgs)

var toolResult string
if toolName == "get_current_weather" {
// 调用外部工具
var args WeatherArgs
err := json.Unmarshal([]byte(toolArgs), &args)
if err != nil {
fmt.Printf("解析工具参数错误 (%s): %v", toolName, err)
toolResult = fmt.Sprintf("解析参数失败: %v", err)
} else {
toolResult = getCurrentWeather(args.Location, args.Unit)
fmt.Println(" 工具执行结果:", toolResult)
}

// 回填工具结果
messages = append(messages, &model.ChatCompletionMessage{
Role: model.ChatMessageRoleTool,
Content: &model.ChatCompletionMessageContent{StringValue: volcengine.String(toolResult)},
ToolCallID: toolCallId,
Name: &toolName,
})
}

fmt.Println("\n--- 下一轮对话 ---", MarshStr(messages))
break
}
}
}
//break
}
}

执行结果如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
➜  my go run main.go
----- streaming request -----
用户需要查询北京和上海的天气,调用 get_current_weather 函数分别获取两地的天气信息。
模型尝试调用工具: get_current_weather, ID: call_pjov7bifbrdpv7e8kncsmhjz 参数: {
"location": "北京",
"unit": "摄氏度"
}
工具执行结果: 北京今天天气晴朗,温度 25 摄氏度。

--- 下一轮对话 --- [{"role":"system","content":"你是豆包,是由字节跳动开发的 AI 人工智能助手","name":null},{"role":"user","content":"先帮我查看一下北京的天气,然后再查看一下上海的天气","name":null},{"role":"assistant","content":"用户需要查询北京和上海的天气,调用 get_current_weather 函数分别获取两地的天气信息。","name":null,"tool_calls":[{"id":"call_pjov7bifbrdpv7e8kncsmhjz","type":"function","function":{"name":"get_current_weather","arguments":" {\n \"location\": \"北京\",\n \"unit\": \"摄氏度\"\n }"}}]},{"role":"tool","content":"北京今天天气晴朗,温度 25 摄氏度。","name":"get_current_weather","tool_call_id":"call_pjov7bifbrdpv7e8kncsmhjz"}]
----- streaming request -----

模型尝试调用工具: get_current_weather, ID: call_0qfxc8xe5h6m74pf9olwptao 参数: {"location": "上海", "unit": "摄氏度"}
工具执行结果: 上海今天天气晴朗,温度 25 摄氏度。

--- 下一轮对话 --- [{"role":"system","content":"你是豆包,是由字节跳动开发的 AI 人工智能助手","name":null},{"role":"user","content":"先帮我查看一下北京的天气,然后再查看一下上海的天气","name":null},{"role":"assistant","content":"用户需要查询北京和上海的天气,调用 get_current_weather 函数分别获取两地的天气信息。","name":null,"tool_calls":[{"id":"call_pjov7bifbrdpv7e8kncsmhjz","type":"function","function":{"name":"get_current_weather","arguments":" {\n \"location\": \"北京\",\n \"unit\": \"摄氏度\"\n }"}}]},{"role":"tool","content":"北京今天天气晴朗,温度 25 摄氏度。","name":"get_current_weather","tool_call_id":"call_pjov7bifbrdpv7e8kncsmhjz"},{"role":"assistant","content":"","name":null,"tool_calls":[{"id":"call_0qfxc8xe5h6m74pf9olwptao","type":"function","function":{"name":"get_current_weather","arguments":" {\"location\": \"上海\", \"unit\": \"摄氏度\"}"}}]},{"role":"tool","content":"上海今天天气晴朗,温度 25 摄氏度。","name":"get_current_weather","tool_call_id":"call_0qfxc8xe5h6m74pf9olwptao"}]
----- streaming request -----
北京今天天气晴朗,温度 25 摄氏度。上海今天天气晴朗,温度 25 摄氏度。 正常finish [{"role":"system","content":"你是豆包,是由字节跳动开发的 AI 人工智能助手","name":null},{"role":"user","content":"先帮我查看一下北京的天气,然后再查看一下上海的天气","name":null},{"role":"assistant","content":"用户需要查询北京和上海的天气,调用 get_current_weather 函数分别获取两地的天气信息。","name":null,"tool_calls":[{"id":"call_pjov7bifbrdpv7e8kncsmhjz","type":"function","function":{"name":"get_current_weather","arguments":" {\n \"location\": \"北京\",\n \"unit\": \"摄氏度\"\n }"}}]},{"role":"tool","content":"北京今天天气晴朗,温度 25 摄氏度。","name":"get_current_weather","tool_call_id":"call_pjov7bifbrdpv7e8kncsmhjz"},{"role":"assistant","content":"","name":null,"tool_calls":[{"id":"call_0qfxc8xe5h6m74pf9olwptao","type":"function","function":{"name":"get_current_weather","arguments":" {\"location\": \"上海\", \"unit\": \"摄氏度\"}"}}]},{"role":"tool","content":"上海今天天气晴朗,温度 25 摄氏度。","name":"get_current_weather","tool_call_id":"call_0qfxc8xe5h6m74pf9olwptao"},{"role":"assistant","content":"北京今天天气晴朗,温度 25 摄氏度。上海今天天气晴朗,温度 25 摄氏度。 ","name":null}]

我的问题是:先帮我查看一下北京的天气,然后再查看一下上海的天气。

我同时告诉模型,有获取天气的API-get_current_weather可以调用。

第一次:信息给到模型后,模型认为需要分别获取两地的天气,所以先返回了查询北京的参数,并决定调用get_current_weather进行查询。

{
“location”: “北京”,
“unit”: “摄氏度”
}

我代码里收到是调用工具,开始调用API,并将结果添加到msg中。

第二次:带着上次的信息给模型后,模型推理要查上海的天气了,同理我完成了一次API调用

第三次:带着两个地区的天气信息我给模型,模型认为无需调用工具,正常结束,这次会话就完全完成了。

我们看一下最终的消息体:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
[
{
"role": "system",
"content": "你是豆包,是由字节跳动开发的 AI 人工智能助手",
"name": null
},
{
"role": "user",
"content": "先帮我查看一下北京的天气,然后再查看一下上海的天气",
"name": null
},
{
"role": "assistant",
"content": "用户需要查询北京和上海的天气,调用 get_current_weather 函数分别获取两地的天气信息。",
"name": null,
"tool_calls": [
{
"id": "call_pjov7bifbrdpv7e8kncsmhjz",
"type": "function",
"function": {
"name": "get_current_weather",
"arguments": " {\n \"location\": \"北京\",\n \"unit\": \"摄氏度\"\n }"
}
}
]
},
{
"role": "tool",
"content": "北京今天天气晴朗,温度 25 摄氏度。",
"name": "get_current_weather",
"tool_call_id": "call_pjov7bifbrdpv7e8kncsmhjz"
},
{
"role": "assistant",
"content": "",
"name": null,
"tool_calls": [
{
"id": "call_0qfxc8xe5h6m74pf9olwptao",
"type": "function",
"function": {
"name": "get_current_weather",
"arguments": " {\"location\": \"上海\", \"unit\": \"摄氏度\"}"
}
}
]
},
{
"role": "tool",
"content": "上海今天天气晴朗,温度 25 摄氏度。",
"name": "get_current_weather",
"tool_call_id": "call_0qfxc8xe5h6m74pf9olwptao"
},
{
"role": "assistant",
"content": "北京今天天气晴朗,温度 25 摄氏度。上海今天天气晴朗,温度 25 摄氏度。 ",
"name": null
}
]

FC在我眼里就像是人的四肢,模型是大脑,只有这样才能发挥更大的作用。如果加上硬件,那就是配备了钛合金盔甲。

疑问

流式调用是为了能够尽快将模型返回内容返回给前端,如果是最终结果肯定没什么问题,但是如果是fc的呢?有办法即使用流式方案,同时只把tool相关的内容返回,content的内容不返回吗?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
➜  my go run main.go
----- streaming request -----
{"index":0,"delta":{"role":"assistant"},"finish_reason":null}
{"index":0,"delta":{"role":"assistant"},"finish_reason":null}
{"index":0,"delta":{"role":"assistant"},"finish_reason":null}
{"index":0,"delta":{"content":"用户","role":"assistant"},"finish_reason":null}
{"index":0,"delta":{"content":"想","role":"assistant"},"finish_reason":null}
{"index":0,"delta":{"content":"了解","role":"assistant"},"finish_reason":null}
{"index":0,"delta":{"content":"北京","role":"assistant"},"finish_reason":null}
{"index":0,"delta":{"content":"和","role":"assistant"},"finish_reason":null}
{"index":0,"delta":{"content":"上海","role":"assistant"},"finish_reason":null}
{"index":0,"delta":{"content":"的","role":"assistant"},"finish_reason":null}
{"index":0,"delta":{"content":"天气","role":"assistant"},"finish_reason":null}
{"index":0,"delta":{"content":",","role":"assistant"},"finish_reason":null}
{"index":0,"delta":{"content":"调用","role":"assistant"},"finish_reason":null}
{"index":0,"delta":{"content":" get","role":"assistant"},"finish_reason":null}
{"index":0,"delta":{"content":"_current","role":"assistant"},"finish_reason":null}
{"index":0,"delta":{"content":"_","role":"assistant"},"finish_reason":null}
{"index":0,"delta":{"content":"weather","role":"assistant"},"finish_reason":null}
{"index":0,"delta":{"content":" ","role":"assistant"},"finish_reason":null}
{"index":0,"delta":{"content":"函数","role":"assistant"},"finish_reason":null}
{"index":0,"delta":{"content":"获取","role":"assistant"},"finish_reason":null}
{"index":0,"delta":{"content":"北京","role":"assistant"},"finish_reason":null}
{"index":0,"delta":{"content":"和","role":"assistant"},"finish_reason":null}
{"index":0,"delta":{"content":"上海","role":"assistant"},"finish_reason":null}
{"index":0,"delta":{"content":"的","role":"assistant"},"finish_reason":null}
{"index":0,"delta":{"content":"天气","role":"assistant"},"finish_reason":null}
{"index":0,"delta":{"content":"信息","role":"assistant"},"finish_reason":null}
{"index":0,"delta":{"content":"。","role":"assistant"},"finish_reason":null}
{"index":0,"delta":{"role":"assistant"},"finish_reason":null}
{"index":0,"delta":{"role":"assistant"},"finish_reason":null}
{"index":0,"delta":{"role":"assistant"},"finish_reason":null}
{"index":0,"delta":{"role":"assistant"},"finish_reason":null}
{"index":0,"delta":{"role":"assistant"},"finish_reason":null}
{"index":0,"delta":{"role":"assistant"},"finish_reason":null}
{"index":0,"delta":{"role":"assistant"},"finish_reason":null}
{"index":0,"delta":{"role":"assistant","tool_calls":[{"id":"call_d17ewqycgbxdfj65zqycj3bu","type":"function","function":{"name":"get_current_weather"},"index":0}]},"finish_reason":null}
{"index":0,"delta":{"role":"assistant","tool_calls":[{"id":"","type":"","function":{"arguments":" {"},"index":0}]},"finish_reason":null}
{"index":0,"delta":{"role":"assistant","tool_calls":[{"id":"","type":"","function":{"arguments":"\n"},"index":0}]},"finish_reason":null}
{"index":0,"delta":{"role":"assistant","tool_calls":[{"id":"","type":"","function":{"arguments":" "},"index":0}]},"finish_reason":null}
{"index":0,"delta":{"role":"assistant","tool_calls":[{"id":"","type":"","function":{"arguments":" \""},"index":0}]},"finish_reason":null}
{"index":0,"delta":{"role":"assistant","tool_calls":[{"id":"","type":"","function":{"arguments":"location"},"index":0}]},"finish_reason":null}
{"index":0,"delta":{"role":"assistant","tool_calls":[{"id":"","type":"","function":{"arguments":"\":"},"index":0}]},"finish_reason":null}
{"index":0,"delta":{"role":"assistant","tool_calls":[{"id":"","type":"","function":{"arguments":" \""},"index":0}]},"finish_reason":null}
{"index":0,"delta":{"role":"assistant","tool_calls":[{"id":"","type":"","function":{"arguments":"北京"},"index":0}]},"finish_reason":null}
{"index":0,"delta":{"role":"assistant","tool_calls":[{"id":"","type":"","function":{"arguments":","},"index":0}]},"finish_reason":null}
{"index":0,"delta":{"role":"assistant","tool_calls":[{"id":"","type":"","function":{"arguments":"上海"},"index":0}]},"finish_reason":null}
{"index":0,"delta":{"role":"assistant","tool_calls":[{"id":"","type":"","function":{"arguments":"\","},"index":0}]},"finish_reason":null}
{"index":0,"delta":{"role":"assistant","tool_calls":[{"id":"","type":"","function":{"arguments":"\n"},"index":0}]},"finish_reason":null}
{"index":0,"delta":{"role":"assistant","tool_calls":[{"id":"","type":"","function":{"arguments":" "},"index":0}]},"finish_reason":null}
{"index":0,"delta":{"role":"assistant","tool_calls":[{"id":"","type":"","function":{"arguments":" \""},"index":0}]},"finish_reason":null}
{"index":0,"delta":{"role":"assistant","tool_calls":[{"id":"","type":"","function":{"arguments":"unit"},"index":0}]},"finish_reason":null}
{"index":0,"delta":{"role":"assistant","tool_calls":[{"id":"","type":"","function":{"arguments":"\":"},"index":0}]},"finish_reason":null}
{"index":0,"delta":{"role":"assistant","tool_calls":[{"id":"","type":"","function":{"arguments":" \""},"index":0}]},"finish_reason":null}
{"index":0,"delta":{"role":"assistant","tool_calls":[{"id":"","type":"","function":{"arguments":"摄氏度"},"index":0}]},"finish_reason":null}
{"index":0,"delta":{"role":"assistant","tool_calls":[{"id":"","type":"","function":{"arguments":"\""},"index":0}]},"finish_reason":null}
{"index":0,"delta":{"role":"assistant","tool_calls":[{"id":"","type":"","function":{"arguments":"\n"},"index":0}]},"finish_reason":null}
{"index":0,"delta":{"role":"assistant","tool_calls":[{"id":"","type":"","function":{"arguments":" "},"index":0}]},"finish_reason":null}
{"index":0,"delta":{"role":"assistant","tool_calls":[{"id":"","type":"","function":{"arguments":" }"},"index":0}]},"finish_reason":null}
{"index":0,"delta":{"role":"assistant","tool_calls":[{"id":"","type":"","function":{},"index":0}]},"finish_reason":null}
{"index":0,"delta":{"role":"assistant"},"finish_reason":"tool_calls"}

资料

https://www.volcengine.com/docs/82379/1494384?redirect=1

https://www.volcengine.com/docs/82379/1262342#%E6%B5%81%E5%BC%8F%E8%BE%93%E5%87%BA%E9%80%82%E9%85%8D

https://www.volcengine.com/docs/82379/1449722

扫一扫,分享到微信

微信分享二维码
如何判断一个项目是否有问题
Go的优雅退出
© 2025 John Doe
Hexo Theme Yilia by Litten