Function Calling介绍

Function Calling(函数调用)是模型的一项输出能力:模型不只输出自然语言,还能输出一段结构化的调用申请——“我要调用这个函数,参数是这些”。

这个名字起得有误导性,先说清楚最容易搞错的一点:

模型不执行任何函数。 它只是把”我想调什么”表达成一个 JSON 对象。真正去执行的永远是调用方——你的代码。

谁做 做什么
模型 决定要不要调、调哪个、参数填什么,输出成结构化数据
你的代码 接收这个申请,执行,把结果发回去

所以更准确的说法是”函数调用申请“。这个往返的完整形状(tool_use / tool_result 怎么配对)上一篇已经拆过了,这里只记住分工:模型负责开口,你负责动手。

1. 它解决了什么

一个流行的概括是 Agent = LLM + Tools + RAG。这个式子里,Function Calling 是让 Tools 那一项真正生效的机制——没有它,Tools 就只是一份没人调用的函数表。

具体填补了三处缝:

一、扩展模型能力。 模型本身无法直接操作外部系统。它的知识停在训练数据那一刻,也拿不到你库里的数据。调函数就能补上:

  • 实时数据 —— 天气、股价、新闻
  • 复杂计算 —— 数学运算、代码执行
  • 操作外部系统 —— 发邮件、查 GitHub、控制设备

二、结构化输出。 把自然语言请求变成结构化参数:

1
2
3
用户说:明天北京天气如何?
↓
模型调用:get_weather(location="北京", date="2026-09-16")

这一步的价值容易被低估。没有它,你得自己写正则去解析模型的自然语言——而模型的措辞每次都不同。有了它,你拿到的是一个可以直接传进函数调用的对象。

三、动态决策。 要不要调、调哪个、调几次,都是模型根据上下文现场决定的,甚至可以链式调用——先查天气,再推荐穿搭。

一句话:Function Calling 是模型从”理解语言”走到”采取行动”的那座桥。

2. 参数的 JSON 是怎么被约束出来的

模型输出的是逐 token 生成的文本。它是怎么保证参数永远是一段合法 JSON 的?

各家实现细节不同,但目标一致——让模型吐出来的参数几乎总是能通过 schema 校验。大体两条路:

  • 训练:模型见过海量”工具定义 → 调用请求”的样本,学会了照着 schema 填
  • 推理时的约束:解码时把不符合 schema 的 token 排除掉,从生成空间上堵死语法错误

两条路叠加的结果是:格式基本不用你操心。

但这里有一条界线必须划清楚:

schema 约束的是格式合法,约束不了语义正确。

input_schema 说 city 是字符串——那模型给你 "北京" 还是 "北极",schema 管不了。说 date 是 YYYY-MM-DD 格式——那填今天的还是明天的,schema 也管不了。

格式问题基本被解决了,剩下的全是语义问题。而语义问题只能靠上下文去调——这就是下一节。

3. 准确率是个要调出来的东西

笔记里有一句很实在的话:“50% AI 能写对”。

这就是 Function Calling 的真实起点。它不是”接上就能用”的能力,而是一个有准确率、而且准确率可以被显著改善的能力。要调的东西分两层:

system prompt 这一层

放什么 为什么
角色 “你是一个数据分析助手”——决定它的默认倾向,影响它倾向用哪类工具
建表语句 把表的 DDL 贴进去。不贴,它只能猜表名和字段名,猜错就是 SQL 报错
私有知识 业务口径(”活跃用户”指 7 天内有登录的)、枚举含义(status=3 是”已取消”)
few-shot:示例 几个”问题 → 正确调用”的样本,比用文字描述规则有效得多
few-shot:错题本 把这个项目上真实踩过的错挑出来当反例——这是最值钱的一类样本

这一层的投入产出比最高,而且它完全是内容工作,不是代码工作。工具接线只要一次,这几样东西要一直往上加。

tools 这一层

工具的描述同样在决定准确率:

  • 描述要写清”什么时候用这个”,也要写清”什么时候不要用”——模型在三个相似工具之间选错,绝大多数时候是因为描述里只说了它们能做什么,没说它们的分工界线在哪
  • 参数描述每个字段都要写,尤其是枚举取值和格式要求

4. 一个具体的例子:SQL 工具该怎么设计

需求:让 Agent 能查数据库。

做法一:两个工具。

1
2
3
@register_tool("write_sql")
def write_sql(question: str) -> str:
"""把自然语言问题翻译成 SQL 语句"""
1
2
3
@register_tool("exec_sql")
def exec_sql(sql: str) -> str:
"""执行 SQL 并返回结果"""

做法二:一个工具。

1
2
3
@register_tool("exec_sql")
def exec_sql(sql: str) -> str:
"""执行 SQL 并返回结果"""

做法二就够了,而且更好。

原因很直接:模型输出参数的时候本来就能直接生成 SQL 文本,它不需要一个”写 SQL”的工具来替它写。write_sql 做的事情是”输入一句话、输出一段文本”——这是一次思考,不是一次外部动作。

用 ReAct 的话说(ReAct 与 Agent Loop §1):Thought 不改变环境,Action 才改变环境。 写 SQL 属于 Thought,它什么都没改。把它做成工具,等于让模型先调一次工具来”想”,再去调另一个工具来”做”——白白多一个往返,还多一次出错的机会。

由此可以提炼出一条工具设计原则:

一个工具 = 一次外部动作。

判断标准一句话:这件事模型自己能不能得出答案?

  • 能(写 SQL、拆解步骤、组织格式)→ 不用做成工具,让它留在模型的思考里
  • 不能(查数据库、读文件、发请求)→ 才需要工具

按这条标准回头看,很多”AI 写不对”的问题其实是工具设计的问题:工具切得太碎,模型就得把一半精力花在编排工具上,而不是解决问题上。

5. Function Calling 和 MCP 是什么关系

先说结论:它们不是一回事,所以不存在”有了 MCP 就不需要 Function Calling”。

常见的对比表里有两处说法要修正:

一、Function Calling 不是”模型厂商的私有接口”。 它是所有主流模型的通用能力,只是字段名不同——Claude 叫 tool_use,OpenAI 叫 function calling / tool_calls,Gemini 叫 functionCall(对照表在 LLM 请求与流式响应 §1 末尾)。

二、MCP 不替代 Function Calling。 它们根本不在同一层:

Function Calling MCP
在哪一层 模型侧的能力 工程侧的接入标准
解决的问题 模型怎么说它要调工具 工具从哪来、怎么连进来
管的事 一次调用 连接、发现、调用、生命周期
两者关系 — MCP 工具最终也是通过一次 Function Calling 被调用的

第四行是理解这件事的关键。模型想调一个 MCP 工具时,它输出的仍然是一个普通的 tool_use;是 Agent 收到这个 tool_use 之后,才把它翻译成 tools/call 发出去的(MCP 协议与工具接入 §4)。

所以 MCP 是站在 Function Calling 上面的一层,而不是并排的另一个选择。

那什么时候只用 Function Calling 就够

  • 工具是你自己写的、只给你自己这个应用用 —— 直接在代码里注册就行,不需要 MCP 的连接、握手、发现那一整套
  • 工具就一两个、很简单 —— 上 MCP 是杀鸡用牛刀
  • 你在意延迟 —— MCP 至少要付一次握手 + 一次 tools/list,走 HTTP 还要多一层网络

什么时候值得上 MCP

  • 工具要给多个应用复用 —— 命令行 Agent 用一个、IDE 里用一个、网页端再用一个
  • 工具由第三方提供,或者你想直接用别人写好的现成 Server
  • 工具会动态增删,需要”发现”这个动作

笔记里那句结论是对的:MCP 可能成为主流,但 Function Calling 作为底层能力会一直存在。 更准确的说法是——MCP 不会取代它,因为它必须建立在它之上。

6. Skill 是更完善的 Function Calling 吗

不是。 Skill 不是”更完善的函数调用”,因为它压根不是一个函数。

一个 Skill 就是一个文件夹,核心是一份 SKILL.md——frontmatter 里写 name 和 description,正文是操作说明。可以再带上脚本、参考文档、模板。它是一包指令和流程,不是一次外部操作。

真正的差别不在先进程度,在加载的东西:

Function Calling / 工具 Skill
传的是什么 一份 JSON Schema,描述一个可调用的操作 一段自然语言说明,讲这件事该怎么做
什么时候进上下文 每轮请求都带着(tools 整份发) 平时只有 name + description(约 100 token),命中了才把正文读进来
装多了会怎样 每个工具都是固定开销——80 个工具约 15,000 token 100 个 Skill 也只占 100 份元数据的量
给模型的是 能不能做 该怎么做
出错的样子 schema 对不上就报错,很响 说明被理解偏了就悄悄做错,很安静

最后两行是这个对比里最关键的部分:工具是能力接口,Skill 是操作方法。 一个 Skill 里完全可以让你去调好几个工具。

那为什么它看起来像一次函数调用

因为在 Claude Code 里,模型决定加载哪个 Skill,走的确实是工具调用那套机制——有一个 Skill 工具,参数是技能名,模型自己判断该不该调它(除非显式设成只能由用户手动触发)。

但这只说明”加载 Skill 这个动作“是一次 function call,不说明 Skill 本身是 function。取回来的东西完全不同:

  • 函数调用取回来的是操作执行完了的结果
  • Skill 取回来的是接下来该怎么做的说明

所以准确的说法是:Skill 是包在 Function Calling 外面的一层——模型用一次工具调用来取一段说明,然后照着那段说明,去调别的工具。

一句话记住:工具给的是”能不能做”,Skill 给的是”该怎么做”。

7. 小结

一、Function Calling 是模型的输出能力,不是执行能力。 模型只输出”我要调什么、参数是什么”,执行永远是调用方的事。叫”函数调用申请”更准确。

二、它填的是”理解语言”到”采取行动”之间的缝:拿实时数据、操作外部系统、把自然语言变成结构化参数,以及由模型自己决定调不调、调哪个、调几次。

三、schema 约束格式,约束不了语义。 参数是不是合法 JSON 基本不用操心;city 填得对不对,schema 管不了。剩下的全是语义问题。

四、准确率要调,而且主战场在 system prompt 和工具设计上。 角色、建表语句、私有知识、示例和错题本——这层投入产出比最高,也最不”工程”。工具这边守住一条:一个工具 = 一次外部动作,模型自己能想出来的东西不用做成工具。

五、Function Calling 和 MCP 是两层,不是两个选项。 FC 在模型侧,MCP 在工程侧,MCP 工具最终也是通过 FC 被调用的。工具只有自己用、只有一两个,直接写 FC 就够;要复用、要给第三方用、要动态发现,才值得上 MCP。

六、Skill 不是更完善的 Function Calling。 工具给”能不能做”,Skill 给”该怎么做”。加载 Skill 靠的是一次工具调用,但取回来的是方法,不是结果。

参考

作者

wangyawei

发布于

2026-09-15

更新于

2026-09-15

许可协议