DeepSeek结合Function Calling打造智能天气查询助手:从API调用到自然语言交互
1. 从“聊天”到“行动”:为什么你的AI助手需要Function Calling?
你有没有遇到过这样的情况?你问一个AI助手:“今天北京热不热?我该穿什么衣服?” 它可能会给你一个很长的回答,告诉你北京夏天通常很热,建议穿短袖,但就是给不出此时此刻的真实温度和具体天气状况。这不是AI笨,而是它被“困”在了自己的知识库里——它只能基于训练时学到的数据来回答,无法获取最新的、实时的外部信息。
这就是Function Calling要解决的核心问题。你可以把它想象成给AI装上了一双“手”和“眼睛”。以前,AI只能“想”和“说”;现在,通过Function Calling,它还能“做”——调用外部的工具、API和服务,把真实世界的数据拿进来,再结合自己的理解力,给你一个真正有用的答案。
我刚开始接触这个概念时,觉得特别酷。这不就是把大模型从一个“百科全书式的聊天机器人”,升级成了一个“能替你办事的智能助理”吗?比如查天气、订机票、查股票、控制智能家居,这些需要连接现实世界数据的任务,现在都能通过Function Calling来实现。
DeepSeek作为国内领先的大模型,对Function Calling的支持已经非常成熟和稳定。我实测下来,它的意图识别准确率很高,能很好地理解用户什么时候需要调用外部函数,并且生成的调用参数也很规范。这让我们开发者可以更专注于业务逻辑,而不是在和大模型“沟通”上耗费太多精力。
所以,如果你想让你的AI应用不再“纸上谈兵”,而是能真正解决实际问题,那么掌握Function Calling就是你的必修课。接下来,我就手把手带你,用最少的代码,打造一个能真正查询实时天气的智能助手。
2. 核心装备准备:三样东西就能开工
在开始写代码之前,我们得把“工具箱”准备好。整个过程只需要三样核心装备,获取起来都不难。
第一样装备:DeepSeek的API访问权限。 这是我们智能助手的大脑。你需要去DeepSeek的官方平台注册一个账号。注册成功后,在控制台通常能找到“API Keys”或类似的管理页面,创建一个新的API Key。这个Key就像一把钥匙,我们的代码用它来告诉DeepSeek平台:“嗨,是我,我要使用你的模型服务了。” 记得把它复制保存好,一会儿要填到代码里。DeepSeek提供了兼容OpenAI的API接口,这意味着我们可以直接用熟悉的openai这个Python库来调用它,这对开发者来说非常友好。
第二样装备:一个可靠的天气数据源,也就是天气API。 这里有很多选择,比如和风天气、高德天气、OpenWeatherMap等。为了教程的通用性,我们选用一个国际化的服务,比如WeatherAPI。你只需要去它的官网注册,同样获取一个API Key。它的免费套餐通常足够我们做开发和测试了。这个API的作用是提供实时的、准确的天气数据,比如温度、湿度、天气状况(晴、雨、多云等)。这是我们助手获取信息的“眼睛”。
第三样装备:你的本地开发环境。 确保你的电脑上安装了Python(建议3.8以上版本)。我们只需要两个额外的Python库:openai和requests。你可以在终端里用一句命令搞定:pip install openai requests。openai库用来和DeepSeek对话,requests库用来调用天气API获取数据。
把这三样东西备齐——DeepSeek的API Key、天气API的Key、配置好的Python环境——我们的基础工作就完成了。整个过程就像准备一顿大餐,食材和厨具都到位了,接下来就是展示烹饪技巧的时候。
3. 第一步:定义“手”的能力——编写天气查询函数
现在,我们要为AI助手打造它的第一项“手艺”:查询天气。这个函数是独立于大模型存在的,由我们开发者用Python亲手编写。它的职责很单纯:接收一个城市名字,然后去问天气API,拿到数据后,整理成一个清晰的字符串结果。
我来给你写一个扎实的例子,并解释里面的每一个关键点:
import requests
def get_current_weather(city: str) -> str:
"""
根据城市名称查询实时天气。
参数:
city (str): 城市名称,例如 "北京"、"Shanghai"。
返回:
str: 格式化的天气信息字符串,或错误信息。
"""
# 1. 替换为你在WeatherAPI上获得的真实API Key
api_key = "YOUR_WEATHERAPI_KEY_HERE"
# 2. WeatherAPI的当前天气接口地址
base_url = "http://api.weatherapi.com/v1/current.json"
# 3. 构造请求参数
params = {
'key': api_key,
'q': city, # 查询的城市
'aqi': 'no' # 我们不查询空气质量指数,简化返回
}
try:
# 4. 发送HTTP GET请求
response = requests.get(base_url, params=params, timeout=10)
# 5. 检查请求是否成功 (HTTP状态码200表示成功)
if response.status_code == 200:
data = response.json() # 将返回的JSON数据解析为Python字典
# 6. 从复杂的JSON结构中提取我们关心的核心信息
location_name = data['location']['name']
current_data = data['current']
temperature_c = current_data['temp_c'] # 摄氏温度
weather_description = current_data['condition']['text'] # 天气描述
humidity = current_data['humidity'] # 湿度
wind_kph = current_data['wind_kph'] # 风速
# 7. 组装一个对人类友好的结果字符串
result = (
f"{location_name}的当前天气:{weather_description}。"
f"气温 {temperature_c}°C,湿度 {humidity}%,风速 {wind_kph}公里/小时。"
)
return result
else:
# 处理API返回的错误,例如城市不存在、Key无效等
return f"查询天气失败。API返回状态码:{response.status_code}。请检查城市名称或API配置。"
except requests.exceptions.Timeout:
return "天气查询请求超时,请稍后重试。"
except requests.exceptions.RequestException as e:
return f"网络请求出错:{e}"
except KeyError as e:
return f"解析天气数据时遇到意外格式,缺少字段:{e}"
这个函数我刻意写得很详细,并加了完整的异常处理。在实际项目中,异常处理至关重要。网络可能会超时,API可能返回错误格式,用户可能输入一个不存在的城市。一个健壮的函数必须能妥善处理这些情况,并返回明确的错误信息,而不是让程序崩溃。你可以先直接用这个函数,把YOUR_WEATHERAPI_KEY_HERE替换成你自己的Key,在Python环境里单独测试一下:print(get_current_weather(“Beijing”)),看看是否能成功拿到天气信息。这一步确保了我们的“手”本身是灵活好用的。
4. 第二步:告诉AI“手”怎么用——用JSON Schema定义工具
好了,现在我们有了一只灵巧的“手”(get_current_weather函数),但AI大脑(DeepSeek模型)还不知道这只手的存在,更不知道什么时候该伸手,以及伸手时该怎么摆姿势。这一步,我们就是要给大脑一份详细的“工具说明书”。
这份说明书必须用一种模型能理解的、结构化的语言来写,这就是 JSON Schema。我们通过一个叫做 tools 的列表,把说明书传递给DeepSeek。下面我来拆解这份说明书里的每一个字段:
tools = [
{
"type": "function", # 固定写法,告诉模型这是一个函数工具
"function": {
"name": "get_current_weather", # 必须和我们在第三步定义的函数名完全一致
"description": "当用户想查询某个城市的当前实时天气时,调用此函数。", # 这是最重要的部分!用自然语言清晰描述函数的用途和调用场景。
"parameters": { # 用JSON Schema定义函数需要的参数
"type": "object",
"properties": { # 定义这个对象有哪些属性(即函数的参数)
"city": {
"type": "string",
"description": "需要查询天气的城市名称,例如:北京、上海、New York。请从用户的问题中提取城市名。" # 告诉模型如何获取这个参数
}
},
"required": ["city"] # 声明哪些参数是调用时必须提供的。这里`city`是必填项。
# 注意:我们没有设置 `additionalProperties: false`,这是非严格模式,允许模型有一定的灵活性。
}
}
}
]
这里有几个我踩过坑的经验点要分享给你:
name字段:一定要和你实际定义的Python函数名一模一样,大小写敏感。模型在决定调用后,返回的会是这个字符串,我们的代码要靠它来找到并执行对应的函数。description字段:这是灵魂所在。模型完全依靠这段描述来判断“用户现在的问题是不是需要调用这个函数”。所以描述要准确、具体。比如这里强调是“当前实时天气”,如果用户问“明天天气”,模型可能就不会调用这个查实时天气的函数(除非我们定义了另一个查预报的函数)。你可以把它想象成给模型的一道判断题。parameters描述:city参数的描述里,我加了一句“请从用户的问题中提取城市名”。这是一个非常有效的提示(prompt),能引导模型更准确地进行信息抽取。比如用户问“巴黎天气怎么样?”,模型就会知道要把“巴黎”这个字符串提取出来,作为city参数的值。
这份“工具说明书”的质量,直接决定了AI助手是否能做出正确的决策。写得模糊,模型就可能该调用时不调用,或者调用时参数乱填。在后续的测试环节,如果发现模型意图识别不准,第一个要回来检查优化的就是这里的描述。
5. 第三步:组装大脑与手——构建完整的对话流程
现在,我们有了大脑(DeepSeek模型)和手(天气函数及说明书),接下来要设计一套流程,让它们协同工作。这个流程就像一场精心安排的对话,涉及用户、AI助手和外部工具三方。
整个流程的核心步骤,我画了一个简单的示意图帮你理解:
用户提问
↓
[DeepSeek模型] 接收问题,分析意图,查看可用的工具(`tools`列表)
↓
模型判断:需要调用 `get_current_weather` 吗?
├── 是 → 生成结构化调用请求 (如 `{"city": "北京"}`),暂停文本生成
└── 否 → 直接生成自然语言回答
↓ (假设需要调用)
我们的代码收到调用请求,执行真实的 `get_current_weather("北京")` 函数
↓
函数返回结果 (如 "北京的当前天气:晴。气温 25°C...")
↓
我们将结果作为“工具执行回复”反馈给模型
↓
[DeepSeek模型] 收到工具结果,结合原始问题,生成最终的自然语言回答
↓
将最终回答呈现给用户
下面就是实现这个流程的完整Python代码。我把关键逻辑都写在了注释里:
import requests
from openai import OpenAI # 使用兼容OpenAI的库
# 1. 初始化DeepSeek客户端
client = OpenAI(
api_key="YOUR_DEEPSEEK_API_KEY_HERE", # 替换成你的DeepSeek API Key
base_url="https://api.deepseek.com", # DeepSeek的API端点
)
# 2. 天气查询函数 (同第三步)
def get_current_weather(city: str) -> str:
# ... (此处省略函数具体实现,和第三步的代码完全一样)
# 记得替换里面的 WeatherAPI Key
pass
# 3. 工具定义 (同第四步)
tools = [
{
"type": "function",
"function": {
"name": "get_current_weather",
"description": "当用户想查询某个城市的当前实时天气时,调用此函数。",
"parameters": {
"type": "object",
"properties": {
"city": {
"type": "string",
"description": "需要查询天气的城市名称,例如:北京、上海、New York。请从用户的问题中提取城市名。",
}
},
"required": ["city"],
},
}
}
]
def chat_with_weather_assistant(user_query: str) -> str:
"""
与智能天气助手对话的主函数。
"""
# 初始化对话历史,从用户问题开始
messages = [{"role": "user", "content": user_query}]
# 第一次调用DeepSeek:让它分析是否需要调用工具
print("🧠 AI正在思考是否需要查询天气...")
response = client.chat.completions.create(
model="deepseek-chat", # 指定使用DeepSeek模型
messages=messages,
tools=tools, # 关键:传入我们定义的工具列表
tool_choice="auto", # 让模型自主决定是否调用工具。也可强制设为 "none"或指定某个工具。
temperature=0.1, # 较低的温度使输出更确定,适合工具调用场景
)
assistant_message = response.choices[0].message
messages.append(assistant_message) # 将模型的回复加入对话历史
# 检查模型是否决定调用工具
if assistant_message.tool_calls:
print("🔧 AI决定调用外部工具...")
# 处理每一个工具调用(本例中通常只有一个)
for tool_call in assistant_message.tool_calls:
func_name = tool_call.function.name
func_args = tool_call.function.arguments # 这是一个JSON字符串
# 安全地解析参数(这里用了json.loads,比eval更安全)
import json
arguments = json.loads(func_args)
city = arguments.get("city")
# 根据函数名,调用我们预先定义好的函数
if func_name == "get_current_weather":
tool_result = get_current_weather(city)
else:
tool_result = f"错误:未知的函数调用 {func_name}"
# 将工具执行结果以特定格式反馈给模型
messages.append({
"role": "tool",
"tool_call_id": tool_call.id, # 必须对应之前的调用ID
"content": tool_result, # 工具返回的结果
})
# 第二次调用DeepSeek:让它基于工具结果生成最终回答
print("🤖 AI正在整合信息,生成最终回答...")
second_response = client.chat.completions.create(
model="deepseek-chat",
messages=messages, # 此时的messages包含了用户问题、模型调用请求、工具结果
temperature=0.7, # 可以调高一点,让最终回答更自然
)
final_answer = second_response.choices[0].message.content
return final_answer
else:
# 模型认为不需要调用工具,直接返回它的回答
print("💬 AI直接回答了问题。")
return assistant_message.content
# 4. 让我们来试试效果!
if __name__ == "__main__":
test_questions = [
"今天北京天气怎么样?",
"上海和广州现在哪里更热?", # 注意:这可能需要模型发起两次工具调用
"帮我写一首关于春天的诗。", # 与天气无关,不应触发工具调用
"巴黎现在多少度?",
]
for question in test_questions:
print(f"\n👤 用户: {question}")
print("-" * 40)
answer = chat_with_weather_assistant(question)
print(f"🤖 助手: {answer}")
print("-" * 40)
这段代码是一个完整的、可运行的智能体核心。我强烈建议你把它复制到本地,填好两个API Key,然后运行看看。观察控制台的打印,你会清晰地看到“AI正在思考...” -> “AI决定调用工具...” -> “AI正在整合信息...” 这个过程,直观地理解Function Calling的运作机制。
6. 效果实测与进阶调优:让你的助手更聪明可靠
代码跑起来后,你可能会遇到一些有趣的情况,这正是调优的起点。我们来分析几个典型场景:
场景一:精准命中。 用户问“北京天气如何?”。模型会轻松识别意图,提取城市“北京”,调用工具,并最终回复:“北京的当前天气:晴。气温 22°C,湿度 35%,风速 10公里/小时。” 完美!
场景二:模糊或复杂查询。 用户问“我下周要去上海和杭州出差,那边天气好吗?”。这里就有挑战了:1. 时间点是“下周”,我们的函数只支持当前天气。2. 涉及两个城市。对于这种情况,目前的助手可能表现不佳。它可能只调用一次查询上海当前天气,或者生成错误的参数。
如何调优?
- 增强工具描述:在工具的
description里更明确地写上“本函数仅查询当前实时天气,无法查询未来预报或历史天气”。这能帮助模型更好地做决策。 - 定义更多工具:我们可以创建第二个函数
get_weather_forecast(city, days),专门查询多日预报,并在tools列表里同时提供这两个工具。模型就能根据用户问题中的“下周”关键词,选择调用预报函数。 - 优化参数提取:对于“上海和杭州”,模型可能需要发起并行工具调用。这需要检查你使用的DeepSeek模型版本是否支持
parallel_tool_calls=True参数。如果支持,模型可以一次性生成两个工具调用请求,我们的代码需要能同时处理它们。 - 处理模型“幻觉”:有时模型可能因为描述不清,调用函数时参数乱写,比如
city: “那个很冷的地方”。这时,我们的get_current_weather函数内部的错误处理就会返回友好提示,模型接收到这个错误结果后,可能会在最终回答中告诉用户“无法找到该城市的天气”。
另一个重要参数是tool_choice。在我们的代码里它设为”auto”。你还可以尝试:
tool_choice={“type”: “function”, “function”: {“name”: “get_current_weather”}}来强制模型调用特定函数。tool_choice=”none”来强制模型不调用任何函数,即使它觉得需要。
实测和调优是一个迭代过程。多准备一些边缘案例进行测试,比如带国家名的城市(“法国巴黎”)、口语化表达(“帝都这天儿咋样?”)、错误拼写等,根据助手的反馈不断打磨工具描述和函数健壮性,你的助手就会变得越来越聪明可靠。
7. 不止于天气:Function Calling的无限可能
一旦掌握了天气查询这个范例,你就打开了AI应用开发的一扇新大门。Function Calling的本质是让大模型成为所有数字服务的统一、智能的交互界面。它的应用场景远远不止查天气。
想象一下这些场景:
- 智能电商客服:用户说“帮我找一款500元以内的无线蓝牙耳机,要续航长的”。你可以定义
search_products(category, max_price, keyword)函数,连接你的商品数据库。模型理解需求后,调用函数搜索,再将结果整理成“为您找到以下几款符合要求的耳机...”这样的自然语言。 - 个人效率助手:用户说“把我明天上午十点的会议记到日历里”。定义
create_calendar_event(title, start_time, end_time)函数,连接Google Calendar或飞书日历API。模型提取时间、事件信息,调用函数创建日程。 - 数据分析助手:用户上传一张销售表格图片,然后问“Q3季度哪个产品销量最高?”。你可以结合视觉模型分析图片提取数据,然后定义
query_sales_data(metric, period, product)函数,让模型调用它来获取具体分析结果。
技术架构上的扩展:
- 工具路由:当你有几十个函数时,不可能把所有定义都塞在一次请求里。你可以设计一个“工具路由层”。先让模型根据用户意图,选择一个大的工具类别,然后再动态加载该类别的具体工具列表进行第二轮调用。
- 结果后处理:工具返回的可能是复杂的JSON。你可以在函数内部或模型回复前,增加一个后处理步骤,比如将数据转换成更清晰的图表描述、提取关键指标等,让模型的最终回答信息量更大。
- 与智能体(Agent)框架结合:像Dify、LangChain这样的框架,提供了更高层级的抽象。它们可以帮你管理对话状态、工具集,甚至实现自动迭代(让模型根据上一个工具的结果决定下一步做什么)。这非常适合构建复杂的多步骤任务助手。
从我自己的项目经验来看,初期最容易犯的错误是试图让一个函数做太多事,或者工具描述写得过于宽泛。我的建议是:保持工具的小而专。一个函数只做好一件事,并用清晰、无歧义的语言描述它。这样模型的判断准确率最高。先从一个工具开始,跑通闭环,获得正反馈,然后再逐步添加更多工具,你的智能助手的能力就会像搭积木一样不断增长。
Function Calling这项技术,真正让大模型从“时代的旁观者”变成了“世界的参与者”。它不再只是谈论世界,而是可以操作和影响世界。希望你通过这个天气助手的实践,已经感受到了这种魔力。接下来,就大胆地去为你关心的领域和问题,打造专属的智能解决方案吧。
更多推荐


所有评论(0)