Python 大模型 Function Calling 实战:让 AI 真正学会"动手干活"
大模型很聪明,但它有个致命缺陷:只能"说",不能"做" 。Function Calling 彻底打破了这道墙。本文将用 Python 从零实现 Function Calling,让大模型不仅能思考,还能调用真实工具完成任务。
- [一、什么是 Function Calling?为什么它改变了 AI 的游戏规则?](#一、什么是 Function Calling?为什么它改变了 AI 的游戏规则?)
- 二、核心原理:大模型如何"学会"调用工具?
- 三、环境准备
- [四、实战 1:天气查询工具 ------ 从最简单的例子开始](#四、实战 1:天气查询工具 —— 从最简单的例子开始)
- [五、实战 2:多工具协作 ------ 打造 AI 助手](#五、实战 2:多工具协作 —— 打造 AI 助手)
- [六、实战 3:工具编排 ------ 让 AI 自主规划执行链](#六、实战 3:工具编排 —— 让 AI 自主规划执行链)
- 七、进阶技巧
- 八、性能与成本分析
- 九、踩坑指南
- 总结
一、什么是 Function Calling?为什么它改变了 AI 的游戏规则?
传统大模型的工作方式是这样的:
用户提问 → 模型思考 → 返回文本回答
模型只能"纸上谈兵"------它不能查天气、不能发邮件、不能操作数据库、不能调用任何外部系统。
Function Calling 让模型从"军师"变成了"将军":
用户提问 → 模型思考 → 决定调用什么工具 → 执行工具 → 获取结果 → 返回最终回答
| 对比维度 | 传统大模型 | 支持 Function Calling |
|---|---|---|
| 能力边界 | 只能用训练数据回答 | 可以调用任意外部工具 |
| 实时性 | 知识截止日期前 | 可以获取实时数据 |
| 准确性 | 数学计算容易出错 | 可以调用计算器保证精确 |
| 交互性 | 纯文本对话 | 操作数据库、发邮件、控制设备 |
| 可扩展性 | 固定能力 | 无限扩展(写什么工具就能用什么) |
一句话总结:Function Calling = 让大模型从"只会聊天"进化为"能干实事"。
二、核心原理:大模型如何"学会"调用工具?
Function Calling 的核心流程并不复杂:
┌─────────┐ ┌─────────┐ ┌──────────┐ ┌─────────┐
│ 用户 │ │ 大模型 │ │ 工具函数 │ │ 大模型 │
│ 提问 │────▶│ 判断 │────▶│ 执行 │────▶│ 总结 │──▶ 最终回答
│ │ │ 需要工具 │ │ 并返回 │ │ 回答 │
└─────────┘ └─────────┘ └──────────┘ └─────────┘
关键步骤:
- 定义工具:告诉模型有哪些工具可用,每个工具接收什么参数
- 模型决策:模型根据用户输入,判断是否需要调用工具,需要调用哪个
- 执行工具:你的代码执行对应函数,拿到结果
- 模型总结:把工具结果返回给模型,生成最终回答
注意:模型本身不执行任何代码,它只是输出结构化的 JSON 表示"我想调用这个函数",真正的执行由你的 Python 代码完成。
三、环境准备
bash
pip install openai python-dotenv
创建 .env 文件:
env
OPENAI_API_KEY=sk-your-key-here
# 也可以用兼容 OpenAI 接口的国内模型
# OPENAI_BASE_URL=https://api.deepseek.com
四、实战 1:天气查询工具 ------ 从最简单的例子开始
4.1 定义工具函数
python
# weather_tool.py
import json
from datetime import datetime
# 模拟天气数据(实际项目中替换为真实 API)
WEATHER_DATA = {
"北京": {"temp": 22, "weather": "晴", "humidity": 45, "wind": "北风3级"},
"上海": {"temp": 26, "weather": "多云", "humidity": 72, "wind": "东南风2级"},
"深圳": {"temp": 30, "weather": "阵雨", "humidity": 85, "wind": "南风3级"},
"成都": {"temp": 20, "weather": "阴", "humidity": 68, "wind": "微风"},
"杭州": {"temp": 24, "weather": "晴转多云", "humidity": 60, "wind": "东风2级"},
}
def get_weather(city: str) -> str:
"""查询指定城市的天气信息
Args:
city: 城市名称,如"北京"、"上海"
Returns:
天气信息的 JSON 字符串
"""
if city in WEATHER_DATA:
data = WEATHER_DATA[city]
return json.dumps({
"city": city,
"temp": data["temp"],
"weather": data["weather"],
"humidity": data["humidity"],
"wind": data["wind"],
"update_time": datetime.now().strftime("%Y-%m-%d %H:%M")
}, ensure_ascii=False)
return json.dumps({"error": f"未找到城市:{city}"}, ensure_ascii=False)
4.2 注册工具到模型
python
# tools定义:告诉模型有哪些工具可用
tools = [
{
"type": "function",
"function": {
"name": "get_weather",
"description": "查询指定城市的实时天气信息,包括温度、天气状况、湿度、风力等",
"parameters": {
"type": "object",
"properties": {
"city": {
"type": "string",
"description": "要查询的城市名称,如'北京'、'上海'、'深圳'"
}
},
"required": ["city"]
}
}
}
]
4.3 核心循环:调用模型 → 解析工具 → 执行 → 再调用
python
# main.py
import json
from openai import OpenAI
from weather_tool import get_weather
client = OpenAI() # 自动读取环境变量
# 工具映射表:函数名 → 实际函数
TOOL_MAP = {
"get_weather": get_weather,
}
def chat_with_tools(user_message: str) -> str:
"""带工具调用能力的对话"""
messages = [
{"role": "system", "content": "你是一个智能助手,可以帮用户查询天气信息。回答时请用简洁自然的语言。"},
{"role": "user", "content": user_message}
]
# 第一次调用:让模型判断是否需要工具
response = client.chat.completions.create(
model="gpt-4o-mini",
messages=messages,
tools=tools,
tool_choice="auto" # 自动决定是否调用工具
)
message = response.choices[0].message
# 如果模型决定不调用工具,直接返回回答
if not message.tool_calls:
return message.content
# 模型决定调用工具,逐个执行
messages.append(message) # 把模型的工具调用记录加入对话
for tool_call in message.tool_calls:
func_name = tool_call.function.name
func_args = json.loads(tool_call.function.arguments)
print(f" [工具调用] {func_name}({func_args})")
# 执行对应工具
if func_name in TOOL_MAP:
result = TOOL_MAP[func_name](**func_args)
else:
result = json.dumps({"error": f"未知工具:{func_name}"})
# 把工具结果加入对话
messages.append({
"role": "tool",
"tool_call_id": tool_call.id,
"content": result
})
# 第二次调用:让模型根据工具结果生成最终回答
final_response = client.chat.completions.create(
model="gpt-4o-mini",
messages=messages
)
return final_response.choices[0].message.content
# 测试
if __name__ == "__main__":
questions = [
"北京今天天气怎么样?",
"上海和深圳哪个城市更热?",
"推荐一个适合出游的城市"
]
for q in questions:
print(f"\n提问:{q}")
answer = chat_with_tools(q)
print(f"回答:{answer}")
print("-" * 50)
运行效果:
提问:北京今天天气怎么样?
[工具调用] get_weather({'city': '北京'})
回答:北京今天天气晴朗,气温22°C,湿度45%,北风3级。整体来说天气不错,
适合外出活动,建议适当补水,风力稍大注意防护。
--------------------------------------------------
提问:上海和深圳哪个城市更热?
[工具调用] get_weather({'city': '上海'})
[工具调用] get_weather({'city': '深圳'})
回答:深圳更热!深圳目前气温30°C,而上海是26°C,深圳比上海高了4度。
不过上海湿度更大(72% vs 85%),体感上深圳更闷热一些。
--------------------------------------------------
注意第二问:模型自主判断需要查两个城市,并行调用了两次工具!
五、实战 2:多工具协作 ------ 打造 AI 助手
真实场景中,我们需要多个工具协同工作。下面打造一个功能更丰富的 AI 助手。
5.1 定义多个工具
python
# tools.py
import json
import math
from datetime import datetime
# ========== 工具 1:数学计算器 ==========
def calculator(expression: str) -> str:
"""安全地计算数学表达式
Args:
expression: 数学表达式,如 "2 + 3 * 4" 或 "sqrt(16)"
"""
# 安全白名单:只允许数学函数和数字运算
ALLOWED_NAMES = {
"abs": abs, "round": round, "min": min, "max": max,
"sqrt": math.sqrt, "pow": pow, "pi": math.pi, "e": math.e,
"sin": math.sin, "cos": math.cos, "tan": math.tan,
"log": math.log, "log10": math.log10, "ceil": math.ceil,
"floor": math.floor,
}
try:
result = eval(expression, {"__builtins__": {}}, ALLOWED_NAMES)
return json.dumps({"expression": expression, "result": result})
except Exception as e:
return json.dumps({"error": f"计算错误:{str(e)}"})
# ========== 工具 2:时间查询 ==========
def get_current_time(timezone_offset: int = 8) -> str:
"""获取当前时间
Args:
timezone_offset: 时区偏移量,默认8(北京时间 UTC+8)
"""
from datetime import timezone, timedelta
tz = timezone(timedelta(hours=timezone_offset))
now = datetime.now(tz)
return json.dumps({
"current_time": now.strftime("%Y-%m-%d %H:%M:%S"),
"weekday": ["周一", "周二", "周三", "周四", "周五", "周六", "周日"][now.weekday()],
"timezone": f"UTC+{timezone_offset}"
}, ensure_ascii=False)
# ========== 工具 3:单位换算 ==========
def unit_convert(value: float, from_unit: str, to_unit: str) -> str:
"""单位换算
Args:
value: 原始值
from_unit: 原始单位(km, mile, kg, lb, celsius, fahrenheit 等)
to_unit: 目标单位
"""
conversions = {
("km", "mile"): lambda v: v * 0.621371,
("mile", "km"): lambda v: v * 1.60934,
("kg", "lb"): lambda v: v * 2.20462,
("lb", "kg"): lambda v: v * 0.453592,
("celsius", "fahrenheit"): lambda v: v * 9/5 + 32,
("fahrenheit", "celsius"): lambda v: (v - 32) * 5/9,
("meter", "feet"): lambda v: v * 3.28084,
("feet", "meter"): lambda v: v * 0.3048,
}
key = (from_unit.lower(), to_unit.lower())
if key in conversions:
result = conversions[key](value)
return json.dumps({
"original": f"{value} {from_unit}",
"converted": f"{round(result, 4)} {to_unit}"
}, ensure_ascii=False)
return json.dumps({"error": f"不支持的转换:{from_unit} → {to_unit}"})
5.2 注册所有工具
python
# 工具定义
ALL_TOOLS = [
{
"type": "function",
"function": {
"name": "calculator",
"description": "计算数学表达式,支持加减乘除、三角函数、对数、幂运算等。当需要精确数值计算时使用。",
"parameters": {
"type": "object",
"properties": {
"expression": {
"type": "string",
"description": "要计算的数学表达式,如 '2**10'、'sqrt(144)'、'sin(pi/4)'"
}
},
"required": ["expression"]
}
}
},
{
"type": "function",
"function": {
"name": "get_current_time",
"description": "获取当前的日期和时间",
"parameters": {
"type": "object",
"properties": {
"timezone_offset": {
"type": "integer",
"description": "时区偏移量,默认为8(北京时间)"
}
},
"required": []
}
}
},
{
"type": "function",
"function": {
"name": "unit_convert",
"description": "在不同度量单位之间进行换算,如长度、重量、温度等",
"parameters": {
"type": "object",
"properties": {
"value": {
"type": "number",
"description": "要换算的数值"
},
"from_unit": {
"type": "string",
"description": "原始单位,如 km、mile、kg、lb、celsius、fahrenheit"
},
"to_unit": {
"type": "string",
"description": "目标单位"
}
},
"required": ["value", "from_unit", "to_unit"]
}
}
}
]
# 工具映射
TOOL_MAP = {
"calculator": calculator,
"get_current_time": get_current_time,
"unit_convert": unit_convert,
}
5.3 通用工具调用引擎
python
# agent.py
import json
from openai import OpenAI
client = OpenAI()
def agent_chat(user_message: str, system_prompt: str = None, max_rounds: int = 5) -> str:
"""
通用 Function Calling 引擎
Args:
user_message: 用户输入
system_prompt: 系统提示词
max_rounds: 最大工具调用轮数(防止死循环)
Returns:
模型的最终回答
"""
messages = []
if system_prompt:
messages.append({"role": "system", "content": system_prompt})
messages.append({"role": "user", "content": user_message})
for round_num in range(max_rounds):
response = client.chat.completions.create(
model="gpt-4o-mini",
messages=messages,
tools=ALL_TOOLS,
tool_choice="auto"
)
message = response.choices[0].message
# 没有工具调用,返回最终回答
if not message.tool_calls:
return message.content
# 有工具调用,执行所有工具
messages.append(message)
for tool_call in message.tool_calls:
func_name = tool_call.function.name
func_args = json.loads(tool_call.function.arguments)
print(f" 🔧 [调用] {func_name}({json.dumps(func_args, ensure_ascii=False)})")
if func_name in TOOL_MAP:
result = TOOL_MAP[func_name](**func_args)
else:
result = json.dumps({"error": f"未知工具:{func_name}"})
print(f" 📋 [结果] {result}")
messages.append({
"role": "tool",
"tool_call_id": tool_call.id,
"content": result
})
return "抱歉,工具调用轮数已达上限。"
# 测试
if __name__ == "__main__":
system_prompt = """你是一个全能AI助手,具备计算、时间查询和单位换算能力。
回答问题时:
1. 需要精确计算时,使用计算器工具
2. 需要知道当前时间时,使用时间查询工具
3. 需要单位换算时,使用换算工具
4. 用简洁自然的中文回答"""
test_cases = [
"帮我算一下 2 的 20 次方是多少",
"100华氏度等于多少摄氏度?",
"现在几点了?距离2026年国庆节还有多少天?",
"一个马拉松42.195公里等于多少英里?跑完大概消耗多少卡路里(按每公里60大卡算)?",
]
for question in test_cases:
print(f"\n{'='*60}")
print(f"🙋 提问:{question}")
print(f"{'='*60}")
answer = agent_chat(question, system_prompt)
print(f"\n💬 回答:{answer}")
运行效果:
============================================================
🙋 提问:100华氏度等于多少摄氏度?
============================================================
🔧 [调用] unit_convert({"value": 100, "from_unit": "fahrenheit", "to_unit": "celsius"})
📋 [结果] {"original": "100 fahrenheit", "converted": "37.7778 celsius"}
💬 回答:100华氏度约等于 **37.78摄氏度**,接近人体正常体温的温度。
============================================================
六、实战 3:工具编排 ------ 让 AI 自主规划执行链
复杂场景下,模型需要链式调用多个工具。看这个例子:
python
# chain_agent.py
import json
from openai import OpenAI
client = OpenAI()
def chain_agent(user_message: str) -> str:
"""
支持多轮工具调用的智能 Agent
模型可以在一轮中调用多个工具,
也可以在拿到结果后决定是否继续调用更多工具。
"""
messages = [
{
"role": "system",
"content": """你是一个智能规划助手。面对复杂问题时:
1. 先分析需要哪些信息
2. 逐步调用工具获取信息
3. 基于工具结果进行推理和回答
你必须主动调用工具获取数据,不要凭空猜测。"""
},
{"role": "user", "content": user_message}
]
for round_num in range(10): # 最多 10 轮工具调用
response = client.chat.completions.create(
model="gpt-4o-mini",
messages=messages,
tools=ALL_TOOLS,
tool_choice="auto"
)
message = response.choices[0].message
if not message.tool_calls:
return message.content
messages.append(message)
print(f"\n--- 第 {round_num + 1} 轮工具调用 ---")
for tool_call in message.tool_calls:
func_name = tool_call.function.name
func_args = json.loads(tool_call.function.arguments)
result = TOOL_MAP[func_name](**func_args)
print(f" {func_name}({json.dumps(func_args, ensure_ascii=False)}) => {result}")
messages.append({
"role": "tool",
"tool_call_id": tool_call.id,
"content": result
})
return "工具调用轮数已达上限"
# 测试复杂链式调用
if __name__ == "__main__":
result = chain_agent(
"我想做一个蛋糕。帮我算一下:如果配方需要把350华氏度的烤箱预热,"
"那是多少摄氏度?另外帮我算一下,2.5磅面粉等于多少公斤?"
)
print(f"\n最终回答:\n{result}")
模型会自主规划:先换算温度,再换算重量,最后综合回答。
七、进阶技巧
7.1 强制调用 / 禁止调用工具
python
# 强制模型必须调用某个工具
response = client.chat.completions.create(
model="gpt-4o-mini",
messages=messages,
tools=tools,
tool_choice={"type": "function", "function": {"name": "get_weather"}}
)
# 禁止调用任何工具(纯对话模式)
response = client.chat.completions.create(
model="gpt-4o-mini",
messages=messages,
tools=tools,
tool_choice="none"
)
7.2 并行工具调用
GPT-4o / GPT-4o-mini 支持在一轮中并行调用多个独立工具:
python
# 当用户问"北京和上海天气对比"时,
# 模型会在一个 tool_calls 中返回两个调用:
# tool_calls[0]: get_weather({"city": "北京"})
# tool_calls[1]: get_weather({"city": "上海"})
7.3 错误处理与重试
python
def safe_tool_call(func, **kwargs, max_retries=2):
"""带重试的安全工具调用"""
for attempt in range(max_retries + 1):
try:
result = func(**kwargs)
return result
except Exception as e:
if attempt == max_retries:
return json.dumps({"error": f"工具执行失败(已重试{max_retries}次):{str(e)}"})
print(f" ⚠️ 工具执行失败,正在重试({attempt + 1}/{max_retries})...")
八、性能与成本分析
| 项目 | 数据 |
|---|---|
| 单次工具调用延迟 | 约增加 0.5-1s |
| Token 消耗 | 工具定义约 100-300 tokens/个 |
| 建议单次对话工具数 | 3-10 个(太多模型会混乱) |
| 推荐模型 | GPT-4o-mini(性价比最高)、GPT-4o(复杂场景)、DeepSeek-V3 |
成本估算(GPT-4o-mini):
- 输入:$0.15 / 1M tokens
- 输出:$0.6 / 1M tokens
- 一次包含 3 个工具的完整对话:约 0.1-0.3 美分
九、踩坑指南
- 工具描述是灵魂 :
description写得越清晰,模型选错工具的概率越低。不要写"查询信息",要写"查询指定城市的实时天气,包括温度、湿度、风力" - 参数类型要准确 :
integer就不要写string,模型会根据类型生成不同格式的参数 - 防止注入:工具函数的参数来自模型输出,务必做安全校验
- 上下文窗口 :每轮工具调用都会增加消息长度,注意
max_tokens限制 - 幂等性:工具函数尽量设计为幂等的(同一参数多次调用结果一致),因为模型可能重复调用
总结
Function Calling 的工作模式:
定义工具 → 注册给模型 → 模型自主决策 → 代码执行工具 → 结果返回模型 → 生成最终回答
| 能力 | 传统大模型 | + Function Calling |
|---|---|---|
| 数学计算 | 容易出错 | 精确无误 |
| 实时信息 | 无法获取 | 随时查询 |
| 外部操作 | 无能为力 | 无限扩展 |
| 多步推理 | 靠谱度一般 | 工具链保障 |
Function Calling 是 AI Agent 的基石。掌握了它,你就掌握了让大模型真正"干活"的能力。下一步可以结合 LangChain、CrewAI 等框架,构建更复杂的多 Agent 协作系统。
如果觉得有用,点赞收藏不迷路!下期我们将深入探讨 RAG 检索增强生成,让 AI 拥有你的私有知识库。
