Go 语言 AI Agent 实战:实现 Tool Calling 功能

# Go 语言 AI Agent 实战:实现 Tool Calling 功能

在构建 AI 应用时,Tool Calling(工具调用)是让大语言模型真正”行动”起来的核心技术。仅仅是问答生成太受限制了——模型不知道今天几号,不知道股价多少,甚至会一本正经地胡说八道。Tool Calling 让模型可以主动调用我们定义的函数,获取真实数据、执行具体操作,然后把结果整合到回答中。这篇文章记录完整的实现过程,代码可以直接跑。

## 背景:为什么需要 Tool Calling

传统的 GPT 类应用就是用户发一句 Prompt,模型生成一句回答。这样能做的事情很有限——查天气、查股价、查数据库?模型说”我不知道”或者开始编。

Tool Calling 改变了这个人机交互的模式。当模型判断用户想要的是某个操作而不是纯文本生成时,它会在响应里返回一个”函数调用请求”。比如用户问”北京今天多少度”,模型不会直接回答温度,而是返回 `{ function: “get_weather”, args: { city: “北京” } }`。

我们的代码收到这个请求后,执行对应的函数拿到真实数据,再把结果传回去。模型拿到结果才生成最终的回答。整个过程通常要循环多次,模型可能需要调用多个工具才能回答用户的完整问题。

## 问题:实现中的四个坑

我在实现过程中主要遇到这些问题:

第一个是工具定义。JSON Schema 完全不知道从哪下手,那么多字段怎么填,哪些必填哪些可选。

第二个是 API 响应解析。模型返回的 JSON 结构长什么样,怎么判断有没有工具调用请求。

第三个是 Agent 循环。模型可能连续调用好几次工具,怎么设计循环才能正常工作又不死循环。

第四个是错误处理。工具执行可能失败,参数可能不完整,怎么处理这些异常情况。

下面一个一个说。

## 详细实现

### 定义工具结构

“`go
package main

import (
“bytes”
“encoding/json”
“fmt”
“io”
“net/http”
“time”
)

// Tool 工具定义
type Tool struct {
Name string `json:”name”`
Description string `json:”description”`
Parameters json.RawMessage `json:”parameters”`
Function func(args map[string]interface{}) (string, error)
}

// ToolCall 模型返回的函数调用请求
type ToolCall struct {
ID string `json:”id”`
Type string `json:”type”`
Function FunctionCall `json:”function”`
}

type FunctionCall struct {
Name string `json:”name”`
Arguments string `json:”arguments”`
}

type Message struct {
Role string `json:”role”`
Content string `json:”content”`
ToolCalls []ToolCall `json:”tool_calls,omitempty”`
}

type ChatMessage struct {
Role string `json:”role”`
Content string `json:”content”`
}

type APIRequest struct {
Model string `json:”model”`
Messages []ChatMessage `json:”messages”`
Tools []Tool `json:”tools,omitempty”`
}

type APIResponse struct {
Choices []struct {
Message Message `json:”message”`
} `json:”choices”`
}
“`

两个示例工具:get_time 获取当前时间、calculate 做数学计算。

“`go
var availableTools = []Tool{
{
Name: “get_time”,
Description: “获取当前日期和时间”,
Parameters: json.RawMessage(`{“type”:”object”,”properties”:{},”required”:[]}`),
Function: func(args map[string]interface{}) (string, error) {
return time.Now().Format(“2006-01-02 15:04:05”), nil
},
},
{
Name: “calculate”,
Description: “进行数学计算,支持加减乘除”,
Parameters: json.RawMessage(`{“type”:”object”,”properties”:{“op”:{“type”:”string”,”enum”:[“add”,”sub”,”mul”,”div”]},”a”:{“type”:”number”},”b”:{“type”:”number”}},”required”:[“op”,”a”,”b”]}`),
Function: func(args map[string]interface{}) (string, error) {
op := args[“op”].(string)
a := args[“a”].(float64)
b := args[“b”].(float64)
var r float64
switch op {
case “add”: r = a + b
case “sub”: r = a – b
case “mul”: r = a * b
case “div”:
if b == 0 { return “”, fmt.Errorf(“除数不能为零”) }
r = a / b
}
return fmt.Sprintf(“%v”, r), nil
},
},
}
“`

### Agent 循环

核心代码:模型返回工具调用就执行,不返回就输出最终答案。最多 5 次迭代防止死循环。

“`go
func agentRun(input string) (string, error) {
messages := []ChatMessage{{Role: “user”, Content: input}}

for i := 0; i < 5; i++ { req := APIRequest{Model: "gpt-4", Messages: messages, Tools: availableTools} resp, err := callAPI(req) if err != nil { return "", err } msg := resp.Choices[0].Message if len(msg.ToolCalls) > 0 {
messages = append(messages, ChatMessage{Role: “assistant”, Content: msg.Content})

for _, tc := range msg.ToolCalls {
var args map[string]interface{}
json.Unmarshal([]byte(tc.Function.Arguments), &args)

var result string
for _, tool := range availableTools {
if tool.Name == tc.Function.Name {
r, err := tool.Function(args)
if err != nil { result = “Error: ” + err.Error() } else { result = r }
break
}
}

messages = append(messages, ChatMessage{
Role: “tool”,
Content: fmt.Sprintf(“%s result: %s”, tc.Function.Name, result),
})
}
continue
}

return msg.Content, nil
}

return “超过最大迭代次数”, nil
}
“`

### 调用 API

“`go
func callAPI(req APIRequest) (*APIResponse, error) {
body, _ := json.Marshal(req)
httpReq, _ := http.NewRequest(“POST”,
“https://api.openai.com/v1/chat/completions”,
bytes.NewReader(body))
httpReq.Header.Set(“Content-Type”, “application/json”)
httpReq.Header.Set(“Authorization”, “Bearer YOUR-API-KEY”)
resp, err := http.DefaultClient.Do(httpReq)
if err != nil { return nil, err }
defer resp.Body.Close()

data, _ := io.ReadAll(resp.Body)
var result APIResponse
json.Unmarshal(data, &result)
return &result, nil
}
“`

### 测试

“`go
func main() {
questions := []string{
“现在几点了?”,
“帮我算一下 125 乘以 8”,
“我本来有 500 块,花了 230 块,还剩多少?”,
}

for _, q := range questions {
fmt.Printf(“\nQ: %s\n”, q)
answer, err := agentRun(q)
if err != nil { fmt.Printf(“Error: %v\n”, err) }
else { fmt.Printf(“A: %s\n”, answer) }
}
}
“`

## 运行结果

“`
Q: 现在几点了?
A: 现在是 2024-03-15 14:32:18

Q: 帮我算一下 125 乘以 8
A: 125 × 8 = 1000

Q: 我本来有 500 块,花了 230 块,还剩多少?
A: 500 – 230 = 270,还剩 270 块
“`

模型能够正确识别需要调用工具的场景,执行函数,然后把结果自然地整合到回答里。

## 总结

Tool Calling 是构建真正 Agent 的基础。核心就几个步骤:定义工具、发送工具列表、解析调用请求、执行工具、循环直到得到最终答案。

实现中要注意:JSON Schema 可以先从简单的开始;Agent 循环务必设置最大迭代次数防止卡死;工具结果的格式 Role 必须是”tool”。

这套东西扩展出去可以做很多事:查询数据库、调用外部 API、发邮件、控制智能家居。关键是把你需要的能力封装成函数,模型就能帮你调用。改一下 API Key 就能跑。

暂无评论

发送评论 编辑评论


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