前言
2023 年之前,大模型是"关在笼子里的天才大脑"------它能写诗、能编程、能回答问题,但它的世界止步于训练数据的截止日期。你不知道今天的天气,查不了实时的股票,动不了数据库里的一行记录。
Function Calling 的出现,打破了这堵墙。
它给大模型装上了"手":模型不再只是"想",还能"做"。当用户说"明天北京适合爬山吗",模型可以调用天气 API 查预报、调用地图 API 查路况、调用笔记工具查你的日程,然后把所有信息整合成一句有温度的建议。
这听起来很美,但真正落地时,你会遇到一连串实际问题:该如何定义工具描述?模型什么时候该调用、什么时候不该调用?多个函数链式调用怎么管理?数据量太大怎么办?模型生成 SQL 不准确怎么优化?
大模型系列目录(持续更新):
一、为什么需要 Function Calling?
大模型擅长理解和生成自然语言,但它无法直接操作外部世界。比如:
- 查询实时天气、股票价格、最新新闻
- 访问和操作数据库
- 执行复杂计算
- 发送邮件或控制智能设备
Function Calling(函数调用) 正是大模型与真实世界交互的"桥梁"------它将大模型从"语言理解"扩展到"具体行动"。
Function Calling 的核心能力
-
扩展模型能力:大模型通过调用预设函数,完成自身无法直接完成的任务
-
结构化输出:模型将用户的自然语言请求转化为结构化的函数参数。例如:
- 用户说"明天北京天气如何?" → 模型调用
get_weather(location="北京", date="2025-05-06")
- 用户说"明天北京天气如何?" → 模型调用
-
动态决策:模型根据上下文自主决定是否调用函数、调用哪个函数,甚至链式调用多个函数(如先查天气再推荐穿搭)
二、Function Calling vs MCP:同与不同
MCP(Model Context Protocol,模型上下文协议)是近年来兴起的一种开放协议,与 Function Calling 作用类似,但定位不同。
| 维度 | Function Calling | MCP |
|---|---|---|
| 定位 | 模型厂商私有接口(OpenAI、Qwen 等各自实现) | 开放协议(类似 HTTP / USB-C 标准) |
| 扩展性 | 需为每个模型单独适配 | 一次开发,多模型兼容 |
| 复杂性 | 适合简单、单次调用任务 | 支持多轮对话与复杂上下文管理 |
| 生态依赖 | 依赖特定模型(如 GPT-4、Qwen) | 跨模型跨平台(Claude、Cursor 等均可使用) |
| 安全性 | 依赖云端 API 密钥 | 支持本地化数据控制 |
有了 MCP,还需要 Function Calling 吗?
答案是:两者会共存。
MCP 可能成为主流协议,但 Function Calling 作为大模型的底层标配能力,仍然不可或缺。对于简单、原子化的任务,Function Calling 更加便捷:
- 查询天气:
get_weather(city="北京") - 数学计算:
calculate(expression="3+5") - 发送通知:
send_email(to="``user@example.com``")
Function Calling 的优势在于开发快捷(无需配置 MCP Server)、低延迟(单次请求-响应,无协议层开销)。当你只需要让大模型调用一个自己写的函数时,Function Calling 是更直接的选择。
三、Qwen3:本次实战的模型基座
阿里于 2025 年 4 月 29 日发布了 Qwen3 系列模型,包含 8 种不同规模,涵盖密集(Dense)和混合专家(MoE)两种架构,全部基于 Apache 2.0 开源协议,支持免费商用。
MoE 模型(高效推理)
| 模型 | 总参数 | 激活参数 | 备注 |
|---|---|---|---|
| Qwen3-235B-A22B | 2350 亿 | 220 亿 | 旗舰级,性能接近 Gemini 2.5 Pro |
| Qwen3-30B-A3B | 300 亿 | 30 亿 | 仅 10% 激活参数即超越前代 QwQ-32B |
Dense 模型(全参数激活)
包括 Qwen3-32B、14B、8B、4B 、1.7B、0.6B。其中 Qwen3-4B 尤其值得关注------它以 4B 的参数量达到了前代 Qwen2.5-72B 的性能水平。这意味着:
- 推理成本大幅降低(一张 RTX 4090 即可运行)
- 端侧部署成为可能(手机、IoT 设备)
Qwen3 引入了混合推理模式,模型会自动根据问题决定是否先进行"思考"(thinking)再给出答案,这是小模型性能大幅提升的关键。
四、实战案例一:天气查询助手
整体架构
用户提问 → Qwen3 解析意图 → 调用 get_current_weather 工具
→ 高德天气 API → 返回数据 → Qwen3 组织回复 → 展示给用户
Step 1:定义 Function Tool
我们需要定义一个 JSON 格式的工具描述,告诉大模型"你有这么一个函数可以用":
makefile
weather_tool = {
"type": "function",
"function": {
"name": "get_current_weather",
"description": "获取指定地点的当前天气",
"parameters": {
"type": "object",
"properties": {
"location": {
"type": "string",
"description": "城市名称,如北京"
},
"adcode": {
"type": "string",
"description": "城市编码,如 110000(北京)"
}
},
"required": ["location"]
}
}
}
注意 :
name用英文(因为它是程序中的函数名),description用中文完全没问题------大模型可以理解中文描述。
Step 2:实现工具函数
python
def get_weather_from_gaode(location: str, adcode: str = None):
"""调用高德地图 API 查询天气"""
gaode_api_key = "你的高德API Key"
base_url = "https://restapi.amap.com/v3/weather/weatherInfo"
params = {
"key": gaode_api_key,
"city": adcode if adcode else location,
"extensions": "base",
}
response = requests.get(base_url, params=params)
if response.status_code == 200:
return response.json()
else:
return {"error": f"请求失败: {response.status_code}"}
Step 3:主流程------两次调用大模型
这里有一个关键设计:为什么需要调用大模型两次?
ini
def run_weather_query():
messages = [
{"role": "system", "content": "你是一个智能助手,可以查询天气信息。"},
{"role": "user", "content": "北京现在天气怎么样?"}
]
# 第一次调用:让大模型决定是否调用工具
response = dashscope.Generation.call(
model="qwen-plus-2025-04-28",
messages=messages,
tools=[weather_tool],
tool_choice="auto",
)
if "tool_calls" in response.output.choices[0].message:
tool_call = response.output.choices[0].message.tool_calls[0]
if tool_call["function"]["name"] == "get_current_weather":
import json
args = json.loads(tool_call["function"]["arguments"])
location = args.get("location", "北京")
adcode = args.get("adcode", None)
# 执行工具函数
weather_data = get_weather_from_gaode(location, adcode)
# 第二次调用:将工具结果传给大模型,让它组织自然语言回复
messages.append({
"role": "tool",
"content": json.dumps(weather_data, ensure_ascii=False)
})
response2 = dashscope.Generation.call(
model="qwen-plus-2025-04-28",
messages=messages,
tools=[weather_tool],
)
print(response2.output.choices[0].message.content)
else:
print(response.output.choices[0].message.content)
两次调用的作用:
- 第一次 :大模型解析用户意图 → 决定调用工具 → 返回
tool_calls(包含参数) - 执行工具:我们拿到参数后调用高德 API,获取天气数据
- 第二次:将工具返回的原始数据(JSON)传给大模型 → 大模型将其组织成友好的自然语言回复
"北京现在天气晴朗,温度 17°C,西南风,风力小于 3 级,湿度 44%。"
高德地图 API Key 申请
- 登录高德开放平台
- 进入「应用管理」→「创建应用」
- 选择「出行」类型,添加 Key
- 选择「Web 服务」类型,提交后保存 Key
⚠️ 注意:高德 API 有调用频率限制,频繁调用可能被封禁账号。
五、实战案例二:门票业务助手(Text-to-SQL)
这个案例展示了 Function Calling 在企业数据分析中的典型应用:通过自然语言查询数据库。
需求场景
对门票业务数据进行查询,例如:
- "2023 年 4、5、6 月一日门票和二日门票的销量是多少?按周统计"
- "2023 年 7 月不同省份的入园人数统计"
- "2023 年 10 月 1 日至 7 日销售渠道订单金额排名"
搭建流程
Step 1:系统 Prompt --- 给大模型" metadata"
要让大模型写出正确的 SQL,必须先告诉它数据库结构:
scss
system_prompt = """我是门票助手,以下是门票订单表相关的字段,我可能会编写对应的SQL进行查询
-- 门票订单表
CREATE TABLE tkt_orders (
order_time DATETIME, -- 订单日期
account_id INT, -- 预定用户ID
gov_id VARCHAR(18), -- 商品使用人身份证号
gender VARCHAR(10), -- 使用人性别
age INT, -- 年龄
province VARCHAR(30), -- 使用人省份
SKU VARCHAR(100), -- 商品SKU名
product_serial_no VARCHAR(30), -- 商品ID
eco_main_order_id VARCHAR(20), -- 订单ID
sales_channel VARCHAR(20), -- 销售渠道
status VARCHAR(30), -- 商品状态
order_value DECIMAL(10,2), -- 订单金额
quantity INT, -- 商品数量
...
);
"""
除了表结构,业务术语也很关键。比如数据库里"两人门票"可能存的是"1.5 倍",如果你不告诉大模型,它可能写出错误的 SQL。
Step 2:注册工具
使用 Qwen-Agent 框架注册一个执行 SQL 的工具:
python
from qwen_agent.tools.base import BaseTool, register_tool
@register_tool('exc_sql')
class ExcSQLTool(BaseTool):
description = '对生成的SQL进行查询'
parameters = [{
'name': 'sql_input',
'type': 'string',
'description': '生成的SQL语句',
'required': True
}]
def call(self, params: str, **kwargs) -> str:
import json
args = json.loads(params)
sql_input = args['sql_input']
database = args.get('database', 'ubr')
# 创建数据库连接
engine = create_engine(f"mysql+pymysql://user:pass@host/{database}")
try:
df = pd.read_sql(sql_input, engine)
return df.head(10).to_markdown(index=False) # 截断行,防止数据过多
except Exception as e:
return f"SQL执行出错: {str(e)}"
Step 3:初始化 Assistant 并启动 Web UI
ini
from qwen_agent.agents import Assistant
def init_agent_service():
llm_cfg = {
'model': 'qwen-turbo-2025-04-28',
'timeout': 30,
'retry_count': 3,
}
bot = Assistant(
llm=llm_cfg,
name='门票助手',
description='门票查询与订单分析',
system_message=system_prompt,
function_list=['exc_sql'], # 传入已注册的工具名
)
return bot
def app_gui():
bot = init_agent_service()
chatbot_config = {
'prompt.suggestions': [
'2023年4、5、6月一日门票,二日门票的销量多少?按周统计',
'2023年7月不同省份的入园人数统计',
'2023年10月1-7日销售渠道订单金额排名',
]
}
WebUI(bot, chatbot_config=chatbot_config).run()
Qwen-Agent 的 Assistant 会自动处理多轮对话和工具调用的控制逻辑,无需我们自己写复杂的流程管理代码。
六、进阶:数据可视化
在门票助手的基础上,我们还想实现:查询数据后自动生成图表。
方案对比
方案一:单独写一个 plot_data 函数
- 优点:功能解耦,可复用
- 缺点:Markdown 传参可能过大;X/Y 轴参数不易传递准确;需先将 Markdown 转回 DataFrame
方案二:在 exc_sql 中集成绘图功能
- 优点:数据无需二次传递;自动推断图表字段;一次调用完成查询+可视化
- 最终选择方案二
自动推断逻辑
X 轴:优先选择第一个字符串类型(日期或分类名称)的列
Y 轴:选择所有数值类型的列,支持多系列数据
ini
# 自动推断 x/y 字段
x_candidates = df.select_dtypes(include=['object']).columns.tolist()
x = x_candidates[0] if x_candidates else df.columns.tolist()[0]
y_fields = df.select_dtypes(include=['number']).columns.tolist()
# 绘制柱状图
plt.figure(figsize=(8, 5))
bar_width = 0.35 if len(y_fields) > 1 else 0.6
x_labels = df[x].astype(str)
x_pos = range(len(df))
for idx, y_col in enumerate(y_fields):
plt.bar([p + idx * bar_width for p in x_pos], df[y_col],
width=bar_width, label=y_col)
版本迭代
整个开发过程经历了三个版本:
| 版本 | 功能 | 说明 |
|---|---|---|
| assistant_ticket_bot-1 | 基本查询 | Function Calling + exc_sql |
| assistant_ticket_bot-2 | 查询+绘图 | 增加柱状图绘制 |
| assistant_ticket_bot-3 | 多类别可视化 | 支持分组与透视(pivot_table) |
第三个版本引入了 pivot_table 支持多类别变量的透视图可视化------比如同时按"日期"和"销售渠道"两个维度展示数据。
七、避坑指南与最佳实践
上下文干扰
当连续提问时,大模型可能会受到前文影响。例如,问完"4、5、6 月数据"后再问"第 13 周数据",模型可能会错误地将第 13 周限制在 4-6 月范围内。解决方案是明确指定时间范围:
arduino
✅ "我看到第13周(2023年3月27日-4月2日)数据有异常..."
数据量过大
- 查询结果可能多达几十万行,返回给大模型会超出上下文限制
- 建议对返回结果做截断(如
head(10)),或只返回聚合统计信息
多表关联场景
- 如果涉及上百张表,不可能把所有 metadata 都塞给大模型
- 采用两阶段策略:先让大模型根据表名初筛 → 再将被选中的表的 DDL 传给模型生成 SQL
准确性优化
- 提供业务术语说明(如 SKU 含义、特定的编码规则)
- 给一些示例 SQL 让大模型模仿
- 先做测试,定位错误集中在哪里(是大模型 Text-to-SQL 能力不足,还是上下文信息缺失)
八、总结
Function Calling 是大模型的底层标配能力,它让大模型从"只能聊天"进化为"能执行任务"的智能体。无论 MCP 如何发展,Function Calling 作为最直接的工具调用方式,仍是每位 AI 应用开发者必须掌握的技能。