0. 引言
本文章将会以“天气怎么样”这个话题讲述 Structured Output、Function Calling 和 MCP 这三个概念,通过本地在 LM Studio 上部署的 qwen/qwen3-4b-2507 进行示例展示
1. Function Calling 工具调用
1.1. Tool 工具
1.1.1 Why 为什么
传统的大模型都表现为对话形式,一问一答,只能 chat,无法与现实世界交互
以最常见的「今天天气怎么样」为例,ai 肯定无法给出或者直接编造出一个回答
那么如何解决这种问题呢,如下:
- 用户发送消息
「我明天要在成都上下班通勤,天气怎么样?」 - 后端在向 LLM 发送请求前解析消息,发现有「明天」「天气」「成都」这些关键字
- 调用已经为这种情况预设好的方法,如
Weather w = weatherService.getWeather("成都", DateUtil.tomorrow());获取天气信息 - 再将天气信息插入到用户消息中,即
「成都今天最高 18℃,最低 12℃,小雨,降水概率 70% 。 我明天要在成都上下班通勤,天气怎么样?」 - 这时候 AI 就不会编造或者无法回答了,将根据后端插入的信息回答
上述流程的问题很明显:
- 关键词的解析非常脆弱:如果用户发送消息换成
「我明天要在成都上下班通勤,会下雨吗?」
那显然就解析失败了,漏判了这个需要获取天气信息的请求
比如「...又是灰蒙蒙的天气死我了」
解析成功了,但是没什么用,在请求中增添了无用的天气信息,污染上下文,浪费 AI 调用成本 - 需要预设场景:需要后端考虑很多场景,并建立相应的解析规则以提供外部信息源
- 不由模型控制,无法与现实世界交互:是后端进行工具的使用,整个表现仍然是一个 chatbox,大语言模型无法主动调用,也就是当模型想获取天气信息时无法主动得到
1.1.2 What 是什么
从上面的示例能看出,LLM 无法自主获取信息,只能被动的在用户的请求中获得
Function Calling 就赋予 LLM 一种与外界交互的能力,通过规定好的格式,让 LLM 告诉后端调用什么工具,并将结果返回进上下文中,以此获取外部信息或是操控外部数据
从概念上看,Function Calling 有三个关键点:
- 工具定义
tools:
告诉 LLM:工具名称、工具描述、参数 JSON Schema
- 工具调用
tool_calls:
当模型认为需要执行某个工具时,会生成一条带有tool_calls参数的Aimessage,里面会有这个工具调用的id、name、arguments,这个工具调用等价于「获取成都未来的天气」
- 工具结果消息
ToolExecutionResultMessage:
后端收到 LLM 的消息,发现有tool_calls,根据里面的工具名和参数执行方法,并将结果包装成一条"rool": "tool", "id": "对应 tool_call 的那条 id"的消息,放进消息历史,让模型能够看到
也就是 Function Calling 让 LLM 拥有了规范化的主动获取信息、主动交互的能力
1.1.3 How 使用
Function Calling 的流程很简单
示例:
请求体:
响应体:
上面的响应体中可以看到 id 为 606046057 的 tool_call 调用,后端读取后,即调用工具,获取天气信息,随后加入到消息历史中,并再次调用 LLM
这里使用的是高德天气查询 API
请求体:
需要注意的是,添加的
ToolExecutionResultMessage里有一个tool_call_id参数需要与AiMessage中tool_calls中的id对应,用来对应调用和结果
2. 结构化输出 Structured Output
2.1 Intro 介绍
通过第一节的工具调用,解决了 LLM 无法获取外部信息的问题,如
很明显,LLM 只能输出一段自然语言,文本很难利用,只能在文本框内派上用场
如果能让 LLM 输出一段规范的数据,那使用场景是不是更多了呢
拿最常见的「天气卡片」来说,如果 LLM 输出的是规范化的、结构化的消息,那卡片内容完全可以由 LLM 产出,根据对用户的记忆可以有非常个性化的内容,如:

截屏2025-11-21 23.43.24.png|500
让 LLM 产出结构化的内容,前端可以直接渲染天气卡片,上面卡片对应的 json 数据如下
看起来似乎直接在提示词中约束模型让其这么回复也行,没错,现在的 LLM 的指令遵循能力很强,上面的 json 就是我直接在系统提示词中约束的,并没有使用 Structured Output
System Prompt 系统提示词
但是最大的问题是不可靠,需要对输出校验、解析、重试,很有可能突然来个「原因如下:」什么的,成功率无法保证,而且上下文一多,就容易混乱
Structured Output 就保证了输出结构的可靠性,用 JSON Schema 将返回结构规范化
2.2 Use 使用
使用 Structured Output 输出 JSON 需要在请求时带上 JSON Schema,上面的案例如果使用 Structured Output 的话,其 JSON Schema 为
在请求体中添加 JSON Schema 约束
responseformat 其实有三种类型 一种是
text,也就是默认的形式,会返回文本 一种是 `jsonobject,保证返回合法的 JSON 一种是json_schema` 保证根据提供的 JSON Schema 返回 不同模型支持能力也不一样,比如 OpenAI 的模型基本都是支持的
即可得到对应格式的回复
3. MCP
3.0 Step 引入
在第一节 Function Calling 中,提到了传统方法的局限性:
- 需要后端提前预设好场景:提前考虑了天气场景,实现了解析、获取才能支持
- 场景解析的不可靠:漏判或者判错场景,缺失外部信息或者增添无用信息
而现在,LLM 已经拥有了两种能力:
- Function Calling:可以主动调用提供好的工具,与外部世界交互
- Structured Output:能够以给定的 JSON Schema 格式输出,这点体现在了 Function Calling 上
实际上是 OpenAI 推出了
Structured Outputs这个概念,统一了response_format: { type: "json_schema", json_schema: {...} }以及tools: [{"type": "function", "function": {}, "strict": true}],让模型能够完全受 JSON Schema 的约束输出
看起来似乎已经很强大了,我们提供 get_weather、search_news、write_memory、read_memory 工具,就可以让 LLM 记录对我们的记忆,产出个性化的、结构化的内容
但是传统方法局限性中还是有一点没有解决,那就是「需要后端提前预设好场景」,需要程序想到这个场景,并专门编写相应代码去实现一些工具
- 当用户想让 LLM 与第三方服务交互时,比如搜索 GitHub 上的某个 repo、读取 Notion 上的一篇文章、在小红书上发布一篇笔记,就需要提前写好这些工具封装,并提供给 LLM
- 想要在另一个 AI 应用中使用这些相同的工具,又要复制一遍代码,或许还要做些调整
- 如果想分享这些工具,但是又不想暴露实现细节,就有些难办
MCP 就是解决这些问题的,将提供工具和使用工具分离开,统一了工具的定义和描述,供模型与外部世界交互
3.1 What & Why
MCP 是一个开放协议,基于 JSON-RPC 2.0,暴露三类能力:Tools、Resources、Prompts,最为常见的无疑就是 Tools
为什么需要 MCP?
Function Calling 解决了模型如何告诉程序“我要调用工具”,而 MCP 解决了共享、发现、管理、复用工具
有三个概念:
MCP Server:提供方,实现工具,并根据 MCP 协议定义向外暴露工具定义
MCP Client:使用方,发现工具,并根据 MCP 协议定义调用工具
Host:程序,把 MCP Client 获取到的的工具列表转换成 tools,接收 tool_calls 并通过 MCP Client 调用 MCP Server 得到 ToolExecutionResultMessage
好处:
- 方便接入,不用在程序内部添加 Tools 的逻辑,直接通过添加 MCP Server 即可,比如在 Cursor 或者 Claude Code 中添加 MCP
- 隐藏了实现细节,对外提供只是一个黑盒,接收请求,返回结果,内部细节不暴露
- 动态管理能力,MCP Client 可以根据 tools/list 动态发现工具,也可以手动禁用某些工具
3.2 MCP 运行流程
流程图如下
案例:
高德 MCP: