Function Calling两轮对话真相:MCP/Skills/A2A四层进化全景
摘要
当大模型收到 "查询北京天气 "指令时,它真的会联网搜索吗? 并没有。它从未真正访问任何
当大模型收到"查询北京天气"指令时,它真的会联网搜索吗?
并没有。它从未真正访问任何天气预报数据源。
大模型的核心动作很简单——生成一张JSON格式的"工单",内容为{"city": "北京"},然后等待下游执行。外部结果返回后,它才据此组织回复,营造出"已查询天气"的表象。
这正是Function Calling(FC)的本质:LLM仅负责决定"调用哪个函数、传递什么参数",自身绝不执行实际操作。
亲手实现一次FC代码后,许多底层细节会瞬间清晰。沿着这条线索向前推演,从FC到MCP、Skills再到A2A,四个概念串起一条清晰的主线,揭示了AI工具系统如何从"单机模式"演化为"联邦协作"。
一、Function Calling的真相:两轮交互,LLM只下单不操作
用类比场景可以轻松理解FC的运行机制:
假设你在公司担任项目经理,既不会编码也不会硬件操作,但擅长拆解需求、判断该找谁执行。
- 你接到的任务:"查询北京天气"
- 你不会亲自去查——你输出一张工单:"调用get_weather函数,参数city='北京'"
- 你的下属(代码层)拿着工单实际执行,返回结果"25°C 晴"
- 下属将结果提交给你,你据此生成最终回复:"北京今天25度,晴天"
整个流程本质上是两轮对话:

一个值得注意的能力——LLM可以同时下发多张工单。例如请求"查上海天气并计算25加17",模型会并行返回两个tool_call:一个调用get_weather,一个调用calculate。你的for循环依次执行,结果统一回传,LLM一次性整合输出。这种机制称为并行工具调用(Parallel Tool Calling)。
二、手写代码验证:从工具定义到完成闭环
整个实现仅需60行代码即可构造完整的两轮对话。首先定义工具结构,这份定义就是提供给LLM的接口文档:
tools = [
{
"type": "function",
"function": {
"name": "get_weather",
"description": "查询指定城市的天气信息",
"parameters": {
"type": "object",
"properties": {
"city": {
"type": "string",
"description": "城市名称,如'北京'、'上海'"
}
},
"required": ["city"]
}
}
},
{
"type": "function",
"function": {
"name": "calculate",
"description": "执行数学计算,支持加减乘除",
"parameters": {
"type": "object",
"properties": {
"expression": {
"type": "string",
"description": "数学表达式,如'2+3'、'10*5'"
}
},
"required": ["expression"]
}
}
}
]
自然不能缺少真正的函数实现:
def get_weather(city: str) -> str:
"""模拟天气查询"""
weather_data = {
"北京": "25°C,晴天,空气质量良",
"上海": "28°C,多云,东南风3级",
"深圳": "32°C,雷阵雨,湿度85%",
}
return weather_data.get(city, f"{city}:暂无天气数据")def calculate(expression: str) -> str:
"""安全计算数学表达式"""
try:
allowed = set("0123456789+-*/.() ")
if not all(c in allowed for c in expression):
return "错误:表达式包含非法字符"
result = eval(expression)
return str(result)
except Exception as e:
return f"计算错误:{e}"
接下来是核心逻辑——两轮对话的完整闭环:
def chat_with_tools(user_message: str):
"""完整的FC两轮对话"""
# ===== 第1轮:用户提问 → LLM返回调用意图 =====
response = client.chat.completions.create(
model=MODEL,
messages=[{"role": "user", "content": user_message}],
tools=tools
)
message = response.choices[0].message
# LLM不需要工具,直接回答
if not message.tool_calls:
print(f"LLM直接回答: {message.content}")
return message.content
# LLM决定调用工具——把意图消息加入对话历史
messages = [
{"role": "user", "content": user_message},
message # tool_calls消息原样保留
]
# 逐个执行LLM要求的工具调用
for tc in message.tool_calls:
func_name = tc.function.name
func_args = json.loads(tc.function.arguments)
if func_name == "get_weather":
result = get_weather(**func_args)
elif func_name == "calculate":
result = calculate(**func_args)
else:
result = f"未知函数: {func_name}"
# 结果喂回——必须是tool message格式
messages.append({
"role": "tool",
"tool_call_id": tc.id, # 绑定到对应的调用ID
"content": result
})
# ===== 第2轮:工具结果喂回去 → LLM生成最终回答 =====
response2 = client.chat.completions.create(
model=MODEL,
messages=messages,
tools=tools
)
return response2.choices[0].message.content
实测结果清晰直观,四种场景覆盖了所有典型用例:
场景1:天气查询(两轮对话)
用户: 北京今天天气怎么样?
LLM决定调用: get_weather({'city': '北京'})
执行结果: 25°C,晴天,空气质量良
最终回答: 北京今天25°C,晴天,空气质量良,适合外出活动
场景2:数学计算(两轮对话)
用户: 帮我算一下123乘以456
LLM决定调用: calculate({'expression': '123*456'})
执行结果: 56088
最终回答: 123乘以456的结果是56088
场景3:无需工具(直接回答)
用户: 给我讲个冷笑话
LLM直接回答: 为什么企鹅的肚子是白色的?因为企鹅的手太短了,洗澡只能搓到肚子前面
场景4:并行工具调用(同时下发两张工单)
用户: 帮我查上海天气,再算25加17
LLM决定调用: get_weather({'city': '上海'})
执行结果: 28°C,多云,东南风3级
LLM决定调用: calculate({'expression': '25+17'})
执行结果: 42
最终回答: 上海28°C多云东南风3级,25+17=42
四个场景全部验证通过,FC的工作机制至此彻底清晰。
三、FC不够用?从单兵作战到团队协作的四层演进
FC存在一个致命缺陷:工具定义与你的应用代码硬绑定,切换项目必须重写一遍。换一个LLM应用,依然要重复同样的工作。
这就像你在自家有一套完整工具箱,到公司却要再买一套——每一个应用都是信息孤岛,开发效率被严重拖累。
沿此问题继续推演,整个AI工具体系经历了四层演进:
第一层:Function Calling——单兵作战
FC本质是LLM与工具之间的一次性合约。你写好tools定义,LLM按照定义发起调用请求。
明显局限:工具定义与应用深度耦合,换应用就得重写。如同你独自拥有一套工具箱,去哪都必须随身携带。
第二层:MCP——供应商入驻
MCP(Model Context Protocol)由Anthropic在2024年底推出,作为开放性标准。核心突破是工具与应用彻底解耦。
MCP的架构分为三层:
- Host:你的LLM应用(例如Claude Desktop、OpenClaw)
- Client:协议转换层,将LLM的工具调用翻译为标准请求
- Server:工具的提供方,自行描述"我有哪些工具、参数是什么"
关键演进点——Server自描述能力:
FC(你替工具写简历):
- 每个应用自行定义tools
- 切换应用 → 重写
- 工具本身没有话语权
MCP(工具自带简历):
- Server主动声明自身能力
- 所有Host自动发现并对接
- 编写一次,随处可用
类比一下:FC相当于你去超市自行找货;MCP则像电商平台,供应商自主上架产品目录,所有买家自动可见。
MCP还提供三种能力:Tools(函数调用)、Resources(上下文数据)、Prompts(提示词模板)。本文聚焦Tools,其余两个了解即可。
传输层有两种模式:stdio(本地高效,进程间通信)和SSE(HTTP远程方案,Ja va天然擅长)。
第三层:Skills——开源社区插件
Skill并非单个函数,而是能力包。它包含指令(SKILL.md)、实现代码、领域知识,甚至支持Agent自我迭代优化。
与MCP Tool的本质差异:
| 维度 | MCP Tool | Agent Skill | 直觉类比 |
|---|---|---|---|
| 定义 | 单个函数 | 能力包(指令+代码+知识) | 电钻 vs 木工师傅 |
| 粒度 | 单次调用 | 多步骤流程 | 钻一下 vs 整个项目 |
| 智能度 | 无状态,调用即结束 | 有状态,能自主判断 | 机器 vs 专家 |
| 迭代 | 人工更新 | 自进化(反思后改进) | 手册 vs 实战经验 |
身边就有真实案例——OpenClaw的Skill系统。你日常使用的feishu-calendar、feishu-task并非简单函数调用,它们内置SKILL.md(使用指南)、专用工具和错误处理策略。一个Skill能让任何Agent操作飞书日历,无需理解飞书API的细节。
第四层:A2A——联邦协作
A2A(Agent to Agent)是Google在2025年推出的协议。至此,Agent之间的关系不再是"人调用工具",而是对等协作。
与前三层本质性区别:
| 维度 | FC / MCP / Skills | A2A |
|---|---|---|
| 关系 | 主人 → 工具 | 同事 → 同事 |
| 决策 | 单个(Host)决策 | 多个Agent协商决策 |
| 通信 | 单向(调用→返回) | 双向(委托+汇报+协商) |
| 主体性 | 工具无自主性 | 每个Agent有目标与边界 |
类比:FC/MCP = 你单干偶尔点外卖;Skills = 你学了新技能不断变强;A2A = 你组建团队,每个人都有专长,互相委托任务。
Google A2A协议的核心三要素:
- Agent Card(自描述卡片:"我是代码审查Agent,擅长Ja va审查")
- Task委托("请审查这段代码")
- 结果推送("审查完成,发现3个问题")
Ja va后端的直觉映射
作为Ja va开发者,这四层可以找到熟悉的对应关系:
| 概念 | Ja va对应 | 理解桥梁 | 一句话 |
|---|---|---|---|
| FC | 反射 method.invoke() | 指定调用哪个方法、传递什么参数 | 你编写调用规则 |
| MCP | SPI | 服务主动注册、自我描述 | 供应商自行上架 |
| Skills | Spring Boot Starter | 引入依赖即获得能力 | 能力包,引入即用 |
| A2A | 微服务 RPC/gRPC | 服务间相互调用 | 团队间互相委托 |
四、总结:一条主线看清AI工具系统的演进方向
从FC到A2A,演进方向清晰明确:从被动工具走向自主协作。
- FC:LLM下单,你执行——最原始但最实用
- MCP:工具自带文档,所有应用自动对接——解决复用难题
- Skills:能力包超越函数,支持Agent自进化——解决智能问题
- A2A:Agent之间对等协作——解决复杂任务问题
对于Ja va后端工程师,好消息是——MCP的SSE传输基于HTTP,Ja va天然适配;Spring AI已内置FC和MCP Client支持;Skills的概念与Spring Boot Starter完全一致,理解成本几乎为零。
简而言之,这四层演进正是Ja va生态走过的老路:从反射调用 → SPI插件机制 → Starter能力包 → 微服务RPC。换了层外衣,内核完全一致。
演进路线清晰可循,Ja va程序员完全有理由自信——当前AI最前沿的"智能体协作"机制,本质上是在重走Ja va生态早已验证过的路径。理解它,并不比掌握一个RPC框架更难。
来源:互联网
本网站新闻资讯均来自公开渠道,力求准确但不保证绝对无误,内容观点仅代表作者本人,与本站无关。若涉及侵权,请联系我们处理。本站保留对声明的修改权,最终解释权归本站所有。