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

你有没有遇到过这种“高血压时刻”:用户问“明天上海下不下雨,顺便帮我订一张去虹桥的高铁票”,模型回答得头头是道,但就是一件实事没干。

这不是模型不聪明,而是它缺了一只手。

Function Calling,本质上就是给大模型装上一只“可控的机械手”——它不直接乱操作系统,而是先告诉你“我要调用哪个函数、参数是什么”,再由你决定执行不执行、怎么执行。

读完这篇,你会彻底弄明白三件事:Function Calling 到底是什么;它和 MCP、Agent 是什么关系;以及怎么把它安全、稳定地落到工程里。

一、先说人话:Function Calling 到底是什么

官方定义很直接:Function Calling(或 Tool Calling)是一种让模型输出结构化工具调用请求的机制,模型会给出函数名和参数,外部程序执行后再把结果回传给模型继续推理。

如果你把 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_salesquery_refund_ratequery_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 应用开发、工程实践和效率工具分享,欢迎扫码关注。

极客老墨微信公众号二维码

相关阅读