模型Function Call

工具调用(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

在这里插入图片描述

查看模型细节:

在这里插入图片描述

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

在这里插入图片描述

代码

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

  1. Chat接口的入参和出参:https://www.volcengine.com/docs/82379/1494384?redirect=1
  2. Function Call的调用方式:https://www.volcengine.com/docs/82379/1262342 ,这个文档主要使用的是非流式方式,而且Go的demo很少,强烈建议加个流式的demo
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
	}
}

执行结果如下:

➜  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调用

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

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

[
    {
        "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的内容不返回吗?

➜  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

最后

大家如果喜欢我的文章,可以关注我的公众号(程序员麻辣烫)

我的个人博客为:https://shidawuhen.github.io/

往期文章回顾:

  1. 设计模式

  2. 招聘

  3. 思考

  4. 存储

  5. 算法系列

  6. 读书笔记

  7. 小工具

  8. 架构

  9. 网络

  10. Go语言

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

程序员麻辣烫

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值