什么是Function Calling:让大模型学会调用工具

你有没有遇到过这种“高血压时刻”:用户问“明天上海下不下雨,顺便帮我订一张去虹桥的高铁票”,模型回答得头头是道,但就是一件实事没干。
这不是模型不聪明,而是它缺了一只手。
Function Calling,本质上就是给大模型装上一只“可控的机械手”——它不直接乱操作系统,而是先告诉你“我要调用哪个函数、参数是什么”,再由你决定执行不执行、怎么执行。
读完这篇,你会彻底弄明白三件事:Function Calling 到底是什么;它和 MCP、Agent 是什么关系;以及怎么把它安全、稳定地落到工程里。
一、先说人话:Function Calling 到底是什么
官方定义很直接:Function Calling(或 Tool Calling)是一种让模型输出结构化工具调用请求的机制,模型会给出函数名和参数,外部程序执行后再把结果回传给模型继续推理。
- OpenAI 官方指南:Function calling
- OpenAI 工具总览:Using tools
- OpenAI 帮助中心:Function Calling in the OpenAI API
如果你把 LLM 当成“会说话的大脑”,那 Function Calling 就是“神经到四肢的运动指令层”。
没有它,模型只能建议你“去查天气”;有了它,模型可以发出结构化指令:call get_weather(city='上海', date='明天')。
老墨说: Function Calling 的价值不是“让模型更会聊天”,而是“让模型开始干活”。
二、为什么它是 MCP 和 Agent 的核心基础
很多同学会把这几个词混在一起:Function Calling、MCP、Agent。它们不是一回事,但关系非常紧密。
flowchart TD
A[用户自然语言需求] --> B[LLM 解析意图]
B --> C[Function Calling
生成结构化调用]
C --> D[工具执行层
API/DB/系统函数]
D --> E[结果回传 LLM]
E --> F[最终回答/下一步规划]从工程分层看:
- Function Calling:是“调用机制层”,解决“模型怎么发指令”。
- MCP(Model Context Protocol):是“协议标准层”,解决“工具如何被统一发现、描述、调用”。参考官方规范:MCP Specification。
- Agent:是“任务编排层”,解决“何时调用、调用谁、调用几次、失败怎么恢复”。
也就是说,Agent 是会做计划的“项目经理”,MCP 是统一的“企业总线”,Function Calling 是每次实际派工时的“标准工单”。
老墨说: 没有 Function Calling,MCP 和 Agent 很容易沦为 PPT 架构图;有了它,系统才真正能跑起来。
三、工作机制拆解:一次调用到底发生了什么
以最常见的聊天接口为例,真实链路通常是 6 步:
sequenceDiagram
participant U as User
participant A as App
participant M as Model
participant T as Tool
U->>A: 询问“上海明天气温?”
A->>M: 发送消息 + tools schema
M-->>A: 返回 tool_call(get_weather, {city:上海,date:明天})
A->>T: 执行真实函数/API
T-->>A: 返回天气结果
A->>M: 把 tool result 回传
M-->>A: 生成最终自然语言答复你会发现,模型并没有越权直接访问数据库或外部 API。它只做“决策与参数生成”,执行权在你的应用里。
这里先埋一个后面会频繁出现的关键词:tool_choice。你可以先把它理解成“这轮对话里,模型到底是可选调用工具、必须调用工具,还是禁止调用工具”的开关。后续文章会详细讨论,在本篇里,你只要记住它是“调用策略开关”就够了。
这点非常关键:可控,才有企业级落地的可能。
老墨说: Function Calling 的本质是“模型提案,人类系统执行”。责任边界清晰,才能上线。
四、一个简单示例
下面这段是一个 Go 中使用 Function Calling 的最小示例,它完整演示了闭环:声明工具、接收 tool_calls、执行本地函数、回传结果、拿到最终回答。你可以把它当作第 13 篇“逐帧拆解”的母版代码。
1package main
2
3import (
4 "context"
5 "encoding/json"
6 "fmt"
7 "log"
8 "os"
9
10 openai "github.com/sashabaranov/go-openai"
11)
12
13type WeatherArgs struct {
14 City string `json:"city"`
15 Date string `json:"date"`
16}
17
18func getWeather(city, date string) string {
19 // 演示用:真实项目里这里可以改成调用天气 API
20 if city == "上海" && date == "明天" {
21 return `{"city":"上海","date":"明天","weather":"多云转小雨","temp":"18~24C"}`
22 }
23 return fmt.Sprintf(`{"city":"%s","date":"%s","weather":"晴","temp":"20~28C"}`, city, date)
24}
25
26func main() {
27 apiKey := os.Getenv("DEEPSEEK_API_KEY")
28 baseURL := "https://api.deepseek.com/v1"
29 if apiKey == "" {
30 log.Fatal("请先设置 DEEPSEEK_API_KEY")
31 }
32
33 cfg := openai.DefaultConfig(apiKey)
34 cfg.BaseURL = baseURL
35 client := openai.NewClientWithConfig(cfg)
36
37 // 定义工具函数
38 tools := []openai.Tool{
39 {
40 Type: openai.ToolTypeFunction,
41 Function: &openai.FunctionDefinition{
42 Name: "get_weather",
43 Description: "查询城市某天的天气",
44 Parameters: json.RawMessage(`{
45 "type":"object",
46 "properties":{
47 "city":{"type":"string","description":"城市名"},
48 "date":{"type":"string","description":"日期,如今天/明天"}
49 },
50 "required":["city","date"]
51 }`),
52 },
53 },
54 }
55
56 msgs := []openai.ChatCompletionMessage{
57 {Role: openai.ChatMessageRoleSystem, Content: "你是一个天气助手,需要时必须调用工具。"},
58 {Role: openai.ChatMessageRoleUser, Content: "帮我查一下上海明天的天气,然后给出穿衣建议。"},
59 }
60
61 // 第一次调用,模型会根据工具函数生成 tool_calls 消息
62 firstResp, err := client.CreateChatCompletion(context.Background(), openai.ChatCompletionRequest{
63 Model: "deepseek-chat",
64 Messages: msgs,
65 Tools: tools, // 传入工具函数
66 ToolChoice: "auto", // 模型自己决定要不要调用
67 })
68 if err != nil {
69 log.Fatalf("first completion error: %v", err)
70 }
71
72 if len(firstResp.Choices) == 0 {
73 log.Fatal("模型没有返回 choices")
74 }
75
76 fmt.Printf("firstResp: %+v\n", firstResp.Choices[0].Message)
77
78 assistantMsg := firstResp.Choices[0].Message
79 msgs = append(msgs, assistantMsg) // 关键:把模型的 tool_calls 消息放回上下文
80
81 // 处理工具函数调用
82 for _, tc := range assistantMsg.ToolCalls {
83 if tc.Function.Name != "get_weather" {
84 continue
85 }
86
87 var args WeatherArgs
88 if err := json.Unmarshal([]byte(tc.Function.Arguments), &args); err != nil {
89 log.Fatalf("arguments parse error: %v", err)
90 }
91
92 // 关键步骤:服务端二次校验(示例里最少要校验非空)
93 if args.City == "" || args.Date == "" {
94 log.Fatal("工具参数缺失:city/date 不能为空")
95 }
96
97 // 调用工具函数获取天气信息
98 fmt.Printf("调用工具函数获取天气信息: %+v\n", args)
99 toolResult := getWeather(args.City, args.Date)
100 msgs = append(msgs, openai.ChatCompletionMessage{
101 Role: openai.ChatMessageRoleTool,
102 ToolCallID: tc.ID, // 关键:必须回填对应的 tool_call_id
103 Name: tc.Function.Name,
104 Content: toolResult,
105 })
106 }
107
108 // 第二次调用,带上工具调用的结果
109 finalResp, err := client.CreateChatCompletion(context.Background(), openai.ChatCompletionRequest{
110 Model: "deepseek-chat",
111 Messages: msgs,
112 })
113 if err != nil {
114 log.Fatalf("final completion error: %v", err)
115 }
116
117 fmt.Println("最终回答:")
118 fmt.Println(finalResp.Choices[0].Message.Content)
119}
本地运行步骤:
1go mod init function-calling
2go get github.com/sashabaranov/go-openai
3export DEEPSEEK_API_KEY="你的Key"
4go run main.go
这段代码背后的工程意义是:工具声明和工具执行彻底分离。模型只负责“该调什么”,你的程序掌握“是否执行、怎么执行、执行权限”。
五、和“普通提示词”相比,它强在哪
只用 Prompt 也能让模型“假装调用”工具,但可靠性很差。Function Calling 的优势,核心在“结构化 + 可验证”。
| 维度 | 纯 Prompt 约束 | Function Calling |
|---|---|---|
| 调用格式 | 容易飘 | 结构化输出(函数名+参数) |
| 参数校验 | 靠字符串解析 | 可基于 JSON Schema 校验 |
| 安全边界 | 容易越权描述 | 执行权在你的程序 |
| 工程可维护性 | 规模变大后混乱 | 可标准化管理 |
如果要严格结构输出,可结合 OpenAI 的 Structured Outputs:Structured Outputs。
老墨说: Prompt 是“口头约定”,Function Calling 是“带签字盖章的合同”。
六、常见坑:不是不会用,是用得不完整
1) 只让模型调工具,不做参数二次校验
模型给的参数不等于可信输入。你必须在服务端做类型、范围、权限校验。
2) 工具返回后不带 tool_call_id 对齐
多工具并发时,如果回传消息不和调用 ID 对齐,模型很容易“串台”。
3) 工具描述写得像谜语
函数描述越含糊,模型越容易误调。工具文档要像给新人写 SOP,明确“何时用、何时不用”。
4) 把 Function Calling 当成“自动执行器”
它是“结构化调用建议”,不是“自动 root 权限”。真正执行前必须过你的安全策略。
老墨说: 大部分线上事故,不是模型太笨,而是开发者把安全闸门拆了。
七、三个真实落地场景
场景 A:企业知识库问答
模型先调用 search_docs 检索文档,再调用 get_doc_chunk 拉取片段,最后汇总回答。
场景 B:运营数据助手
用户问“本周 GMV 下滑原因”,模型按顺序调用 query_sales、query_refund_rate、query_campaign_logs,再给出分析。
场景 C:Agent 自动化流程
一个任务会触发多轮工具调用:先查库存,再查物流,再创建工单,最后通知用户。这里 Function Calling 是每一步动作的“原子执行接口”。
八、Function Calling、MCP、Agent 的选型建议
如果你是个人开发者或小团队,先把 Function Calling 跑顺,做出一个“能查、能调、能回传”的闭环。
如果你要接多数据源、多工具、跨团队协作,逐步引入 MCP 做标准化,减少“每换一个模型就要重写一遍工具适配层”的成本。
如果你要做复杂任务自动化,再上 Agent 编排(规划、反思、重试、状态管理)。
顺序建议:Function Calling → MCP → Agent。别一上来就追“大而全”,容易把自己绕进去。
老墨总结
Function Calling 看着像一个 API 字段,实际上是大模型应用从“会说”走向“会做”的分水岭。
你可以把它理解成 AI 工程里的“最小行动单元”:模型负责判断和组织调用意图,系统负责执行、校验和兜底。MCP 是它的标准化放大器,Agent 是它的编排放大器,但地基永远是 Function Calling。
我的建议很务实:先做小闭环,优先把一两个高价值工具接起来,打通“模型提议—系统执行—结果回传—模型生成最终答复”的完整链路。这个链路一旦跑通,你的 AI 应用就不再是聊天机器人,而是能落地解决问题的生产力工具。
文章有帮助?转发给同样在踩坑的朋友。有不同意见?评论区见。
关注公众号:极客老墨
更多 AI 应用开发、工程实践和效率工具分享,欢迎扫码关注。
