工具调用(Function Calling)是一种将大模型与外部工具和 API 相连的关键功能,作为自然语言与信息接口之间的"翻译官",它能够将用户的自然语言请求智能地转化为对特定工具或 API 的调用,从而高效满足用户的特定需求。
简单来说,大模型的知识是固定的,你问他李白有哪些诗它肯定知道,但是你问它现在北京天气怎么样,它就不知道了。但大模型有推理能力,如果你问它北京天气怎么样的同时,还告诉他有哪些API可以使用,它就会选择对应的API。这样我们可以用工程手段调用这些API,然后将API结果再给大模型,由大模型进行总结了。
前置操作
首先我们得开通个大模型,这里我用火山的模型来演示。吐槽一句,我感觉各家的云平台操作起来都听不顺畅的,找个内容得四处翻。
进入控制台:console.volcengine.com/ark/region:...
开通模型:我选择开通的是豆包-1.5-pro-32k

查看模型细节:

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

代码
看代码前,希望大家能先看一下这两个文档
- Chat接口的入参和出参:www.volcengine.com/docs/82379/...
- Function Call的调用方式:www.volcengine.com/docs/82379/... ,这个文档主要使用的是非流式方式,而且Go的demo很少,强烈建议加个流式的demo
go
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
}
}
执行结果如下:
swift
➜ 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调用
第三次:带着两个地区的天气信息我给模型,模型认为无需调用工具,正常结束,这次会话就完全完成了。
我们看一下最终的消息体:
json
[
{
"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的内容不返回吗?
ruby
➜ 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"}
资料
www.volcengine.com/docs/82379/...
www.volcengine.com/docs/82379/...
www.volcengine.com/docs/82379/...
最后
大家如果喜欢我的文章,可以关注我的公众号(程序员麻辣烫)
我的个人博客为:shidawuhen.github.io/
往期文章回顾: