
缘起
如果我们把大语言模型比喻为一位拥有海量知识储备的超级学者,它上知天文下知地理无所不知,它能告诉你制作佛跳墙的详细步骤,甚至能精确到香料比例和火候控制。但这位“学究”却无法亲自拧开燃气灶,也无法伸手取出砂锅为你完成这碗佛跳墙的烹饪。这就是大语言模型(LLM)一开始出现时给人的深刻印象:只能进行问答,却无法和真实世界进行任何的连接。
在2023年之前,大模型的工具调用还处于"手工时代"。开发者需要像教小朋友用筷子一样,通过复杂的规则和代码教会模型使用每个工具。直到OpenAI推出Function Calling,才开启了工具调用的工业化时代。而2024年11月横空出世的MCP协议,则让这个过程变得像用USB接口连接设备般简单。
MCP的出现让工具调用生态变得无比繁荣,个人用户、开发者、企业都可以坐享它带来的便利。然而,在这种运动式的技术热潮之中,在LLM之上的层层封装,就像一场越来越浓,无法消散的迷雾,让我们很容易就迷失在封装的表象里,而忽略了大语言模型使用工具的本质。
我们会去关心、去争论:“MCP背后是不是用的Function Call?”、“Manus有没有用MCP?”,“MCP和Function Call有什么区别?”等等诸如此类的问题,网络上关于这类的文章数不胜数,但鲜有人会聊“影响MCP工具调用准确性的因素有哪些?”、“工具定义是如何影响LLM推理的?”。本文尝试从大语言模型的四种工具调用的技术实现范式,聊一聊工具调用的技术本质,以及对构建MCP Server和使用工具的一些关联关系。
在LLM底层(MaaS API层,不包括各类Agent构建平台和Agent框架的封装),工具调用的实现方法大体可以归纳为四大类:
- Function Calling
- Prompt结构化输出
- API结构化输出
- 意图识别+预定义函数
Function Calling的本质和原理

概述:雾里看花
2023年6月13,OpenAI正式对外发布了大语言模型第一个具有划时代意义的API特性:Function Calling。从此基于OpenAI的LLM API开发应用,可以让LLM应用实现外部工具的连接。Function Calling一经推出后,便成为了事实上的行业标准,其它大语言模型的工具调用能力,从API的参数设计、模型的训练方法几乎都借鉴和兼容了OpenAI的这套规范,是当之无愧的工具调用标准。
OpenAI在不久之后,陆续推出的Agent平台产品GPTs,并在该平台中创建自定义Agent的界面上默认集成了Function Calling和基于OpenAPI实现的工具执行能力,第一次在行业中探索了LLM + 工具调用的完整实现。 普通非技术用户,也可以通过界面简单配置便可实现完整的工具调用流程。Function Calling这个概念也从使用API的开发者进入到非技术人员的视野。久而久之,Function Calling的概念也自然进行了泛化和延伸。
实现原理:移花接木的艺术

《黄山迎客松》
从大语言模型问世以来,无论模态和API封装怎么变化,人模交互的模式始终没有发生改变,至始至终都是基于自然语言的交互,而自然语言的技术名词代表就是:“Prompt”。理解这一点至关重要,因为在模型之上的种种封装设计(API、产品交互等),其本质都是拼接成一个符合一定文本格式规范的“Prompt”。
Function Calling是在LLM推理引擎之上的API层接口封装,它核心解决三个问题:
-
告知LLM用户提供了哪些可用的工具。对应到Chat Completions API接口中的 tools参数 -
工具的定义是什么,解决什么问题,需要哪些输入参数。对应到tools参数中的每一个具体的tool定义 -
希望模型选择工具的行为模式是什么(自动选择、强行选择、其它)。对应到tool_choice参数
譬如,我们来看一个天气查询例子从API到底层LLM推理的Prompt的映射:
Function Calling API
curl -X POST https://api.openai.com/v1/chat/completions
-H "Content-Type: application/json"
-H "Authorization: Bearer ${API_KEY}"
-d '{
"messages": [{
"role": "system",
"content": "你是一个会使用工具的助理,请结合提供的工具清单,回答用户的问题。",
},{
"content": "北京明天天气如何?",
"role": "user"
}],
"model": "gpt-4o",
"tools": [
{
"type": "function",
"function": {
"name": "get_chinese_weather",
"description": "查询中国所有城市的天气预报",
"parameters": {
"type": "object",
"properties": {
"city": {
"type": "string",
"description": "城市或者省,如上海"
}
},
"required": [
"city"
]
}
}
},
{...其它工具定义}
],
"tool_choice":"auto"
}'
最终Transformer底层推理的Prompt
(实际拼接格式取决于各家模型自身的数据规范):
<system>
你是一个会使用工具的助理,请结合提供的工具清单,回答用户的问题。
用户提供的工具清单如下:
<tool-list>
<tool>
- 工具名称:get_chinese_weather
- 工具描述:查询中国所有城市的天气预报
- 参数:{"city":{"type":"string","description":"城市或者省,如上海","required":true}}
</tool>
</tool-list>
</system>
<user>
北京明天天气如何?
</user>
模型推理的响应结果如下:
{
"role": "assistant",
"content": null,
"tool_calls": [
{
"id": "call_abc123",
"type": "function",
"function": {
"name": "get_chinese_weather",
"arguments": "{"city": "Beijing"}"
}
}
]
}
至此,一个完整的Function Calling API的工作就结束了,通过上面例子,我们可以清楚地认识到:
Function Calling只是工具选择,并没有包含工具执行。最终输出也不过是一个json格式的函数调用描述(上述代码蓝色区域)而已,真正调用get_chinese_weather函数需要开发者在自己的应用中自行对接。
实现Function Calling的两层工作
-
算法
人类因为学会使用工具而开始进入快速发展期,模型亦如是。但首先要让模型学会使用工具,也就是在给定的工具清单中,根据用户的问题,选择是否要使用工具,使用哪个工具。这个过程就是LLM训练的过程,模型需要针对性地构建上述案例中右侧拼接后的推理Prompt数据,作为训练数据喂给模型进行学习。这个过程亦有各种考量。
譬如:
-
一次是否可以输出多个工具调用?
-
先输出调用工具描述还是直接输出调用?
-
是否同时输出调用工具的解释和工具调用本身?
上述考量本文不做详细论述。读者只需知道Function Calling能力是需要单独训练的,这也是为什么我们看到很多模型能力虽然很强,但却不支持Function Calling,譬如大名鼎鼎的DeepSeek R1。OpenAI的O系列推理模型,从发布至今,也是经历过半年以上的时间才在近期支持了Function Calling。
-
工程
模型层支持了工具调用后,工程层面要做的就是设计一个标准的Function Calling API,以便为开发者提供统一的开发体验。这个过程相当于给模型的工具调用能力提供一本标准化的操作说明书。
- tools参数的设计参考了JSON Schema协议,为开发者定义了一个统一的描述工具的规范;而模型提供方会在后台自行实现一套Prompt拼接规则,如上面获取天气的例子所示。
- tool_choice参数则根据开发者使用该API的场景诉求,提供了模型输出工具调用的模式的定制能力,譬如:
- auto模式:模型自主判断是否需要调用工具。通过对比用户输入与已注册工具的功能描述,计算意图匹配度。当模型认为外部工具能更准确或高效解决问题时(如需要实时数据或执行操作),生成工具调用请求;否则直接输出自然语言响应。
- required模式:强制模型必须调用至少一个工具。通过修改模型的输出概率分布,抑制自然语言生成的权重,使工具调用成为唯一有效输出路径。具体的实现其实是在底层模型Infra工程上,通过在解码阶段屏蔽非工具调用的token序列,仅保留符合tool_calls结构的输出来实现的。
- function模式:将工具选择空间限制为单个预定义函数。模型仅解析用户输入中与该函数参数相关的信息,忽略其他工具的存在
- none模式:完全关闭工具调用能力,模型仅依赖内部知识生成响应。实现方式包括从请求中移除tools参数或显式设置tool_choice="none",以及在预处理阶段过滤工具相关的prompt指令,确保模型不接收工具定义信息。
实际的处理需要考虑的比上述会更多,更细致,本文重在知其理,因此其它细节不再赘述,感兴趣的可以自行研究。
MCP和Function Calling的关系:剪不断理还乱

