mcp vs function calling

自 Anthropic 公司推出 MCP 之后,有很多人把 Function Calling 与 MCP 做了类比。其中流传最广、最为常见的一种说法便是:MCP 统一了 Function Calling 的协议,有了 MCP 之后,Function Calling 便没有存在的必要了。这种说法到处可见,流传极广,甚至还会出现在各类 “专业” 的科普视频和科普文章中。乍一看这种说法好像是对的,毕竟这两者做的都是调用工具相关的事情,而 MCP 更加的标准,那肯定是 MCP 取代 Function Calling 了。但实际上这种说法其实是有很大问题的。Function Calling 和 MCP 并不是互相替代的关系,而是互补的关系。它们在大模型领域所处的地位完全不同,在未来很长的一段时间里,Function Calling 和 MCP 都将同时存在,各自发挥自己的价值。

实际上在 MCP 终极指南的基础篇中,我也提到过 Function Calling 和 MCP 甚至可以同时出现在一个链路中,因为它们在链路中所扮演的角色完全不同。为了帮助大家搞清楚 Function Calling 和 MCP 之间的关系,我整理了本篇文章,给大家全方位介绍一下 Function Calling,主要是覆盖了 Function Calling 的具体概念、使用方法和协议细节,让大家彻底明白 Function Calling 到底是个什么东西?与 MCP 的关系到底是怎么样的?本文最后的例子,为了验证我的说法,我将演示如何在一个链路中同时使用 Function Calling 和 MCP。此外,本文所有的代码均会放在GitHub 仓库中,有需要的请自行查看。

什么是 Function Calling?在通常情况下,一个模型训练好了之后,它的知识库就定格了,对于超出知识库范围的答案,它无法给你满意的回复。用经典的问答场景举例,用户问模型一个问题,模型就只能从自己的知识库里面寻找答案,如果用户问的问题超出了模型的知识范围,模型要么回答不知道,要么就胡言乱语。那现在试想一下,如果模型有一种方式能够调用天气接口,拿到纽约最近几天的天气信息,那模型是不是就可以回答这个问题了?没错,这就是 Function Calling 所提供的能力了。

简单来说,Function Calling 指的是模型与外部工具交互的一种能力。在这些外部工具中,有些可以用来查询天气,有些可以用来查询新闻,有些可以用来发送邮件,这些都统称为外部工具。不过这个图其实有些问题,因为模型自己其实是无法调用工具的,它想调用还需要一个中间人来帮它做这件事情,暂时你不必细究。工具这种说法比较抽象,换一个词更合适一些,那就是函数。没错,工具本质上就是编程语言里面的函数。还是以我们前面的天气预告来举例,假如说我们想要使用查询天气的工具,那本质上可能就是要调用这个叫做 get_forecast 的函数,你传入所查地区的经纬度就可以拿到对应的天气预告结果。在这个过程里面,我们既可以说是使用了查询天气的工具,也可以说是调用了查询天气的函数,这两种说法都是一样的。再回头想想 Function Calling 这个词,那不就是函数调用吗?所以使用工具就是调用函数,说白了,Function Calling 就是模型调用函数的一种能力。在调用函数之后,我们不是可以拿到函数的返回结果吗?模型就可以从这个结果中总结答案,再告诉用户纽约明天的天气信息。是不是还是感觉有些抽象?没关系,我们可以拿 ChatGPT 来举个具体点的例子,辅助你理解。

Function Calling 链路分析。首先在输入框中输入:纽约明天的天气怎么样?然后我们点击发送,此时从界面中可以看出,ChatGPT 先搜索了网络,搜索结束之后才给出了最终答案。

我不清楚它背后是用什么原理完成这条流程的,毕竟是 Closed AI,除了早期的模型之外,什么也没有 open。不过我可以肯定的是,这套流程用 Function Calling 的方式是肯定可以完成的,而且它大概率用的就是 Function Calling,因为这个技术就是 OpenAI 发扬光大的。所以后面我假设它用的就是 Function Calling,给你画一下整体的链路图,让你看看它整个链路是怎么运转的。当然,我要提前给大家打个预防针,如果 OpenAI 不是用 Function Calling 来实现搜索功能的话,请你也不要喷我。我不是来探讨 OpenAI 是如何实现搜索的,我是要以这个操作为例,讲一下 Function Calling 的具体流程。后面我们还要用 Function Calling 的形式来做一个跟它一样的链路,先搜索,再回答。所以我向你保证,这个链路用 Function Calling 来实现绝对是没有问题的。

