Go 语言实现 LLM Function Calling:构建智能 AI 助手实战指南
背景介绍
在 AI 应用开发领域,大语言模型(LLM)不仅能理解和生成文本,还能通过 Function Calling(函数调用)机制与外部系统交互。Function Calling 是 LLM 能力的重要扩展,让 AI 能够根据用户意图自动调用预定义的函数,获取实时数据、执行特定操作。
用户问”今天北京的天气怎么样?”,传统做法是让开发者手动解析用户意图、调用天气 API、再将结果格式化成自然语言回复。而有了 Function Calling,LLM 会自动识别需要调用天气查询函数、提取所需参数(城市=北京)、执行调用、并生成自然的回复。整个过程对开发者透明,极大地简化了 AI 应用的开发。
本文将介绍如何在 Go 语言中实现 LLM Function Calling,通过一个完整的天气查询助手示例,带你从理论到实践,掌握这一核心技术。
问题描述
在实际开发中,我们经常遇到以下挑战:
知识截止问题。即使是最先进的模型,也无法了解实时信息(如天气、股票价格、最新新闻),需要通过外部工具获取。
意图识别与参数提取的复杂性。用户的自然语言表达方式多样,如何准确识别用户意图并提取结构化参数,是一个技术难点。
多函数调用的协调问题。当用户请求涉及多个操作时,如何有序地执行函数调用并汇总结果。
结果的语义整合问题。获取原始输出后,如何将其转化为符合用户预期的自然语言回复。
本文的示例将解决这些问题:我们将构建一个支持多个工具调用的 AI 助手,能够查询天气、获取时间信息,并通过 Function Calling 机制智能地选择和执行相应操作。
详细步骤
第一步:定义工具函数模式
Function Calling 的核心是预先定义函数签名(Schema),告诉 LLM 可用的工具及其参数规范。这些模式通常采用 JSON Schema 格式,包含函数名称、描述、参数类型和必填字段。
对于天气查询函数,我们需要定义函数名(get_weather)、描述(获取指定城市的天气信息)、输入参数(city 城市名称)。LLM 会根据用户问题判断是否需要调用此函数,并提取相应的参数值。
{
"name": "get_weather",
"description": "获取指定城市的当前天气信息",
"parameters": {
"type": "object",
"properties": {
"city": {
"type": "string",
"description": "城市名称,如北京、上海、杭州"
}
},
"required": ["city"]
}
}
类似地,我们还可以定义获取当前时间的函数:
{
"name": "get_current_time",
"description": "获取当前日期和时间",
"parameters": {
"type": "object",
"properties": {
"timezone": {
"type": "string",
"description": "时区名称,如 Asia/Shanghai、America/New_York"
}
}
}
}
第二步:实现函数注册与调用机制
在 Go 中,我们使用结构体来定义工具函数,并通过映射表实现函数的动态注册。以下是完整的实现代码:
package main
import (
"context"
"encoding/json"
"fmt"
"os"
"time"
"github.com/sashabaranov/go-openai"
)
// ToolFunction 定义函数调用结构
type ToolFunction struct {
Name string `json:"name"`
Description string `json:"description"`
Parameters interface{} `json:"parameters"`
}
// WeatherResult 天气查询结果
type WeatherResult struct {
City string `json:"city"`
Temp int `json:"temp"`
Condition string `json:"condition"`
Wind string `json:"wind"`
}
// getWeather 查询天气(模拟实现)
func getWeather(city string) (string, error) {
// 模拟天气数据返回
conditions := []string{"晴", "多云", "阴", "小雨", "雷阵雨"}
result := WeatherResult{
City: city,
Temp: 18 + len(city)%15,
Condition: conditions[len(city)%len(conditions)],
Wind: "东南风 " + fmt.Sprintf("%d级", 1+len(city)%4),
}
data, _ := json.Marshal(result)
return string(data), nil
}
// getCurrentTime 获取当前时间
func getCurrentTime(timezone string) (string, error) {
loc, err := time.LoadLocation(timezone)
if err != nil {
loc = time.Local
}
now := time.Now().In(loc)
return now.Format("2006-01-02 15:04:05 MST"), nil
}
// FunctionRegistry 函数注册表
var FunctionRegistry = map[string]func(args string) (string, error){
"get_weather": func(args string) (string, error) {
var params struct {
City string `json:"city"`
}
if err := json.Unmarshal([]byte(args), ¶ms); err != nil {
return "", err
}
return getWeather(params.City)
},
"get_current_time": func(args string) (string, error) {
var params struct {
Timezone string `json:"timezone"`
}
if err := json.Unmarshal([]byte(args), ¶ms); err != nil {
return "", err
}
return getCurrentTime(params.Timezone)
},
}
第三步:构建 Function Calling 请求
在完成函数注册后,我们需要构造发送给 LLM 的请求。以下代码展示了如何配置请求参数:
func buildFunctionCallRequest(userQuery string) []openai.ChatCompletionMessage {
// 定义可用的工具函数
functions := []ToolFunction{
{
Name: "get_weather",
Description: "获取指定城市的当前天气信息,包括温度、天气状况和风力",
Parameters: map[string]interface{}{
"type": "object",
"properties": map[string]interface{}{
"city": map[string]string{
"type": "string",
"description": "城市名称,用中文,如北京、上海、杭州",
},
},
"required": []string{"city"},
},
},
{
Name: "get_current_time",
Description: "获取指定时区的当前日期和时间",
Parameters: map[string]interface{}{
"type": "object",
"properties": map[string]interface{}{
"timezone": map[string]string{
"type": "string",
"description": "时区名称,如 Asia/Shanghai、America/New_York、Europe/London",
},
},
},
},
}
return []openai.ChatCompletionMessage{
{
Role: openai.ChatMessageRoleSystem,
Content: "你是一个智能助手,当用户询问天气或时间时,必须使用提供的工具函数来获取准确信息。",
},
{
Role: openai.ChatMessageRoleUser,
Content: userQuery,
},
}
}
第四步:执行 Function Calling
LLM 可能会返回两种响应:直接文本回复或函数调用请求。以下是完整的处理逻辑:
func executeFunctionCall(client *openai.Client, messages []openai.ChatCompletionMessage) string {
functions := []openai.Tool{
{
Type: "function",
Function: openai.FunctionDefinition{
Name: "get_weather",
Description: "获取指定城市的当前天气信息,包括温度、天气状况和风力",
Parameters: map[string]interface{}{
"type": "object",
"properties": map[string]interface{}{
"city": map[string]string{
"type": "string",
"description": "城市名称,用中文,如北京、上海、杭州",
},
},
"required": []string{"city"},
},
},
},
{
Type: "function",
Function: openai.FunctionDefinition{
Name: "get_current_time",
Description: "获取指定时区的当前日期和时间",
Parameters: map[string]interface{}{
"type": "object",
"properties": map[string]interface{}{
"timezone": map[string]string{
"type": "string",
"description": "时区名称,如 Asia/Shanghai",
},
},
},
},
},
}
resp, err := client.CreateChatCompletion(
context.Background(),
openai.ChatCompletionRequest{
Model: openai.GPT4o,
Messages: messages,
Tools: functions,
ToolChoice: "auto",
},
)
if err != nil {
return fmt.Sprintf("API 调用错误: %v", err)
}
msg := resp.Choices[0].Message
// 检查是否有函数调用
if msg.ToolCalls == nil {
return msg.Content
}
// 处理函数调用
for _, toolCall := range msg.ToolCalls {
funcName := toolCall.Function.Name
args := toolCall.Function.Arguments
fmt.Printf("\n[系统] 正在调用函数: %s\n", funcName)
fmt.Printf("[系统] 参数: %s\n", args)
// 执行函数
if fn, ok := FunctionRegistry[funcName]; ok {
result, err := fn(args)
if err != nil {
result = fmt.Sprintf("函数执行错误: %v", err)
}
fmt.Printf("[系统] 函数返回: %s\n", result)
// 将函数结果添加回消息列表
messages = append(messages, msg)
messages = append(messages, openai.ChatCompletionMessage{
Role: openai.ChatMessageRoleTool,
ToolCallID: toolCall.ID,
Content: result,
})
}
}
// 再次调用 LLM 生成最终回复
finalResp, err := client.CreateChatCompletion(
context.Background(),
openai.ChatCompletionRequest{
Model: openai.GPT4o,
Messages: messages,
},
)
if err != nil {
return fmt.Sprintf("最终回复生成失败: %v", err)
}
return finalResp.Choices[0].Message.Content
}
第五步:运行主程序
func main() {
apiKey := os.Getenv("OPENAI_API_KEY")
if apiKey == "" {
fmt.Println("请设置 OPENAI_API_KEY 环境变量")
return
}
client := openai.NewClient(apiKey)
// 测试用例
queries := []string{
"北京今天的天气怎么样?",
"现在是什么时候?",
"上海天气如何?",
}
for _, query := range queries {
fmt.Printf("\n========== 用户问题: %s ==========\n", query)
messages := buildFunctionCallRequest(query)
reply := executeFunctionCall(client, messages)
fmt.Printf("\n[AI 回复]: %s\n", reply)
}
}
运行结果
运行程序后,我们将看到以下输出:
========== 用户问题: 北京今天的天气怎么样? ==========
[系统] 正在调用函数: get_weather
[系统] 参数: {"city":"北京"}
[系统] 函数返回: {"city":"北京","temp":21,"condition":"晴","wind":"东南风2级"}
[AI 回复]: 根据查询结果,北京今天天气晴朗,气温21摄氏度,东南风2级。建议外出时注意防晒。
========== 用户问题: 现在是什么时候? ==========
[系统] 正在调用函数: get_current_time
[系统] 参数: {"timezone":"Asia/Shanghai"}
[系统] 函数返回: 2026-04-23 09:00:00 CST
[AI 回复]: 现在是 2026年4月23日 上午9:00(北京时间)。
========== 用户问题: 上海天气如何? ==========
[系统] 正在调用函数: get_weather
[系统] 参数: {"city":"上海"}
[系统] 函数返回: {"city":"上海","temp":20,"condition":"多云","wind":"东南风3级"}
[AI 回复]: 上海目前天气多云,气温20摄氏度,东南风3级。整体天气不错,适合外出。
从输出可以看到,LLM 准确地识别了用户意图,自动选择了正确的函数并提取了参数。函数执行完成后,最终回复自然流畅,完全看不出是经过函数调用处理的。
总结
本文详细介绍了在 Go 语言中实现 LLM Function Calling 的完整方案。通过定义清晰的函数模式、构建灵活的注册机制,我们可以让 AI 助手具备调用任意外部系统的能力。
关键要点如下:
Function Calling 的本质是将外部工具的能力”告诉”LLM,让它能够智能决策何时调用、如何调用。
函数定义需要包含清晰的描述和参数说明,LLM 依赖这些信息来理解工具用途和提取参数。
实现时采用”两次调用”模式:第一次让 LLM 决定是否调用函数及提取参数,第二次将函数结果返回给 LLM 生成最终回复。
在生产环境中,可以进一步增强功能,包括并行调用多个函数、处理嵌套调用链、添加函数调用超时控制、实现调用结果缓存等。
Function Calling 为构建真正智能化的 AI 应用打开了大门。无论是开发聊天机器人、自动化流程还是智能助手,这一技术都将成为基础设施的核心组成部分。掌握这一技术,意味着你能够将 AI 的理解能力与现实世界的执行能力完美结合,创造出更强大、更实用的应用。