自从Anthropic推出的MCP(模型上下文协议)火了之后,MCP和Function Calling的关系的阐述和争论在网络上就从来没有停止过。总结起来无非如下两种观点:
-
MCP背后使用的是Function Calling?
-
MCP会取代Function Calling?
先说结论,这两种观点严格说来都是不正确的。先说第一个:
观点A:MCP背后使用的是Function Calling
上文提到LLM工具调用的实现主要有四种方法,Function Calling只是其中之一,是以OpenAI的实现为行业标准的影响力最大的一种实现,但不是唯一的实现。MCP是一个支持工具调用的Agent上下文协议,他本身只定义了MCP Server要如何向MCP Client提供支持的工具清单的接口规范,如下所示:
// 工具清单查询请求
{
"jsonrpc":"2.0",
"id":"unique_request_id",
"method":"tools/list",
"params":{}// 通常为空对象,保留扩展性
}
// 工具清单查询的响应
{
"jsonrpc":"2.0",
"id":"original_request_id",
"result":{
"tools":[
{
"name":"get_weather",
"description":"查询指定地点的实时天气",
"input_schema":{
"type":"object",
"properties":{
"location":{"type":"string","description":"城市名称,如'北京'"},
"unit":{"type":"string","enum":["celsius","fahrenheit"]}
},
"required":["location"]
}
},
{
"name":"search_web",
"description":"互联网搜索引擎",
"input_schema":{
"type":"object",
"properties":{
"query":{"type":"string"},
"max_results":{"type":"integer","minimum":1}
},
"required":["query"]
}
}
]
}
}
MCP协议并没有要求MCP Host必须要使用Function Calling来实现工具调用,作为一个协议设计者,也断然不会将协议与某种功能的实现路径直接绑定,这种设计模式本身就是有缺陷的。那么为什么网络上很多人会持有这种观点呢?原因很简单:Function Calling这个名字太过于深入人心,在很多没有深入研究的朋友记忆中,基本上把Function Calling和工具调用划等号了。 外加很多只输出What信息的媒体铺天盖地的广而告之,自然就会有这样的现象。
一个MCP应用(MCP Host,例如:Cursor、Cline)要实现基于MCP的工具调用,在工具调用的技术路径选择上是需要做很多考量的。这不是一个简简单单的选择题,而是一套复杂的工程策略,举个例子大家就明白了:
Cline作为一个开源的IDE插件,允许开发者自定义接入各种模型的API,但问题在于,并不是所有模型都支持Function Calling 能力的,尤其是像DeepSeek R1这样的推理模型,大多都不支持。因此在工程实现上,可能就会是这样的策略:
- 维护一个Model Provider配置清单,包含是否支持Function Calling的配置
- 基于用户选择的Model,判断是否支持Function Calling。如果支持,则使用Function Calling作为工具调用的技术;如果不支持,则选择其他技术路径;
- 用户选的模型是否支持强制使用结构化输出?如果是,则使用Prompt + 强制的结构化输出作为技术方案;如果不是,则使用纯Prompt的限定
- 使用纯Prompt限定输出+正则匹配的技术方案,做好模型输出不正确的兜底工作。
综上所述,读者应该可以自行判断为什么这个观点是不正确的了。
观点B:MCP会取代Function Calling
有了上述认知,我们就可以轻易地判断这个观点为什么是错误的了:MCP是比Function Calling大的多的概念,除了支持工具查询和调用之外,还支持Prompt、Resources等所有可以影响模型上下文的内容的抽象和通信标准定义。而Function Calling只是在MCP 应用需要执行工具时的一种技术实现而已,而且更为重要的是,Function Calling并不涉及到工具执行的层面,仅仅提供了工具调用的选择和描述。上面的例子很形象地说明了这一点。
那么为什么很多人会有这样的认知呢?这里头最主要的原因是混淆了Function Calling的定义。OpenAI 在推出Function Calling API时,也没有提供任何关于Function Calling的应用落地实践。直到推出了GPTs之后,才将Function Calling技术作为GPTs平台上默认的工具调用技术予以集成,并且在GPTs平台中,和Function Calling配合的还有基于OpenAPI规范的工具执行环节的设计。
只是很多非技术朋友搞不清楚这里面的概念边界,为了方便理解,直接把Function Calling + 基于OpenAPI的工具执行打包称之为Function Calling。 因此随着科技媒体的传播,大家也渐渐分不清楚这个词的定义边界了。类似的Case还有MCP Host和MCP Client的关系, MCP协议中非常清晰地定义了什么是MCP Client,什么是MCP Host,但随着MCP被越来越多人熟知,理解这两个概念的区别和边界颇为麻烦,传统互联网的CS架构深入人心,方便对照理解,于是这两个概念就被混为一谈了。
Function Calling在应用上的优缺点
Function Calling作为一个LLM的能力项,它本身也是有可用性(准确性)的概念的。就像你给相同的任务和工具给到一个聪明人和一个傻子,大家对于是否该使用工具,使用哪个工具,怎么使用工具的理解都是完全不一致的,这很大程度取决于LLM的基座能力和是否有针对性地训练过这个能力。因此我们可以尝试总结一下Function Calling这个工具调用的实现技术有哪些优缺点,以方便读者在开发中进行更好的选择:
优点
Function Calling的优点主要体现在以下几个方面:
-
标准化程度高 标准化程度高非常好理解,行业上基本支持Function Calling的模型都Follow了OpenAI的接口规范,即便有个别的没Follow,很多模型代理的开源库也都做了兼容了。 -
准确率有保障 敢于对外暴露Function Calling API的厂商,都是经过精心训练的,如果准确率太低,岂不是拍拍打脸,上个新功能都不敢声张的那种。因此如果哪家厂商有刻意强调过其Function Calling能力的准确性,一定建议去试试对比一下。 -
主流云厂商均提供支持 因为标准化,所以主流的云厂商都按照OpenAI的接口标准提供了Function Calling的支持,有些厂商还针对不同厂商在Function Calling的是实现差异上做了封装,或者优雅降级等。开发体验比较友好
缺点
Function Calling的缺点主要有以下几点:
-
需要特殊训练 上面已经讲过,细节不再赘述。因为需要训练,所以并不是所有模型都支持,而且效果也不同,这对于开发者在集成不同模型时会带来额外的选择和对比成本。
-
参数生成出错需额外处理 模型在输出工具调用的时候,可能生成无效参数(如字段缺失或类型错误),需开发者做额外的校验
-
工具描述过于冗长 使用了JSON Schema协议来描述工具定义,上面例子可以看到,一个函数的描述就有上百Token,如果接入工具有几十个,每次推理都需要把这一大坨工具描述传给模型推理,可想而知Token耗费有多高。
-
责任模糊 因为Function Calling的封装,开发者会下意识地认为工具选择或者参数设置有问题,是模型能力的锅。但真相是,工具定义中的name、description、parameters的内容,以及工具清单的选择,这些都会影响最终的效果。开发者在使用Function Calling的时候,需要清晰地认识到:这些不起眼的工具定义,其实正是Prompt本身。如前所述