Function Calling 原理:让 AI 调用你的函数
🦞 一只用 AI Agent 搭副业产线的程序员
上篇我们写了 Agent 的骨架——感知-决策-执行循环。但循环里最关键的一步没有展开:AI 怎么知道该调用哪个函数?
很多人以为 Function Calling 是 API 自带的功能,点了开关就行。其实不是。Function Calling 本质上就是「你告诉 AI 有哪些函数可以用,AI 在合适的时机告诉你该调哪个」。
这篇我们把这个过程拆到骨头。手写一遍,你就彻底懂了。
Function Calling 不是魔法,是一份说明书
整个过程分 4 步:
① 你定义 Tool Schema(函数的说明书)
② 你把 Schema 塞进 API 请求
③ AI 返回时告诉你「我要调用 tool_xxx,参数是...」
④ 你执行函数,把结果再发回给 AI
没有哪一步是 API 自动帮你做的。API 只负责第 3 步——告诉你该调什么。第 1、2、4 步全是你自己写的代码。
完整实现
package main
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
"os"
"strings"
"time"
)
// ───────── 1. 定义 Tool Schema ─────────
type Tool struct {
Name string `json:"name"`
Description string `json:"description"`
Parameters map[string]interface{} `json:"parameters"`
}
type ToolResult struct {
ToolName string
Success bool
Data string
Error string
}
// 定义一个「获取天气」的工具
var weatherTool = Tool{
Name: "get_weather",
Description: "获取指定城市的当前天气信息",
Parameters: map[string]interface{}{
"type": "object",
"properties": map[string]interface{}{
"city": map[string]interface{}{
"type": "string",
"description": "城市名称,例如 '北京' 或 'Shanghai'",
},
"unit": map[string]interface{}{
"type": "string",
"enum": []string{"celsius", "fahrenheit"},
"description": "温度单位,默认 celsius",
},
},
"required": []string{"city"},
},
}
// 定义一个「发送邮件」的工具
var emailTool = Tool{
Name: "send_email",
Description: "发送一封邮件到指定地址",
Parameters: map[string]interface{}{
"type": "object",
"properties": map[string]interface{}{
"to": map[string]interface{}{
"type": "string",
"description": "收件人邮箱地址",
},
"subject": map[string]interface{}{
"type": "string",
"description": "邮件主题",
},
"body": map[string]interface{}{
"type": "string",
"description": "邮件正文",
},
},
"required": []string{"to", "subject", "body"},
},
}
// ───────── 2. 构建 API 请求 ─────────
type Message struct {
Role string `json:"role"`
Content string `json:"content"`
}
type ChatRequest struct {
Model string `json:"model"`
Messages []Message `json:"messages"`
Tools []Tool `json:"tools"`
}
type ToolCallRequest struct {
ID string `json:"id"`
Type string `json:"type"`
Func struct {
Name string `json:"name"`
Arguments string `json:"arguments"` // JSON 字符串
} `json:"function"`
}
type ChatResponse struct {
Choices []struct {
Message struct {
Role string `json:"role"`
Content string `json:"content"`
ToolCalls []ToolCallRequest `json:"tool_calls"`
} `json:"message"`
} `json:"choices"`
}
func callLLMWithTools(messages []Message, tools []Tool) (ChatResponse, error) {
reqBody := ChatRequest{
Model: "deepseek-v4-pro",
Messages: messages,
Tools: tools,
}
data, _ := json.Marshal(reqBody)
req, _ := http.NewRequest("POST",
"https://api.deepseek.com/anthropic/v1/chat/completions",
bytes.NewReader(data))
req.Header.Set("Authorization", "Bearer "+os.Getenv("DEEPSEEK_API_KEY"))
req.Header.Set("Content-Type", "application/json")
client := &http.Client{Timeout: 30 * time.Second}
resp, err := client.Do(req)
if err != nil {
return ChatResponse{}, fmt.Errorf("API 调用失败: %w", err)
}
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
var chatResp ChatResponse
if err := json.Unmarshal(body, &chatResp); err != nil {
return ChatResponse{}, fmt.Errorf("解析响应失败: %w", err)
}
return chatResp, nil
}
// ───────── 3. 执行本地函数 ─────────
func executeWeatherTool(args map[string]interface{}) ToolResult {
city, _ := args["city"].(string)
unit := "celsius"
if u, ok := args["unit"].(string); ok {
unit = u
}
// 模拟天气查询(实际项目在这里调真实的天气 API)
weatherData := fmt.Sprintf(
`{"city": "%s", "temperature": 22, "condition": "晴", "humidity": 55, "unit": "%s"}`,
city, unit,
)
return ToolResult{ToolName: "get_weather", Success: true, Data: weatherData}
}
func executeEmailTool(args map[string]interface{}) ToolResult {
to, _ := args["to"].(string)
subject, _ := args["subject"].(string)
body, _ := args["body"].(string)
fmt.Printf("📧 模拟发送邮件:\n 收件人: %s\n 主题: %s\n 正文: %s\n", to, subject, body)
return ToolResult{
ToolName: "send_email",
Success: true,
Data: fmt.Sprintf(`{"status": "sent", "to": "%s"}`, to),
}
}
func executeTool(toolName string, argsJSON string) ToolResult {
var args map[string]interface{}
if err := json.Unmarshal([]byte(argsJSON), &args); err != nil {
return ToolResult{ToolName: toolName, Success: false, Error: fmt.Sprintf("参数解析失败: %v", err)}
}
switch toolName {
case "get_weather":
return executeWeatherTool(args)
case "send_email":
return executeEmailTool(args)
default:
return ToolResult{ToolName: toolName, Success: false, Error: "未知工具"}
}
}
// ───────── 4. 完整的 Agent 循环 ─────────
func main() {
messages := []Message{
{Role: "system", Content: "你是一个生活助手。当用户询问天气时,调用 get_weather 工具。当需要发送邮件时,调用 send_email 工具。"},
{Role: "user", Content: "北京今天天气怎么样?如果气温超过 20 度,发一封邮件给 boss@company.com,主题'今日天气提醒',正文'北京今天很暖和'。"},
}
tools := []Tool{weatherTool, emailTool}
maxSteps := 10
for step := 0; step < maxSteps; step++ {
resp, err := callLLMWithTools(messages, tools)
if err != nil {
fmt.Printf("❌ 第 %d 步出错: %v\n", step+1, err)
break
}
if len(resp.Choices) == 0 {
fmt.Println("❌ 空响应")
break
}
msg := resp.Choices[0].Message
// 有工具调用 → 执行
if len(msg.ToolCalls) > 0 {
for _, tc := range msg.ToolCalls {
fmt.Printf("🔧 AI 请求调用: %s(%s)\n", tc.Func.Name, tc.Func.Arguments)
// 记录 AI 的工具调用请求
messages = append(messages, Message{
Role: "assistant",
Content: fmt.Sprintf("调用工具 %s", tc.Func.Name),
})
// 执行本地函数
result := executeTool(tc.Func.Name, tc.Func.Arguments)
// 把结果发回给 AI
resultMsg := fmt.Sprintf("工具 %s 返回: %s", result.ToolName, result.Data)
if !result.Success {
resultMsg = fmt.Sprintf("工具 %s 执行失败: %s", result.ToolName, result.Error)
}
messages = append(messages, Message{Role: "user", Content: resultMsg})
}
continue
}
// 没有工具调用 → 最终答案
fmt.Println("\n✅ AI 最终回复:")
fmt.Println(msg.Content)
break
}
}
关键细节拆解
1. Tool Schema 就是函数的说明书
var weatherTool = Tool{
Name: "get_weather",
Description: "获取指定城市的当前天气信息", // 这句话最重要
Parameters: map[string]interface{}{ ... }, // JSON Schema 格式
}
AI 不知道你的代码。它只会根据你的 Description 和 Parameters 判断该不该用这个工具。Description 写得好不好,决定了 AI 能不能正确地选对工具。
2. AI 只做「决策」,不做「执行」
AI 返回的是:
{
"tool_calls": [{
"function": {
"name": "get_weather",
"arguments": "{\"city\": \"北京\"}"
}
}]
}
它说「我建议调 get_weather,参数是北京」。真正执行 get_weather 的是你的代码。 AI 没有权限访问你的文件系统、网络或数据库——除非你给它工具。
3. 结果反馈是关键
messages = append(messages, Message{
Role: "user",
Content: fmt.Sprintf("工具 %s 返回: %s", result.ToolName, result.Data),
})
执行完函数后,把结果格式化成文本塞回消息历史。AI 读到结果后,才能决定下一步。
一个真实的坑:参数幻觉
有一次我定义了一个工具 search_database,参数 query 的描述我偷懒写成 "搜索条件"。
AI 传回来的参数是:{"query": "最近一周销量最高的产品"}。这是个自然语言句子,不是 SQL。
教训:Tool 的 Description 和参数定义要精确到「机器可执行的级别」。 写法则是——如果参数是 SQL,就在描述里写明「SQL 查询语句,例如 SELECT * FROM orders WHERE…」,别给 AI 自由发挥的空间。
一眼看懂的流程
你的代码 AI API
│ │
├─ 定义 Tool Schema │
├─ 发送 [messages, tools] ───────→│
│ ├─ 分析用户意图
│ ├─ 决定用哪个 tool
│ ←──── [tool_calls] ──────────┤
├─ 解析 tool call │
├─ 执行本地函数 │
├─ 发送 [result] ────────────────→│
│ ├─ 理解结果
│ ├─ 决定是否继续
│ ←──── [final_answer] ────────┤
├─ 输出给用户 │
总结
Function Calling 不是 API 替你执行的。它只是 AI 说「我建议调这个函数」——剩下的都是你的代码在做。
理解了这一层,你就能写出完全受控的 Agent。下一篇我们解决一个工程问题:当你有 10 个 Tool 时,怎么设计统一的接口?怎么处理超时?怎么让错误不炸掉整个 Agent?
关注我,别错过。
🦞 一只用 AI Agent 搭副业产线的程序员
全平台同名:虾哥不加班
需要定制 AI 工具?来聊聊 → lob_ai
更多推荐

所有评论(0)