OpenAI Function Calling 实战:让 AI 调用外部工具的完整指南

# OpenAI Function Calling 实战:让 AI 调用外部工具的完整指南

你是否想过,AI 不仅仅是对话,还能帮你查天气、搜资料、操作数据库?Function Calling 就是让 LLM 具备这种能力的关键技术。本文会从头讲起,带你一步步实现 AI 调用外部工具的功能。

## 背景介绍

GPT-4 这样的语言模型,本质上是个概率预测机——它根据训练数据预测下一个最可能出现的字。这种能力让生成文章、代码变得轻而易举,但模型本身并不具备执行能力。你问它”今天北京天气怎么样”,它只能根据训练数据猜测,而无法告诉你真实的天气情况。

这时候就需要 Function Calling(函数调用)机制。简单来说,这个功能的流程是:用户提问 -> 模型判断需要调用哪个函数 -> 返回函数签名给前端 -> 前端执行实际调用 -> 把结果返回给模型 -> 模型生成最终回答。

这个设计很巧妙:模型只负责”思考”需要什么工具,真正执行交给外部程序。这样既保留了语言模型的理解能力,又获得了实际操作的能力。

## 问题描述

很多开发者在集成 Function Calling 时会遇到几个典型问题。

一是函数定义格式错误。OpenAI 对 JSON Schema 有严格要求,参数类型必须是标准格式。二是流式输出的处理。Function Calling 通常需要多轮交互,怎么在流式输出中正确处理。三是结果转换。函数返回的 JSON 怎么格式化后传给模型才能得到最好的回答。

接下来我们用 Go 语言实现一个完整的 demo,功能是让 AI 帮助查询天气。

## 详细步骤

### 第一步:定义函数签名

函数定义的核心是 JSON Schema。我们需要告诉模型这个函数叫什么、做什么、参数是什么格式。

// weather_functions.go
package main

import (
    "encoding/json"
    "fmt"
    "strings"

    "github.com/sashabaranov/go-openai"
)

// 天气查询函数定义
var weatherFunctions = []openai.FunctionDefinition{
    {
        Name:        "get_weather",
        Description: "查询指定城市的天气信息",
        Parameters: json.RawMessage(`{
            "type": "object",
            "properties": {
                "city": {
                    "type": "string",
                    "description": "城市名称,如 北京、上海、Seattle"
                },
                "unit": {
                    "type": "string",
                    "enum": ["celsius", "fahrenheit"],
                    "description": "温度单位,摄氏或华氏",
                    "default": "celsius"
                }
            },
            "required": ["city"]
        }`),
    },
}

这里有几个关键点。Description 会直接展示给模型,影响它是否选择这个函数,一定要描述清晰。parameters 部分遵循 JSON Schema 标准,type、description 都是必填的。required 数组声明哪些参数必须有,没填的话模型就不会选择这个函数。

### 第二步:实现函数注册和处理

模型返回函数选择后,前端需要找到对应的处理函数并执行。

// function_registry.go
package main

import (
    "fmt"
    "math/rand"
    "time"
)

// FunctionRegistry 保存所有可用函数
var FunctionRegistry = map[string]func(args map[string]interface{}) (string, error){
    "get_weather": getWeather,
}

// 天气查询的实际实现
func getWeather(args map[string]interface{}) (string, error) {
    city, ok := args["city"].(string)
    if !ok {
        return "", fmt.Errorf("city 参数缺失")
    }

    unit := "celsius"
    if u, ok := args["unit"].(string); ok {
        unit = u
    }

    // 模拟天气查询(实际项目中替换为真实 API 调用)
    temp := rand.Intn(15) + 15 // 15-30 度随机
    if unit == "fahrenheit" {
        temp = temp*9/5 + 32
    }

    weather := []string{"晴", "多云", "阴", "小雨"}
    cond := weather[rand.Intn(len(weather))]

    return fmt.Sprintf(`{"city": "%s", "temperature": %d, "unit": "%s", "condition": "%s"}`,
        city, temp, unit, cond), nil
}

注册表的设计很直接:用函数名作为 key,value 是实际处理函数。实际项目中,这里应该替换为调用 Weather API、搜狗搜索等真实服务。

### 第三步:多轮对话处理

Function Calling 通常需要多轮交互。这里是个简化版的处理流程:

// chat_with_functions.go
package main

import (
    "context"
    "fmt"
    "log"

    "github.com/sashabaranov/go-openai"
)

func chatWithFunction(ctx context.Context, client *openai.Client, messages []openai.ChatMessage) error {
    // 第一轮:发送消息,获取模型响应
    resp, err := client.CreateChatCompletion(ctx, openai.ChatCompletionRequest{
        Model:       "gpt-4-turbo",
        Messages:    messages,
        Functions:   weatherFunctions,
        FunctionCall: "auto",
    })
    if err != nil {
        return fmt.Errorf("创建聊天失败: %w", err)
    }

    message := resp.Choices[0].Message

    // 检查模型是否选择了函数调用
    if message.FunctionCall.Name == "" {
        // 普通对话,直接输出
        fmt.Println("助手:", message.Content)
        return nil
    }

    // 模型选择了函数,提取函数名和参数
    funcName := message.FunctionCall.Name
    var funcArgs map[string]interface{}
    if err := json.Unmarshal([]byte(message.FunctionCall.Arguments), &funcArgs); err != nil {
        return fmt.Errorf("解析函数参数失败: %w", err)
    }

    fmt.Printf("模型选择调用函数: %s, 参数: %v\n", funcName, funcArgs)

    // 查找并执行函数
    if handler, ok := FunctionRegistry[funcName]; ok {
        result, err := handler(funcArgs)
        if err != nil {
            return fmt.Errorf("执行函数失败: %w", err)
        }

        fmt.Printf("函数返回结果: %s\n", result)

        // 将函数结果添加到对话历史,继续请求模型
        messages = append(messages, message)
        messages = append(messages, openai.ChatMessage{
            Role:    "function",
            Name:    funcName,
            Content: result,
        })

        // 第二轮:获取最终回复
        finalResp, err := client.CreateChatCompletion(ctx, openai.ChatCompletionRequest{
            Model:    "gpt-4-turbo",
            Messages: messages,
        })
        if err != nil {
            return fmt.Errorf("获取最终回复失败: %w", err)
        }

        fmt.Println("助手:", finalResp.Choices[0].Message.Content)
        return nil
    }

    return fmt.Errorf("未找到函数: %s", funcName)
}

这里的核心是多轮对话。第一轮把用户问题发给模型,模型判断需要调用天气函数;第二轮把函数执行结果发回模型,让它根据结果生成回答。

### 第四步:运行测试

把以上代码组合成完整的入口文件:

// main.go
package main

import (
    "context"
    "fmt"
    "log"
    "os"

    "github.com/sashabaranov/go-openai"
)

func main() {
    apiKey := os.Getenv("OPENAI_API_KEY")
    if apiKey == "" {
        log.Fatal("请设置 OPENAI_API_KEY 环境变量")
    }

    client := openai.NewClient(apiKey)
    ctx := context.Background()

    // 测试用例
    messages := []openai.ChatMessage{
        {
            Role:    "user",
            Content: "北京今天天气怎么样?",
        },
    }

    err := chatWithFunction(ctx, client, messages)
    if err != nil {
        log.Fatal(err)
    }
}

运行前先设置环境变量:

export OPENAI_API_KEY=sk-你的API密钥
go run main.go

## 运行结果

一次典型的运行输出:

模型选择调用函数: get_weather, 参数: map[city:北京 unit:celsius]
函数返回结果: {"city": "北京", "temperature": 22, "unit": "celsius", "condition": "多云"}
助手: 根据查询结果,北京今天天气多云,气温22摄氏度。建议带伞出门。

整个过程耗时约 2-3 秒,主要消耗在 API 调用上。实际应用中可以通过缓存、并发请求等方式优化。

## 总结

Function Calling 为 AI 应用打开了新世界的大门。它让语言模型从”只会说”变成了”真的能做”。关键要点:

一是函数定义要准确。JSON Schema 的格式必须符合规范,description 要清晰到模型能理解。二是注册表设计要灵活。把函数实现和模型调用分离,方便后续扩展。三是多轮对话处理好。模型可能连续调用多个函数,每次都要把结果喂回去。

进阶方向可以探索:多个函数并行调用,流式输出中实时展示调用进度,用 Tool Calls 实现 RAG 检索增强等。这些内容我们以后再展开。

暂无评论

发送评论 编辑评论


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