🌟 为什么需要 Function Calling?
大语言模型(如 GPT、DeepSeek)虽然能回答很多问题,但它不知道今天北京是否下雨,因为它:
- 训练数据截止于过去
- 无法主动访问互联网
为了解决这个问题,OpenAI 提出了 Function Calling(函数调用) 机制:
让 LLM 在需要时,主动“调用”你提供的工具(比如查天气、查汇率),再根据结果生成回答。
这就像给 AI 配了一个“助手”,它说:“帮我查下上海天气”,你就去查,然后告诉它结果,它再转述给人类。
本文将带你从零实现这个过程,并逐行解释所有关键代码,尤其深入讲解 工具(Tool)定义中的每个参数。
第一步:安装依赖
pip install requests openai
requests:用于向网络 API 发送请求(比如获取天气)openai:用于调用兼容 OpenAI 协议的大模型(如 DeepSeek、Moonshot、官方 GPT)
❌ 不要用
npm install,那是 JavaScript 的命令!
第二步:编写天气查询函数(工具本身)
这是整个系统的核心工具。我们一步步来写。
2.1 导入 requests 库
import requests
这行代码告诉 Python:“我要用 requests 这个工具去访问网络”。
2.2 定义函数并添加类型提示
def get_weather(location: str) -> str:
def表示定义一个函数。- 函数名叫
get_weather(获取天气)。 - 它接收一个参数
location,类型是字符串(: str)。 - 它最终返回一个字符串(
-> str),比如"北京当前天气:晴"。
✅ 类型提示不是强制的,但能让代码更清晰,编辑器也能帮你检查错误。
2.3 设置 API 地址
url = "https://api.seniverse.com/v3/weather/now.json"
这是 心知天气 提供的实时天气接口地址。
所有天气请求都要发到这个 URL。
2.4 构造请求参数:重点讲解 params
params = {
"key": "SspUltXUBqSJDjhYz",
"location": location,
"language": "zh-Hans"
}
🔍 params 到底是干什么的?
当你在浏览器访问:
https://example.com/api?name=张三&age=25
问号 ? 后面的部分叫 查询参数(Query Parameters) 。
手动拼接这些参数很麻烦,还容易出错(比如中文要编码)。
而 requests 库提供了一个聪明的办法:用字典传参,自动拼接!
当你写:
requests.get(url, params=params)
它会自动把 params 转成:
?key=Ssp...&location=北京&language=zh-Hans
并附加到 url 后面,完全不用你操心!
参数说明:
| 键 | 值 | 说明 |
|---|---|---|
key | "SspUltXUBqSJDjhYz" | 你的 API 密钥(必须注册心知天气获取) |
location | 用户传入的城市名 | 比如 "上海"、"广州" |
language | "zh-Hans" | 返回简体中文结果 |
⚠️ 重要:示例中的
key是无效的,请登录 心知天气控制台 获取你自己的免费密钥!
2.5 发送请求并设置超时
resp = requests.get(url, params=params, timeout=10)
requests.get():发起 GET 请求(最常用的 HTTP 方法)。timeout=10:如果 10 秒内没收到回复,就放弃请求,防止程序卡死。
2.6 解析 JSON 响应
data = resp.json()
API 返回的是 JSON 格式的数据,看起来像这样(简化版):
{
"results": [
{
"location": { "name": "深圳" },
"now": {
"text": "多云",
"temperature": "28"
}
}
]
}
.json() 方法会自动把这个 JSON 字符串转换成 Python 的字典(dict),方便我们取值。
2.7 提取关键信息
if "results" in data:
r = data["results"][0]
city = r["location"]["name"]
now = r["now"]
text = now["text"]
temp = now["temperature"]
return f"{city}当前的天气:{text},气温:{temp}℃"
else:
return "查询失败"
- 先检查是否有
"results"字段(避免 API 报错时程序崩溃)。 data["results"]是一个列表,通常只包含一个城市的结果,所以取[0]。- 通过层层“下钻”拿到城市名、天气描述、温度。
- 用 f-string(格式化字符串)组合成一句自然语言。
✅ 示例输出:杭州当前的天气:小雨,气温:19℃
2.8 添加异常处理(让程序更健壮)
把上面所有逻辑包在 try...except 中:
try:
# ... 发送请求、解析数据 ...
except Exception as e:
return f"异常:{e}"
这样即使网络断了、API 挂了、密钥错了,程序也不会崩溃,而是返回友好的错误提示。
2.9 完整函数代码(带注释)
import requests
def get_weather(location: str) -> str:
"""
获取指定城市的实时天气
:param location: 城市名称,如 "北京"
:return: 格式化的天气信息字符串
"""
url = "https://api.seniverse.com/v3/weather/now.json"
params = {
"key": "你的API密钥", # ← 务必替换成你自己的!
"location": location,
"language": "zh-Hans"
}
try:
resp = requests.get(url, params=params, timeout=10)
data = resp.json()
if "results" in data:
r = data["results"][0]
city = r["location"]["name"]
text = r["now"]["text"]
temp = r["now"]["temperature"]
return f"{city}当前的天气:{text},气温:{temp}℃"
else:
return "查询失败,请检查城市名称或 API 密钥"
except Exception as e:
return f"请求异常:{e}"
2.10 测试函数
print(get_weather("成都"))
# 输出示例:成都当前的天气:阴,气温:21℃
如果看到正常结果,恭喜!你已经掌握了调用第三方 API 的核心技能。
💡 这个函数就是我们即将“教给 LLM”的工具。但光有函数还不够,我们还得用一种 LLM 能理解的方式“描述”它。
第三步:定义工具(Tool)——重点来了!
LLM 是“读不懂 Python 代码”的。
为了让它知道“你能调用什么工具”,我们必须用 JSON 格式 描述工具的结构。
这就是 tools 列表的作用:
tools = [
{
"type": "function",
"function": {
"name": "get_weather",
"description": "获取指定城市的实时天气",
"parameters": {
"type": "object",
"properties": {
"location": {
"type": "string",
"description": "要查询的城市名称,例如‘北京’或‘Shanghai’"
}
},
"required": ["location"]
}
}
}
]
下面,我们逐字段详细解释每个参数的意义,这是新手最容易卡住的地方!
🔍 3.1 "type": "function"
- 表示这是一个“函数类型”的工具。
- 目前 OpenAI 只支持
"function",未来可能支持其他类型(如"retrieval")。 - 必须写,不能省略。
🔍 3.2 "name": "get_weather"
- 这是函数的名字,必须和你实际写的 Python 函数名完全一致!
- LLM 会根据这个名字决定调用哪个函数。
- ✅ 正确:
"name": "get_weather"对应def get_weather(...) - ❌ 错误:写成
"getName"或"weather",程序就找不到函数,会报错。
🔍 3.3 "description": "获取指定城市的实时天气"
- 这是给 LLM 看的“说明书” 。
- 它告诉模型:“这个工具能干什么”。
- 写得越清楚,LLM 越容易在合适的时候调用它。
✅ 好的例子:
“获取用户指定城市的当前天气状况和温度”
❌ 差的例子:
“天气函数” —— 太模糊,LLM 可能不知道何时使用。
💡 小技巧:想象你在教一个实习生,你会怎么描述这个工具的功能?
🔍 3.4 "parameters":定义函数需要哪些输入
这是最复杂的部分,采用 JSON Schema 标准(一种描述数据结构的规范)。
▶ "type": "object"
- 表示这个函数接收的是一个“对象”(在 Python 中就是字典
dict)。 - 几乎所有工具都用
"object",因为我们要传多个参数(哪怕只有一个)。
▶ "properties":列出所有可能的参数
这是一个字典,每个键是一个参数名,值是该参数的描述。
以我们的例子为例:
"properties": {
"location": {
"type": "string",
"description": "要查询的城市名称,例如‘北京’"
}
}
"location":参数名,必须和 Python 函数的参数名一致。"type": "string":说明这个参数必须是字符串类型(不能是数字或列表)。"description":再次说明这个参数的含义,帮助 LLM 正确提取用户问题中的信息。
💡 举例:当用户问“上海天气怎么样?”,LLM 会根据 description 知道要把“上海”作为
location的值。
▶ "required": ["location"]
- 这是一个列表,列出哪些参数是必须提供的。
- 如果 LLM 没有提供这些参数,调用就会失败。
- 我们的函数只有一个参数
location,所以写["location"]。
✅ 如果有多个必填参数:
"required": ["city", "unit"]
✅ 如果某些参数可选(比如单位默认是摄氏度),就不放进 required。
🧩 总结:Tool 定义的核心逻辑
| 字段 | 作用 | 类比 |
|---|---|---|
name | 函数名字 | 工具的“身份证号” |
description | 功能说明 | 工具的“使用说明书” |
parameters.properties | 参数列表及说明 | 工具的“输入接口说明” |
parameters.required | 必填参数 | “不填就不能用”的字段 |
⚠️ 写错任何一个,LLM 要么不会调用,要么调用失败!
第四步:让 LLM 使用这个工具
现在,我们把工具“教给”大模型,并让它处理用户问题。
4.1 初始化客户端
from openai import OpenAI
client = OpenAI(
api_key="你的大模型API密钥",
base_url="https://api.deepseek.com/v1" # 使用 DeepSeek
)
4.2 用户提问
messages = [{"role": "user", "content": "成都现在多少度?"}]
4.3 第一次请求:让 LLM 决定是否调用工具
response = client.chat.completions.create(
model="deepseek-reasoner",
messages=messages,
tools=tools, # ← 把我们定义的工具传进去!
tool_choice="auto", # 让 LLM 自动决定是否调用
temperature=0.3
)
tools=tools:把前面定义的工具列表传给 LLM。tool_choice="auto":让模型自己判断要不要调用工具(也可以强制指定)。
4.4 执行工具调用
# 1. 获取 LLM 的回复(可能包含 tool_calls)
response_message = response.choices[0].message
messages.append(response_message) # 加入对话历史
# 2. 如果有工具调用请求
if response_message.tool_calls:
for tool_call in response_message.tool_calls:
# 3. 提取函数名和参数
func_name = tool_call.function.name
func_args = json.loads(tool_call.function.arguments) # JSON 字符串 → dict
# 4. 执行对应函数
if func_name == "get_weather":
result = get_weather(func_args["location"])
else:
result = "未知工具"
# 5. 将结果反馈给 LLM(关键!)
messages.append({
"tool_call_id": tool_call.id, # 必须匹配
"role": "tool", # 角色必须是 "tool"
"name": func_name,
"content": result
})
✅ 关键:
tool_call_id必须原样返回,用于关联调用与结果。
4.5 第二次请求:生成最终回答
final_response = client.chat.completions.create(
model="deepseek-reasoner",
messages=messages,
temperature=0.3
)
print(final_response.choices[0].message.content)
✅ 输出示例:
成都当前气温为21℃,天气阴,建议适当增添衣物。
第五步:常见错误与调试建议
| 问题 | 可能原因 | 解决方案 |
|---|---|---|
| LLM 直接瞎编天气 | 没传 tools,或 tool_choice="none" | 确保 tools=tools 且 tool_choice="auto" |
| 调用时参数为空 | description 写得太模糊 | 明确写“从用户问题中提取城市名” |
| 程序报错“KeyError: 'location'” | LLM 没生成 location 参数 | 检查 required 是否包含 location |
| 返回“查询失败” | 心知天气 key 无效或城市名不支持 | 在 Seniverse 控制台 测试 API |
结语:你已经掌握了 AI 联网的核心技能!
通过本文,你不仅学会了:
- 如何调用第三方 API
- 如何定义 LLM 工具(Tool)
- 如何处理 Function Calling 的完整流程
更重要的是,你理解了 每个参数背后的设计意图——这才是真正掌握技术的关键。
🚀 下一步你可以尝试:
- 添加“查汇率”工具
- 支持多城市查询
- 用自然语言解析更复杂的问题(如“明天上海会下雨吗?”)
动手实践吧!AI 的未来,由你构建 💪
附:完整代码 + 注释版已整理至 GitHub,欢迎 Star & 提问!