首先,整个链路至少有两个角色:用户和 ChatGPT 应用。大致流程是:用户发送问题给 ChatGPT 应用,ChatGPT 应用返回答案给用户。注意,我这里所说的 ChatGPT 应用不是指 GPT4o 模型,而是包括 GPT4o 模型在内的一个完整的用户产品,也就是我们前面演示的时候所使用的这整个的应用。它不仅能够调用模型,还能够搜索网络、存储历史对话内容等,跟 GPT4o 模型并不是一回事。说明完了 ChatGPT 应用的概念,我们再来继续看看链路。当用户的请求发送给 ChatGPT 应用之后会发生很多事情,ChatGPT 应用会调用 OpenAI 的服务器,OpenAI 的服务器又会调用 GPT4o 的模型,还会搜索网络等。所以为了把事情解释清楚,我们需要把 ChatGPT 应用拆分为三个角色,分别是:网络搜索函数、OpenAI 服务器和 GPT4o 模型。到这里为止,我们的四大 boss 就算是集齐了,下面我们就要研究下这四个 boss 在这段时间里面到底是干了些什么事情。一开始,用户的问题会发送给 OpenAI 服务器,OpenAI 服务器接到问题之后会把用户的问题转发给模型。不过同时给到模型的不仅仅是用户问题,还有可用的工具,比如搜索网络、搜索文件之类的。这其实就是告诉模型:用户想知道这个问题的答案,你看看你能不能直接回答,如果可以就直接给我答案好了;否则我这里还有些工具,你看看能不能用上,想要哪个跟我说。模型接到了 OpenAI 服务器的请求之后,先评估了下用户要问明天的天气是吧?那我自己肯定是回答不出来的,我的知识到今年一月份就截止了。不过好在这里有个搜索网络的工具,我要不就用它先搜索网络。于是模型就决定要使用这个工具了。不过注意,模型的功能只是接受用户的问题,返回对应答案,它本身其实是没有使用工具的能力的。那谁能使用工具?当然是 OpenAI 服务器。所以这里模型仅仅是返回了一个工具使用请求给 OpenAI 服务器,告诉他:我想要使用搜索网络这个工具,工具参数是纽约明天的天气,请帮我调用一下。OpenAI 服务器接到请求之后,调用了对应的网络搜索函数,函数执行完毕之后返回了对应的结果。openai 服务器拿到结果之后紧接着把它发回给了模型。模型接到结果后根据用户问题总结了一份最终的答案,并把它发给了 openai 服务器。openai 服务器再把答案返回给用户。整个对话到此就结束了,这就是刚才问完问题之后所发生的事情。

你可能不禁要问:链路我明白了,function calling 在哪?别着急,不妨先猜一猜。首先 function calling 翻译成中文是函数调用,链路里面哪个地方在调用函数?没错,就是这个地方。function calling 肯定就发生在这个环节里面了,对吗?不对,这个就是很多人理解上第一个误区,也正是这个想法让很多人认为 mcp 会取代 function calling,毕竟都在干调用函数的事情。

实际上虽然 function calling 这个名字的含义是函数调用,但是它本身并没有规定对应的函数到底是如何调用的,所以它跟红色框里面的这一部分几乎没有什么关系。function calling 这个名字有一定的误导性,回忆一下之前所说的 function calling 的概念,function calling 是模型调用函数的一种能力,这种能力是需要一个中间人去参与的。在链路里面这个是模型,这个是函数,openai 服务器就是那个人。不过光有这三个角色还不够,想要搞清楚 function calling 到底是什么,还需要把模型这个角色再具体细化一下。首先链路用到了 gpt4o 模型,但这个地方写 gpt4o 模型并不准确,openai 服务器调用的并不是 gpt4o 模型,它调用的其实是 gpt4o 模型的 api,api 就是 http 接口,接口内部所调用的才是真正的 gpt4o 模型。在 api 拿到请求之后会把请求做个转换发给真正的 gpt4o 模型,gpt4o 模型返回结果之后对应的 api 还会再把模型的结果再做一次转换,通过 api 接口发送出去。解析工具调用结果的这一部分也是如此,api 先将工具执行结果给到模型,模型解析之后再返回最终答案。你可能会问:模型的输入和输出还会跟模型 api 的输入和输出不同吗?怎么中间还有个转换?确实是不同,由于模型训练方法的差异,每个模型能接受的参数格式以及它输出的内容格式都会有所不同,所以这一层转换是必须的。再回忆一下前面说过 function calling 指的是模型调用函数的一种能力,模型在这,函数也通过参数传给模型了,所以严格来说 function calling 是作用在这一部分的。

