Go 语言调用 OpenAI Function Calling 实现结构化数据提取

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 应用,不管是简单问答还是复杂的业务逻辑处理,都能搞定。

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