Function Call、Tool、MCP:大模型工具调用三件事
-
- 一、大模型缺什么
- [二、Function Call:让大模型学会"下命令"](#二、Function Call:让大模型学会“下命令”)
- 三、Tool:自带描述和干活逻辑的方法
- [四、完整流程:本地 Tool](#四、完整流程:本地 Tool)
- 五、为什么执行完还要回传大模型
- [六、Tool 多了的麻烦](#六、Tool 多了的麻烦)
- [七、MCP:让第三方自己提供 Tool](#七、MCP:让第三方自己提供 Tool)
-
- [7.1 核心思路](#7.1 核心思路)
- [7.2 两方各自做什么](#7.2 两方各自做什么)
- [7.3 SDK 是什么,在哪用](#7.3 SDK 是什么,在哪用)
- [7.4 我们怎么写代码:两种方式](#7.4 我们怎么写代码:两种方式)
- [八、MCP 配置实战](#八、MCP 配置实战)
-
- [8.1 配置文件](#8.1 配置文件)
- [8.2 框架启动时自动做了什么](#8.2 框架启动时自动做了什么)
- [8.3 大模型该怎么调还怎么调](#8.3 大模型该怎么调还怎么调)
- [8.4 调用流程](#8.4 调用流程)
- [九、有无 MCP 对比](#九、有无 MCP 对比)
- 十、演变总结
- 十一、速记卡
一、大模型缺什么
| 大模型能做的 | 大模型不能做的 |
|---|---|
| 回答学过的东西 | 查实时天气 |
| 理解你给的文章 | 调你的 API |
| 翻译、润色 | 操作你的文件 |
它缺"手"和"眼",需要工具帮忙。
二、Function Call:让大模型学会"下命令"
大模型不直接说人话,而是输出一段 JSON,告诉程序:"我要调用哪个函数,参数是什么"。
| 普通对话 | Function Call |
|---|---|
| "您可以打开天气 App 看北京天气" | {"function":"get_weather","city":"北京"} |
格式谁定的: 大模型厂商内置好的,你不用教它。
填什么内容: 大模型根据你给的"工具菜单"和用户的话,自己匹配工具、提取参数。
三、Tool:自带描述和干活逻辑的方法
Tool 就是一个方法,包含两部分:
| Tool 里有什么 | 作用 | 给谁看 |
|---|---|---|
| 描述部分(名称、功能描述、参数定义) | 告诉大模型"我能干嘛、需要什么参数" | 大模型 |
| 执行逻辑(方法体里的代码) | 真正干活的代码,调 API、跑脚本 | 程序自己 |
用 Java 写一个本地 Tool:
java
@Tool(name = "get_weather", description = "查询指定城市的实时天气")
public String getWeather(@ToolParam(description = "城市名称") String city) {
return 天气API.query(city); // 执行逻辑
}
| 组成部分 | 代码里写的 | 作用 |
|---|---|---|
| 名称 | get_weather |
大模型在 JSON 里填这个名字 |
| 描述 | "查询指定城市的实时天气" | 大模型据此判断能不能用 |
| 参数 | city,城市名称 |
大模型知道要传什么 |
| 执行逻辑 | 调天气 API | 大模型不关心,程序自己跑 |
四、完整流程:本地 Tool
天气API 大模型 应用程序 用户 天气API 大模型 应用程序 用户 #mermaid-svg-QQ6u3894hLqJHHLX{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-QQ6u3894hLqJHHLX .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-QQ6u3894hLqJHHLX .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-QQ6u3894hLqJHHLX .error-icon{fill:#552222;}#mermaid-svg-QQ6u3894hLqJHHLX .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-QQ6u3894hLqJHHLX .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-QQ6u3894hLqJHHLX .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-QQ6u3894hLqJHHLX .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-QQ6u3894hLqJHHLX .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-QQ6u3894hLqJHHLX .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-QQ6u3894hLqJHHLX .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-QQ6u3894hLqJHHLX .marker{fill:#333333;stroke:#333333;}#mermaid-svg-QQ6u3894hLqJHHLX .marker.cross{stroke:#333333;}#mermaid-svg-QQ6u3894hLqJHHLX svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-QQ6u3894hLqJHHLX p{margin:0;}#mermaid-svg-QQ6u3894hLqJHHLX .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-QQ6u3894hLqJHHLX text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-QQ6u3894hLqJHHLX .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-QQ6u3894hLqJHHLX .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-QQ6u3894hLqJHHLX .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-QQ6u3894hLqJHHLX .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-QQ6u3894hLqJHHLX #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-QQ6u3894hLqJHHLX .sequenceNumber{fill:white;}#mermaid-svg-QQ6u3894hLqJHHLX #sequencenumber{fill:#333;}#mermaid-svg-QQ6u3894hLqJHHLX #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-QQ6u3894hLqJHHLX .messageText{fill:#333;stroke:none;}#mermaid-svg-QQ6u3894hLqJHHLX .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-QQ6u3894hLqJHHLX .labelText,#mermaid-svg-QQ6u3894hLqJHHLX .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-QQ6u3894hLqJHHLX .loopText,#mermaid-svg-QQ6u3894hLqJHHLX .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-QQ6u3894hLqJHHLX .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-QQ6u3894hLqJHHLX .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-QQ6u3894hLqJHHLX .noteText,#mermaid-svg-QQ6u3894hLqJHHLX .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-QQ6u3894hLqJHHLX .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-QQ6u3894hLqJHHLX .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-QQ6u3894hLqJHHLX .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-QQ6u3894hLqJHHLX .actorPopupMenu{position:absolute;}#mermaid-svg-QQ6u3894hLqJHHLX .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-QQ6u3894hLqJHHLX .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-QQ6u3894hLqJHHLX .actor-man circle,#mermaid-svg-QQ6u3894hLqJHHLX line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-QQ6u3894hLqJHHLX :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 1. 发送工具列表(Tool 描述) 2. "查北京天气" 3. 转发用户的话 4. 匹配 get_weather 5. {"function":"get_weather","city":"北京"} 6. 解析,执行 get_weather 7. 调 API 8. {"temp":25,"weather":"晴"} 9. 回传原始数据 10. 润色 11. "北京今天晴天,25度" 12. 展示
一句话:大模型点菜(JSON),程序炒菜(执行方法),大模型上菜(润色)。
五、为什么执行完还要回传大模型
程序拿到的是原始数据,不会"说人话"。
| 原始数据 | 用户期待的回复 |
|---|---|
{"temp":25,"weather":"晴"} |
"北京今天晴天,25度,体感舒适。" |
| 角色 | 负责 |
|---|---|
| Tool 执行逻辑 | 动手拿数据 |
| 大模型 | 动脑判断 + 动嘴润色 |
六、Tool 多了的麻烦
早期每接一个第三方服务,都要手写一个 Tool。
| 服务 | 开发者要做什么 |
|---|---|
| 天气 | 手写 Tool 封装 API |
| GitHub | 手写 Tool |
| 地图 | 手写 Tool |
本质问题:我们在替服务方写 Tool。
七、MCP:让第三方自己提供 Tool
7.1 核心思路
第三方服务按统一标准封装接口,我们直接连,不用替它写 Tool。
类比:
- MCP 之前:买电器送裸线,自己接插头
- MCP 之后:电器出厂自带标准插头,直接插
7.2 两方各自做什么
| 角色 | 做什么 | 用什么 |
|---|---|---|
| 第三方服务方 | 把自己的 API 封装成 MCP 服务 | MCP 官方提供的 SDK |
| 我们 | 配置连接地址,直接调用 | 配置文件里写 URL |
7.3 SDK 是什么,在哪用
SDK(软件开发工具包)是 MCP 官方提供的一套现成工具包,给第三方服务方用的。第三方用这个 SDK,可以把自己的服务(比如 GitHub API)快速变成一个标准的 MCP 服务端,暴露一个 URL 出来。
SDK 是第三方用的,不是我们用的。 我们的 Java 程序不需要引入 MCP SDK,只需要配一个 URL。
7.4 我们怎么写代码:两种方式
| 方式 | 做法 | 适用场景 |
|---|---|---|
| 本地手动封装 | 自己写 @Tool 方法,方法体里调第三方 API |
第三方没提供 MCP 接口 |
| MCP 配置连接 | 在配置文件里写 URL,框架自动拉取 | 第三方提供了 MCP 服务端 |
如果第三方提供了 MCP 服务,我们选第二种,零代码。
八、MCP 配置实战
8.1 配置文件
假设有三个第三方服务都提供了 MCP 接口:
yaml
spring:
ai:
mcp:
client:
connections:
github:
type: STREAMABLE_HTTP
url: https://github-mcp.example.com/mcp
weather:
type: STREAMABLE_HTTP
url: https://weather-mcp.example.com/mcp
email:
type: STREAMABLE_HTTP
url: https://email-mcp.example.com/mcp
有多少个 MCP 工具,就配多少个 connections 节点。 不需要为每个工具写 Java 类。
8.2 框架启动时自动做了什么
1. 读取配置文件里的 MCP 连接列表
2. 逐个连接 MCP 服务端
3. 从每个服务端拉取它提供的工具描述
4. 把这些工具描述合并到本地 Tool 列表中
5. 把完整的工具列表发给大模型
你不需要手动写任何代码去拉取、合并、发送。框架全自动完成。
8.3 大模型该怎么调还怎么调
大模型眼里,所有工具都一样,不区分是本地的还是 MCP 远程的。它照样输出 Function Call:
json
{"function": "github_search", "arguments": {"query": "AI agent"}}
框架收到后自动判断:这个工具来自 MCP,走 MCP 协议远程调用,拿到结果回传大模型润色。
8.4 调用流程
MCP服务端(第三方) 大模型 应用程序 用户 MCP服务端(第三方) 大模型 应用程序 用户 #mermaid-svg-mIXMcR3l00mGIRYY{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-mIXMcR3l00mGIRYY .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-mIXMcR3l00mGIRYY .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-mIXMcR3l00mGIRYY .error-icon{fill:#552222;}#mermaid-svg-mIXMcR3l00mGIRYY .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-mIXMcR3l00mGIRYY .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-mIXMcR3l00mGIRYY .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-mIXMcR3l00mGIRYY .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-mIXMcR3l00mGIRYY .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-mIXMcR3l00mGIRYY .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-mIXMcR3l00mGIRYY .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-mIXMcR3l00mGIRYY .marker{fill:#333333;stroke:#333333;}#mermaid-svg-mIXMcR3l00mGIRYY .marker.cross{stroke:#333333;}#mermaid-svg-mIXMcR3l00mGIRYY svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-mIXMcR3l00mGIRYY p{margin:0;}#mermaid-svg-mIXMcR3l00mGIRYY .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-mIXMcR3l00mGIRYY text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-mIXMcR3l00mGIRYY .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-mIXMcR3l00mGIRYY .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-mIXMcR3l00mGIRYY .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-mIXMcR3l00mGIRYY .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-mIXMcR3l00mGIRYY #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-mIXMcR3l00mGIRYY .sequenceNumber{fill:white;}#mermaid-svg-mIXMcR3l00mGIRYY #sequencenumber{fill:#333;}#mermaid-svg-mIXMcR3l00mGIRYY #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-mIXMcR3l00mGIRYY .messageText{fill:#333;stroke:none;}#mermaid-svg-mIXMcR3l00mGIRYY .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-mIXMcR3l00mGIRYY .labelText,#mermaid-svg-mIXMcR3l00mGIRYY .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-mIXMcR3l00mGIRYY .loopText,#mermaid-svg-mIXMcR3l00mGIRYY .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-mIXMcR3l00mGIRYY .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-mIXMcR3l00mGIRYY .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-mIXMcR3l00mGIRYY .noteText,#mermaid-svg-mIXMcR3l00mGIRYY .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-mIXMcR3l00mGIRYY .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-mIXMcR3l00mGIRYY .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-mIXMcR3l00mGIRYY .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-mIXMcR3l00mGIRYY .actorPopupMenu{position:absolute;}#mermaid-svg-mIXMcR3l00mGIRYY .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-mIXMcR3l00mGIRYY .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-mIXMcR3l00mGIRYY .actor-man circle,#mermaid-svg-mIXMcR3l00mGIRYY line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-mIXMcR3l00mGIRYY :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 1. 启动时自动拉取 MCP 工具,合并到工具列表,发给大模型 2. "搜索 GitHub AI 项目" 3. 转发 4. {"function":"github_search","query":"AI agent"} 5. 框架自动走 MCP 协议请求远端 6. 返回结果 7. 回传润色 8. 最终回复 9. 展示
九、有无 MCP 对比
| 对比项 | 没有 MCP | 有 MCP |
|---|---|---|
| 接一个工具要写多少代码 | 一个完整的 @Tool 类 |
一行 YAML 配置 |
| 接十个工具 | 十个 @Tool 类 |
十段 YAML 配置 |
| 工具更新维护 | 改代码,重新部署 | 第三方自己更新,我们不用动 |
| 工具列表发给大模型 | 手动收集 | 框架自动合并本地 Tool + MCP 工具 |
| 调用逻辑 | 框架自动 | 框架自动(跟本地 Tool 一样) |
十、演变总结
| 阶段 | 核心 |
|---|---|
| 起点 | 大模型没手没脚 |
| Function Call | 大模型能下命令(输出 JSON) |
| Tool | 方法含描述和执行逻辑,描述给大模型看,逻辑自己跑 |
| MCP | 第三方自己封装,我们配置 URL 直接用 |
十一、速记卡
Function Call = 大模型下命令的语法
Tool = 描述(给大模型看)+ 执行逻辑(自己跑)
MCP = 第三方用官方 SDK 封装服务,我们配 URL 直接连
本地 Tool 和 MCP 工具的关系:
大模型眼里都一样
框架自动合并,一起发给大模型
调用流程不变
SDK 给谁用:
第三方服务方用,用来封装 MCP 服务端
我们不用,我们只配 URL