Go 语言调用 OpenAI Function Calling 实现结构化数据提取
背景介绍
开发 AI 应用时,有一个问题特别让人头疼:模型输出的内容格式不稳定,有时输出的内容很难直接用到后续的业务逻辑里。传统做法是通过 Prompt 工程来约束输出格式,或者用正则表达式解析响应,但这些方法总是不够靠谱。
OpenAI 在 GPT-4 系列模型中引入了 Function Calling 功能。这是一个有意思的改进:模型可以根据用户的意图去调用预定义的函数,然后返回结构化的结果。这样一来,我们和 AI 模型交互的方式就完全不同了。
问题描述
具体来说,实际项目中经常遇到这些问题:
- 输出格式不稳定:Prompt 里明明写着要 JSON,可模型偶尔还是会写出格式错误的内容
- 解析困难:非结构化的输出需要额外的解析逻辑,一不小心就出错
- 缺乏类型安全:输出格式无法在编译时验证
- 调试困难:当输出不符合预期时,很难找出问题在哪
Function Calling 换了个思路:不让模型自己生成格式,而是让它决定调用哪个函数、传入什么参数。这样就从根本上绕开了这些问题。
详细步骤
准备工作
首先,装好 Go 环境(1.18 及以上版本),然后获取 OpenAI Go 客户端库:
go get github.com/sashabaranov/go-openai
定义函数工具
Function Calling 的关键是定义函数工具(Function Tool)。我们需要让模型知道函数叫什么、接受什么参数、用来做什么:
package main
import (
"context"
"encoding/json"
"fmt"
"log"
"github.com/sashabaranov/go-openai"
)
// WeatherParams 定义天气查询函数的参数结构
type WeatherParams struct {
City string `json:"city"` // 城市名称,必填
Country string `json:"country"` // 国家代码,可选
}
实现函数工具定义
用 JSON Schema 格式来定义函数的参数规范:
// 定义可用的函数工具
func getTools() []openai.Tool {
return []openai.Tool{
{
Type: openai.ToolTypeFunction,
Function: openai.FunctionDefinition{
Name: "get_weather",
Description: "获取指定城市的天气信息",
Parameters: json.RawMessage(`{
"type": "object",
"properties": {
"city": {
"type": "string",
"description": "城市名称,如 Beijing, 上海"
},
"country": {
"type": "string",
"description": "国家代码,如 CN, US"
}
},
"required": ["city"]
}`),
},
},
}
}
实现实际的函数逻辑
函数的具体实现:
// getWeather 获取天气信息的函数
func getWeather(city, country string) string {
// 这里用模拟数据演示一下
if country == "" {
country = "CN"
}
weatherData := map[string]map[string]string{
"Beijing": {"CN": "晴,15-25°C", "US": "Sunny, 59-77°F"},
"Shanghai": {"CN": "多云,18-28°C", "US": "Cloudy, 64-82°F"},
"New York": {"CN": "晴,10-20°C", "US": "Sunny, 50-68°F"},
"Tokyo": {"CN": "雨,16-22°C", "US": "Rainy, 61-72°F"},
}
if cityWeather, ok := weatherData[city]; ok {
if temp, ok := cityWeather[country]; ok {
return fmt.Sprintf("%s, %s 的天气是 %s", city, country, temp)
}
}
return fmt.Sprintf("未找到 %s, %s 的天气信息", city, country)
}
实现主交互逻辑
把所有的部分组合在一起,完成整个 Function Calling 的流程:
func main() {
// 初始化客户端
client := openai.NewClient("your-api-key")
ctx := context.Background()
// 用户问题
userQuestion := "北京今天的天气怎么样?"
// 构造请求
req := openai.ChatCompletionRequest{
Model: openai.GPT4o,
Messages: []openai.ChatCompletionMessage{
{
Role: openai.ChatMessageRoleSystem,
Content: "你是一个有用的助手,可以调用函数来获取信息。",
},
{
Role: openai.ChatMessageRoleUser,
Content: userQuestion,
},
},
Tools: getTools(),
}
// 发送请求
resp, err := client.CreateChatCompletion(ctx, req)
if err != nil {
log.Fatalf("创建请求失败: %v", err)
}
// 处理响应
message := resp.Choices[0].Message
// 检查是否有函数调用
if len(message.ToolCalls) > 0 {
for _, toolCall := range message.ToolCalls {
fmt.Printf("模型调用了函数: %s\n", toolCall.Function.Name)
fmt.Printf("参数: %s\n", toolCall.Function.Arguments)
// 解析参数
var params WeatherParams
if err := json.Unmarshal([]byte(toolCall.Function.Arguments), ¶ms); err != nil {
log.Fatalf("解析参数失败: %v", err)
}
// 执行实际函数
result := getWeather(params.City, params.Country)
fmt.Printf("函数返回结果: %s\n", result)
// 将结果发回给模型,获取最终回复
req.Messages = append(req.Messages, message)
req.Messages = append(req.Messages, openai.ChatCompletionMessage{
Role: openai.ChatMessageRoleTool,
Content: result,
ToolCallID: toolCall.ID,
})
// 获取最终响应
finalResp, err := client.CreateChatCompletion(ctx, req)
if err != nil {
log.Fatalf("获取最终响应失败: %v", err)
}
fmt.Printf("\n最终回复: %s\n", finalResp.Choices[0].Message.Content)
}
} else {
fmt.Printf("直接回复: %s\n", message.Content)
}
}
处理多个函数
更复杂的需求下,也许需要定义多个函数:
// 扩展工具列表支持多个函数
func getExtendedTools() []openai.Tool {
return []openai.Tool{
{
Type: openai.ToolTypeFunction,
Function: openai.FunctionDefinition{
Name: "get_weather",
Description: "获取指定城市的天气信息",
Parameters: json.RawMessage(`{
"type": "object",
"properties": {
"city": {"type": "string", "description": "城市名称"}
},
"required": ["city"]
}`),
},
},
{
Type: openai.ToolTypeFunction,
Function: openai.FunctionDefinition{
Name: "get_calendar",
Description: "获取指定日期的日历信息",
Parameters: json.RawMessage(`{
"type": "object",
"properties": {
"date": {"type": "string", "description": "日期,格式 YYYY-MM-DD"}
},
"required": ["date"]
}`),
},
},
{
Type: openai.ToolTypeFunction,
Function: openai.FunctionDefinition{
Name: "search_products",
Description: "搜索产品信息",
Parameters: json.RawMessage(`{
"type": "object",
"properties": {
"query": {"type": "string", "description": "搜索关键词"},
"max_results": {"type": "integer", "description": "最大结果数"}
},
"required": ["query"]
}`),
},
},
}
}
运行结果
跑一下上面的代码。用户问”北京今天的天气怎么样?”时,输出是这样的:
模型调用了函数: get_weather
参数: {"city":"Beijing","country":"CN"}
函数返回结果: Beijing, CN 的天气是 晴,15-25°C
最终回复: 根据查询结果,北京今天的天气是晴天,气温在15到25摄氏度之间。建议您外出时穿着轻便,注意防晒。
如果用户的问题是”明天去上海需要带伞吗?”,模型会先调用天气函数查一下上海明天的天气,然后根据返回的信息给出建议。
总结
这篇文章讲了 Go 语言里怎么用 OpenAI Function Calling。这个做法有几个好处:
- 函数参数本身就是结构化的数据,用不着再另外解析
- 通过 Go 的结构体定义,编译时就能检查类型
- 模型会严格按照我们定义的参数格式返回数据,不会乱来
- 添加新函数来扩展功能也很容易
实际生产环境里,建议把流式响应和 Function Calling 结合起来,用户体验会更好。同时,记得做好错误处理和日志记录,方便调试和监控。
把函数工具设计好,就能搭出功能很强的 AI 应用,不管是简单问答还是复杂的业务逻辑处理,都能搞定。