具体来说,如果模型能够挑选函数、解析函数执行结果,并且根据结果给出最终答案,那么就称模型有 function calling 的能力。不过我相信大家基本上都没有跟模型直接打过交道,顶多就是调用个模型的 api。实际上对于 gpt4o 这种闭源模型,你也没法跟它打交道,连模型都拿不到,还打什么交道?能做的就是通过模型的 api 来间接地使用 gpt4o 的模型。所以在大部分情况下会把 function calling 的概念稍微地变一变,把它变为模型 api 调用函数的能力,具体指代的就是这一部分的内容。仔细看一看这两部分其实本质上是一样的,都是从函数列表里面挑选函数的能力,只不过一个是用模型 api 在挑选,一个是用模型在挑选。

一般情况下只要模型有了 function calling 的能力,对应的模型 api 也必将有 function calling 的能力,毕竟模型 api 与模型之间也仅仅只有一些转换逻辑而已。由于大部分情况下调用的都是模型的 api,而并非直接调用模型,因此关注的更多是模型的 api 是否支持 function calling。鉴于这个原因,在本视频中着重讲解的就是模型 api 的 function calling 能力,也就是图中红色框的内容。而对于模型api涉及的与真实模型进行的一些交互暂时不涉及,所以就先把这一部分去掉以简化流程。现在终于得到了 function calling 的作用环节了,就是红色框显示的这一部分。不过这个推导过程并不简单,如果你还是不太明白,建议你再把这段看一遍。

function calling 作用在红色这一环节,如果像很多人所说的那样 function calling 和 mcp 是互相替代的关系,那 mcp 也必然是作用在红色框这个环节的,但实际上并不是这样。如果你看过我 mcp 终极指南的基础篇,就会明白 mcp 本质上就是一套函数的发现与调用协议,它只作用在服务器和函数之间,也就是这里。mcp 是规定了服务器如何发现函数,如何调用函数。mcp 本身其实跟模型并没有什么直接的关系,跟模型有直接关系的是 function calling。

注意看,之前标红那一部分是 function calling,上图说明的这一部分是 mcp,从这里就可以看出 mcp 与 function calling 分别作用在链路的两个环节,发挥的作用各不相同,根本就没有互相替代这一说。另外本文章着重讲的是 function calling,mcp 的使用和相关原理将一带而过。如果你对 mcp 的运行链路一知半解,建议到我的之前的文章里面找一下 mcp 终极指南的基础篇、进阶篇和番外篇三篇文章,这三个文章看完之后,我保证你可以对 mcp 的使用和运行原理有一个彻底而清楚的了解。

function calling 背后是有一套协议的,这套协议规定了工具列表要怎么传给模型 api,而模型 api 又如何返回所选的工具以及对应的参数。各个大模型厂商的 function calling 协议大致相同,不过有些细微的区别。

想要彻底搞懂肯定还是要看一下协议到底写了些什么。搞懂协议的最好办法就是截取模型 api 的输入和输出,不过对于前面那个示例是没有办法截取模型 api 的输入和输出的,毕竟 chatgpt 应用又没有开源。所以我借助大模型的力量写了一个与大模型聊天的应用,我给它起了个名字叫做 mark chat。在这个应用里面我截取了模型 api 的输入输出,并且打印到了外部文件里,所以通过分析外部文件就可以得知 function calling 协议到底是个什么样子的。首先我来给你演示一下 mark chat 的使用方法。启动服务器,打开 127.0.0.1:5000 这个地址

# 运行方法

1. 在当前目录中新建一个名为 `.env` 的文件,内容如下:

   ```text
   OPENROUTER_API_KEY="sk-xxx"
   ```

   其中 `sk-xxx` 替换为你的 OpenRouter API Key。

2. 启动服务器

   ```bash
   uv run start.py
   ```

3. 打开浏览器,访问 `http://localhost:5000`。

输入问题:纽约明天的天气怎么样?稍等一会就拿到了答案,可以看到 mark chat 的返回一共是包含了两部分的内容。

第一部分是调用了一个叫做 search 的工具,在网上搜索了纽约明天的天气预报,并且拿到了工具的调用结果。

第二部分则是模型根据工具的返回结果做出了最终的解答。整个流程很简单,而且跟在 chatgpt 里面看到的流程基本一致,先调工具,再做总结。既然了解了整个流程,不妨就进去看看代码,分析下它到底写了些什么?markchat 的代码量其实还挺大的,毕竟前后端都有,不过好在大部分的代码根本就不用管,因为这里面绝大多数代码跟 functioncalling 都没什么关系。

着重看的就是 backend.py 文件,打开这个文件之后再找到其中的 process_user_query 函数。当用户问题发给 markchat 之后,mark chat 便会去调用 process_user_query 函数来获取答案,这个函数里面的代码便是 functioncalling 的核心逻辑了,所以来仔细研究一下。首先 query 参数代表的含义是用户的问题,比如在这个例子里面,query 的值就应该是纽约明天的天气怎么样?在接到用户的问题之后,这个函数会把用户的请求内容放入到 self.history 列表里面,相信从这个变量的名字里面也可以看出 self.history 代表的就是历史消息。现在 self.history 中只有用户的问题,写的内容比较少,但是后面就可以看到还会把模型 api 的工具选择结果、工具的执行结果都放到 self.history 里面,并在请求模型 api 的时候再发给它。因为模型本身是没有记忆的,所以如果模型想要了解之前发生了什么,就必须要从历史消息里面找。在处理完了 self.history 之后,调用 call_model 函数,这个函数主要是把用户问题发送给了模型 api,让它来解答。模型 api 是从 self.history 这个变量里面获取到用户问题的,具体实现后面再看。然后把模型 api 的返回放入到 first_model_response 这个变量中,first_model_response 的值大致是这个样子的,它代表的含义是第一次调用模型时模型的返回结果,其中的核心内容是 message,它里面包含了模型想要调用的函数以及对应的调用参数。所以在代码里面先把 message 提取出来,再放到历史消息中。然后检查下模型是否想要调用工具,如果想,把工具名称和工具参数提取出来,然后调用 self.execute_tool方法执行工具,再把执行结果放入到历史消息列表里,再次请求模型 api。这是第二次请求模型 api 了,这次请求的时候模型 api 就可以从历史消息列表里面获取到工具的执行结果,并且给出一个最终答案。把这个最终答案抽取出来之后放入到 final_message 变量中,再把 final_message 也加入到历史消息列表里。最后把所有的信息都放在一起打包返回。

之前讲的是有工具调用的场景,else 分支便是处理没有工具调用的情况,例子暂时不涉及,所以就不用管它了。

到这里整个方法就告一段落了,让我们先复习一下,总结总结整个链路里面最重要的几个部分。

第一、调用模型 api。

第二、提取工具名称和参数。

第三、调用工具。

第四、再次调用模型 api,这次调用的时候模型 api 会拿到工具的执行结果,并根据结果做出判断。

第五、返回模型 api 给出的最终答案。不知道你是否对这五步有一种似曾相识的感觉?想想之前画的这个链路图,这五步其实跟前面所聊的这个链路图是一样的,只不过用的不是 openai 的服务器而已,用的是 markchat 的本地服务器。再回来看看代码,在之前梳理链路的过程中漏调了三个函数的实现,现在来看这三个函数里面写了些什么?

第一个是 call_model,用于在第一次调用模型 api 的时候使用,来打开看看。这里面主要是用 post 方法请求了 openrouter 的接口,openrouter 是个大模型调用平台,在其中可以调用各个类型的模型 api,包括 openai 的、claude  等等,很有名,在 mcp 中级指南的基础篇里面讲过。当然这里直接换成 openai 的 api 也是可以的,方案与平台无关,因为 openrouter 可以非常方便的切换模型,所以我用 openrouter 比较多,这里也用它进行演示。这里面的 self.router_base_url 对应的就是 openrouter 的 url,在这个类创建的时候 self.router_base_url 就已经设置好了。而请求体也就是 request_body 一共是包含四个部分,分别是 model、messages、tools 和 stream,这些字段的含义后面会详细阐述,先不用管。之后会把模型 api 的请求和返回单独打印到一个文件中,后面便会查阅这个文件的内容来了解 functioncalling 协议的具体细节。最后返回模型 api 给出的最终答案,整个方法就结束了,这是漏掉的第一个函数。

第二个漏掉的函数叫做 execute_tool,它是用来执行工具的,为了减少代码量,这里直接造了一个假的数据返回,比如返回 “纽约明天的天气是晴天,明天的天气多云”。在真实场景中,这里面是应该调用对应的外部工具的,比如可以调个 HTTP 之类的,调用后拿到真实结果再返回。

第三个漏掉的函数叫做 call_model_after_tool_execution,它跟我们前面所见的 call_model 几乎是一模一样的,它也是先请求模型 api,再获取返回,然后打印日志,最后返回结果。区别仅仅在于这次请求的时候,历史消息里面已经包含了工具的执行结果,所以就不细说了。在每次调用模型 api 的时候,我们都会打两行日志,一个是模型 api 的请求,另外一个是模型 api 的返回。在完成了一次完整的对话之后,就应该能够找到四个日志记录,分别对应第一次模型 api 的请求、第一次模型 api 的返回、第二次模型 api 的请求和第二次模型 api 的返回。现在打开日志文件,这个就是日志文件的内容了,我们逐一来分析。首先看第一次模型 api 请求,就是第一次的模型 api 请求内容,它包含了四个部分,分别是 model、messages、tools 和 stream。

model 我们的 model 设为了 GPT-4o,代表要调用的模型。

messages 代表的是目前发送给模型的所有历史消息,第一次发送请求给模型 api 的时候,历史消息里面只有用户的问题。

后面的 tools 呢,它是一个列表,其中包含了所有的可用工具,在这个例子里面可用工具只有一个,用于搜索网络,它的名称叫做 search,用于描述工具的入参,它的参数是一个 JSON Schema,用来描述工具需要的参数。

最后的这个 “stream”: false,代表不使用流式返回,因为流式的返回结果太复杂,如果你不知道流式返回是什么意思,建议你不用管这个选项。模型 api 的请求到这里就结束了,我们再来看第一次模型 api 的返回,就是这个样子了。我们重点看看其中的 message 部分,这里面包含了模型想要调用的叫做 search 的工具,以及对应的调用参数。需要注意的是,模型是不会真的去调用这个工具的,调用函数的是我们 MarkChat 服务器。函数执行完毕之后,会把执行结果放入到历史消息列表中,并在下一次调用模型 api 的时候将历史消息发送给模型 api。我们再回到日志文件里面,看看第二次模型 api 请求,这里面包含了三部分的内容,分别是用户的问题、第一次模型 api 的返回结果和工具的执行结果。

用户的问题和第一次模型 api 的返回结果是之前模型选择的工具所挑选的工具名称跟之前第一次模型 api 返回结果中的工具名称是完全一致的。

最后的这个内容则是工具的执行结果,这也是一个非常关键的消息,因为只有把工具的执行结果返回给模型,模型才能够回答用户的问题。

最后的 tools 就是工具列表了,这里就不再赘述了,和第一次的工具列表是一样的。当然,模型也可以选择不用工具,如果模型不选择工具,就不会再选择使用工具了。现在看第二次模型 api 返回,就是对第二次请求的响应,我们就看这里面的 message 部分,所代表的就是模型根据之前工具的执行结果,对用户的问题所做出的最终回答。所以判断一个模型或者模型 api 是否具有 function calling 的能力,就是模型能够使用工具的一种能力,具体来说就是:

  1. 解析工具列表
  2. 挑选工具并给出参数
  3. 解析工具执行结果你把这个定义中的模型换成模型 API 也是可以的,只不过是站在不同的角度去定义 Function Calling。

同时使用 Function Calling 与 MCP。那如果说我们希望让这条链路也用上 MCP 的话,我们直接修改项目里面的 execute_tool 这个函数就可以了。我们这里是返回了一段假的天气信息,在真实的场景中,这里可能是调用了一个 HTTP 接口,也可能是调用了一个 SDK 的函数,Function Calling 是没有规定具体要怎么调用的,那究竟是谁规定了呢?就是 MCP。MCP 规定了一套统一的工具发现与调用协议,你只需要按照 MCP 的规范,在这个地方调用 MCP 的工具就可以了。其实这个调用 MCP 工具的函数我也写好了,只需要把原来的 execute_tool 换成 execute_tool_with_mcp 就行了。execute_tool_with_mcp 内部会启动一个 MCP Client,这个 MCP Client 会连接一个 MCP Server,这个 MCP Server 的返回数据也是假的,仅仅是为了表明这个结果是从 MCP Server 获取的。虽然说这个数据是假的,但整个流程是真的,我们成功 MCP 工具调用之后,再来看一下最终的效果。再问一下刚才的问题,纽约明天的天气怎么样?可以看到模型正常返回了,并且在返回结果中带有 MCP Server 的标识,这样我们就实现了在一个链路里面同时使用 MCP 和 Function Calling 的效果。你可能会对 MCP Client 和 MCP Server 的具体实现感兴趣,我们这里就不细说了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值