# 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 就能跑。