Structured Output、Function Calling 和 MCP
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 就不会编造或者无法回答了,将根据后端插入的信息回答
Mermaid Loading...
上述流程的问题很明显:
- 关键词的解析非常脆弱:如果用户发送消息换成
「我明天要在成都上下班通勤,会下雨吗?」
那显然就解析失败了,漏判了这个需要获取天气信息的请求
比如「...又是灰蒙蒙的天气死我了」
解析成功了,但是没什么用,在请求中增添了无用的天气信息,污染上下文,浪费 AI 调用成本 - 需要预设场景:需要后端考虑很多场景,并建立相应的解析规则以提供外部信息源
- 不由模型控制,无法与现实世界交互:是后端进行工具的使用,整个表现仍然是一个 chatbox,大语言模型无法主动调用,也就是当模型想获取天气信息时无法主动得到
1.1.2 What 是什么
从上面的示例能看出,LLM 无法自主获取信息,只能被动的在用户的请求中获得
Function Calling 就赋予 LLM 一种与外界交互的能力,通过规定好的格式,让 LLM 告诉后端调用什么工具,并将结果返回进上下文中,以此获取外部信息或是操控外部数据
从概念上看,Function Calling 有三个关键点:
- 工具定义
tools:
告诉 LLM:工具名称、工具描述、参数 JSON Schema
"tools": [
{
"type": "function",
"function": {
"name": "get_weather",
"description": "获取指定城市的天气信息。当用户询问天气、降水、温度等相关问题时使用此工具。",
"parameters": {
"type": "object",
"properties": {
"location": {
"type": "string",
"description": "城市名称,例如:成都、北京"
},
"extensions": {
"type": "string",
"enum": [
"base",
"all"
],
"description": "气象类型:\n - base: 返回实况天气,适用于查询\"现在\"、\"当前\"、\"实时\"的天气\n - all: 返回预报天气(未来3-4天),适用于查询\"明天\"、\"后天\"、\"未来几天\"的天气"
}
},
"required": [
"location",
"extensions"
]
}
}
}
]- 工具调用
tool_calls: 当模型认为需要执行某个工具时,会生成一条带有tool_calls参数的Aimessage,里面会有这个工具调用的idnamearguments,这个工具调用等价于「获取成都未来的天气」
"tool_calls": [
{
"type": "function",
"id": "606046057",
"function": {
"name": "get_weather",
"arguments": "{\"location\":\"成都\",\"extensions\":\"all\"}"
}
}
]- 工具结果消息
ToolExecutionResultMessage: 后端收到 LLM 的消息,发现有tool_calls,根据里面的工具名和参数执行方法,并将结果包装成一条"rool": "tool", "id": "对应 tool_call 的那条 id"的消息,放进消息历史,让模型能够看到
也就是 Function Calling 让 LLM 拥有了规范化的主动获取信息、主动交互的能力
1.1.3 How 使用
Function Calling 的流程很简单
Mermaid Loading...
示例:
请求体:
{
"model": "qwen3-4b-2507",
"tools": [
{
"type": "function",
"function": {
"name": "get_weather",
"description": "获取指定城市的天气信息。当用户询问天气、降水、温度等相关问题时使用此工具。",
"parameters": {
"type": "object",
"properties": {
"location": {
"type": "string",
"description": "城市名称,例如:成都、北京"
},
"extensions": {
"type": "string",
"enum": [
"base",
"all"
],
"description": "气象类型:\n - base: 返回实况天气,适用于查询\"现在\"、\"当前\"、\"实时\"的天气\n - all: 返回预报天气(未来3-4天),适用于查询\"明天\"、\"后天\"、\"未来几天\"的天气"
}
},
"required": [
"location",
"extensions"
]
}
}
}
],
"messages": [
{
"role": "user",
"content": "我明天要在成都上下班通勤,会下雨吗?"
}
]
}响应体:
{
"id": "chatcmpl-g23i230saulvl49aru2okl",
"object": "chat.completion",
"created": 1763727823,
"model": "qwen/qwen3-4b-2507",
"choices": [
{
"index": 0,
"message": {
"role": "assistant",
"content": "",
"tool_calls": [
{
"type": "function",
"id": "606046057",
"function": {
"name": "get_weather",
"arguments": "{\"location\":\"成都\",\"extensions\":\"all\"}"
}
}
]
},
"logprobs": null,
"finish_reason": "tool_calls"
}
],
"usage": {
"prompt_tokens": 275,
"completion_tokens": 26,
"total_tokens": 301
},
"stats": {},
"system_fingerprint": "qwen/qwen3-4b-2507"
}上面的响应体中可以看到 id 为 606046057 的 tool_call 调用,后端读取后,即调用工具,获取天气信息,随后加入到消息历史中,并再次调用 LLM
请求体:
{
"model": "qwen/qwen3-4b-2507",
"messages": [
{
"role": "user",
"content": "我明天要在成都上下班通勤,天气怎么样?"
},
{
"content": "",
"role": "assistant",
"tool_calls": [
{
"id": "398625313",
"function": {
"arguments": "{\"location\":\"成都\",\"extensions\":\"all\"}",
"name": "get_weather"
},
"type": "function"
}
]
},
{
"role": "tool",
"tool_call_id": "398625313",
"name": "get_weather",
"content": "{\"status\": \"1\", \"count\": \"1\", \"info\": \"OK\", \"infoc... <Truncated in logs> ... \"nighttemp_float\": \"8.0\"}]}], \"_city_name\": \"成都\"}"
}
]
}需要注意的是,添加的
ToolExecutionResultMessage里有一个tool_call_id参数需要与AiMessage中tool_calls中的id对应