Go语言实战:让LLM调用你的函数

# Go语言实战:让LLM调用你的函数

你有没有想过,为什么AI助手只能”说话”,却不能帮你做事?问它天气,它只能告诉你”我没法查天气”;让它帮你记个待办事项,它也只能说”抱歉,我做不到”。这感觉就像雇了一个全能秘书,却只能看不能用。

Function Calling改变了这个局面。这是大语言模型的一个”超能力”——它能理解用户想要什么操作,然后帮你调用相应的函数。整个过程一气呵成,用户甚至感觉不到背后发生了什么。

## 为什么需要Function Calling

说白了,LLM再聪明也只是文本生成器。你让它查天气,它只能根据训练数据猜一个答案,或者直接告诉你”我查不到”。但现实中,我们需要AI做更多的事:查数据库、操作文件、调用第三方API……这些操作靠AI自己根本完成不了。

Function Calling的思路很简单:LLM负责”动脑”——理解用户意图、决定调用哪个函数、生成正确的参数;具体执行交给程序去干。这是一种分工,LLM做它擅长的,我们做我们擅长的。

主流模型基本都支持这个功能了。OpenAI GPT-4、Anthropic Claude、通义千问、智谱清言……都用得上。本文用Go语言完整演示一遍怎么做。

## 一步步实现

### 环境配置

确保Go版本在1.21以上,然后初始化项目:

“`bash
mkdir llm-function-calling && cd llm-function-calling
go mod init llm-function-calling
“`

安装OpenAI的Go客户端:

“`bash
go get github.com/sashabaranov/go-openai
“`

### 定义函数

在Go里,我们用openai.Tool来描述一个函数。它包含函数名、描述和参数模式三部分。参数模式用JSON Schema来写,这样能精确告诉LLM每个参数应该是什么类型、有什么约束。

天气查询函数这样定义:

“`go
weatherTool := openai.Tool{
Type: openai.ToolTypeFunction,
Function: openai.FunctionDefinition{
Name: “get_weather”,
Description: “获取指定城市的天气信息”,
Parameters: &openai.JSONSchema{
Type: “object”,
Properties: map[string]*openai.JSONSchemaProperty{
“city”: {
Type: “string”,
Description: “城市名称,如北京、上海”,
},
“date”: {
Type: “string”,
Description: “日期,格式为YYYY-MM-DD,默认为今天”,
},
},
Required: []string{“city”},
},
},
}
“`

城市是必填的,日期是可选的——这些约束很重要,LLM会根据这些决定是否需要询问用户补充信息。

### 发送请求

把函数定义和用户问题一起发给LLM:

“`go
package main

import (
“context”
“encoding/json”
“fmt”
“log”

“github.com/sashabaranov/go-openai”
)

func main() {
client := openai.NewClient(“your-api-key”)

tools := []openai.Tool{
{
Type: openai.ToolTypeFunction,
Function: openai.FunctionDefinition{
Name: “get_weather”,
Description: “获取指定城市的天气信息”,
Parameters: &openai.JSONSchema{
Type: “object”,
Properties: map[string]*openai.JSONSchemaProperty{
“city”: {
Type: “string”,
Description: “城市名称”,
},
“date”: {
Type: “string”,
Description: “日期,格式为YYYY-MM-DD”,
},
},
Required: []string{“city”},
},
},
},
{
Type: openai.ToolTypeFunction,
Function: openai.FunctionDefinition{
Name: “add_todo”,
Description: “添加待办事项”,
Parameters: &openai.JSONSchema{
Type: “object”,
Properties: map[string]*openai.JSONSchemaProperty{
“task”: {
Type: “string”,
Description: “待办事项内容”,
},
“priority”: {
Type: “string”,
Description: “优先级:high/medium/low”,
Enum: []string{“high”, “medium”, “low”},
},
},
Required: []string{“task”},
},
},
},
}

resp, err := client.CreateChatCompletion(
context.Background(),
openai.ChatCompletionRequest{
Model: openai.GPT4o,
Messages: []openai.ChatMessage{
{
Role: openai.ChatMessageRoleUser,
Content: “北京明天天气怎么样?记得帮我把准备演讲稿加入待办事项,优先级高”,
},
},
Tools: tools,
},
)
if err != nil {
log.Fatal(err)
}

fmt.Println(“Response:”, resp.Choices[0].Message.Content)
fmt.Println(“Tool Calls:”, resp.Choices[0].Message.ToolCalls)
}
“`

这里有个细节要注意。用户说”北京明天”,但我们的函数需要”2024-12-15″这样的日期格式。LLM能自动完成这个转换,前提是你的描述足够清楚。

### 处理函数调用

当LLM决定调用函数时,响应里会有ToolCalls字段。解析它,提取函数名和参数,执行真正的逻辑,然后把结果返回给LLM让它生成最终回复。

完整流程:

“`go
func handleFunctionCall(client *openai.Client, message openai.ChatMessage) string {
if len(message.ToolCalls) == 0 {
return message.Content
}

var messages []openai.ChatMessage
messages = append(messages, message)

for _, call := range message.ToolCalls {
fmt.Printf(“Calling function: %s\n”, call.Function.Name)
fmt.Printf(“Arguments: %s\n”, call.Function.Arguments)

var args map[string]interface{}
json.Unmarshal([]byte(call.Function.Arguments), &args)

result := executeFunction(call.Function.Name, args)
messages = append(messages, openai.ChatMessage{
Role: openai.ChatMessageRoleTool,
ToolCallID: call.ID,
Content: result,
})
}

finalResp, err := client.CreateChatCompletion(
context.Background(),
openai.ChatCompletionRequest{
Model: openai.GPT4o,
Messages: messages,
},
)
if err != nil {
return “处理函数调用时出错: ” + err.Error()
}

return finalResp.Choices[0].Message.Content
}

func executeFunction(name string, args map[string]interface{}) string {
switch name {
case “get_weather”:
city := args[“city”].(string)
date := “今天”
if d, ok := args[“date”].(string); ok {
date = d
}
return fmt.Sprintf(`{“city”: “%s”, “date”: “%s”, “weather”: “晴”, “temperature”: “15-22°C”}`, city, date)
case “add_todo”:
task := args[“task”].(string)
priority := “medium”
if p, ok := args[“priority”].(string); ok {
priority = p
}
return fmt.Sprintf(`{“status”: “success”, “task”: “%s”, “priority”: “%s”}`, task, priority)
default:
return `{“error”: “unknown function”}`
}
}
“`

## 实际效果

跑一下代码,看会发生什么。

用户说:”北京明天天气怎么样?记得帮我把准备演讲稿加入待办事项,优先级高”

LLM收到后,立刻识别出需要调用两个函数:

“`
ToolCall[0]: get_weather – {“city”: “北京”, “date”: “2024-12-15”}
ToolCall[1]: add_todo – {“task”: “准备演讲稿”, “priority”: “high”}
“`

参数完全正确。它知道”明天”要转换成具体日期”2024-12-15″,也知道”优先级高”要映射成”high”。

执行完函数后,把结果塞回去,LLM最后生成的回复是:

“北京明天是2024年12月15日,天气晴朗,气温15-22°C。我已经帮你把准备演讲稿加入了待办事项,优先级设为高。”

整个过程用户只说了一句话,AI完成了理解、调用、执行的完整链条。

## 总结

Function Calling让AI从”花架子”变成了”真把式”。它负责动脑思考该做什么,你负责动手具体执行,两者配合,做事效率才会上来。

实际用的时候,有几点建议:函数描述写清楚,别舍不得字数;参数约束设合理了,能省很多调试功夫;多函数调用的情况下,注意处理依赖关系和错误情况。

这就是本文的全部内容。一个小功能,打开了一扇大门。剩下的自己去玩吧。

暂无评论

发送评论 编辑评论


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