5分钟搞定Python天气查询机器人:用和风天气API+OpenAI打造你的第一个智能助手
从零构建你的第一个智能天气助手:融合大语言模型与开放数据的实战指南
最近几年,AI应用开发的门槛正在以惊人的速度降低。几年前还需要深厚机器学习背景才能涉足的领域,如今一个具备基础Python能力的开发者,借助成熟的API和开源模型,就能在几个小时内搭建出功能完整、体验流畅的智能应用。这种变化的核心,在于大语言模型(LLM) 作为一种强大的“通用理解层”,正在将复杂的自然语言处理任务,简化为清晰的API调用。今天,我们就以“天气查询”这个看似简单、实则蕴含典型智能体(Agent)逻辑的场景为切入点,亲手搭建一个能听懂人话、自主工作、并给出友好回复的智能助手。整个过程,你不需要理解Transformer架构的细节,也无需准备海量的训练数据,我们要做的,是像搭积木一样,将几个现成的、强大的服务组合起来,创造属于自己的AI产品。
这个项目非常适合有一定Python基础,但对AI应用开发感到好奇的初学者。它不涉及复杂的数学公式,重点在于理解一个现代AI应用的核心工作流:如何将用户模糊的自然语言指令,转化为精确的API调用参数;如何安全、高效地获取外部数据;以及如何将原始数据重新组织成人类友好的信息。我们将使用国内稳定可访问的天气数据服务,并结合大语言模型的语义理解能力,打造一个完全可运行、可扩展的智能体原型。你会发现,所谓的“智能”,其实现路径比你想象的要清晰和直接得多。
1. 项目蓝图:理解智能体的核心三要素
在开始写代码之前,我们有必要先厘清这个智能天气助手究竟要做什么,以及它是如何思考的。很多人听到“智能体”会觉得高深莫测,其实在这个项目中,你可以把它理解为一个高度自动化的信息处理管道。它的目标很明确:用户用最自然的方式提问,它负责搞定背后所有繁琐的步骤,并给出最终答案。
1.1 智能体的工作流拆解
想象一下,你对助手说:“帮我看看后天上海会不会下雨,需不需要带外套?”一个合格的智能体不会愣住,它会默默完成以下一连串动作:
- 意图识别与参数提取:理解“后天”、“上海”、“下雨”、“外套”这些关键词背后的意图是查询天气,并精确提取出“城市:上海”和“时间偏移量:2天(后天)”这两个API调用所必需的结构化参数。
- 工具调用与数据获取:拿着“上海”和“2”这两个参数,去调用一个可靠的天气数据服务(API),获取后天上海详细的天气预报数据,包括温度、降水概率、风力等。
- 信息整合与个性化回复:将获取到的原始数据(可能是一堆JSON字段),结合用户关于“带外套”的隐含关切,组织成一段通顺、有用、带有一点建议的自然语言回复,例如:“后天上海多云转小雨,气温18-23℃,降水概率60%。建议带上一件薄外套和雨伞。”
这个“理解-执行-反馈”的循环,就是智能体最核心的骨架。我们的代码结构也将严格遵循这个逻辑进行模块化设计。
1.2 技术选型:为什么是LLM + 专业API?
实现上述工作流,有多种技术路径。我们选择“大语言模型(LLM)+ 专业领域API”的组合,是基于以下几点考量:
- 分工明确,各司其职:LLM(如GPT、通义千问等)擅长理解和生成自然语言,但在获取实时、精确的领域数据(如天气、股票、交通)方面并不专业,且可能产生“幻觉”(编造信息)。专业API则能提供准确、实时的结构化数据。让LLM做它擅长的“翻译”和“对话”,让专业API做它擅长的“数据提供”,这是当前构建可靠AI应用的最佳实践。
- 开发效率极高:我们无需训练模型,只需通过精心设计的提示词(Prompt)来引导LLM,即可完成复杂的语义解析任务。这相当于拥有一个理解能力超强的“万能文本处理函数”。
- 可扩展性强:这个架构是模块化的。未来,如果你想为助手增加“查股价”、“订机票”等功能,只需增加新的“工具调用模块”(调用相应API),并升级LLM的提示词,教会它何时调用哪个工具即可。
基于以上思路,我们的项目将包含三个核心Python文件,正好对应智能体的三大功能模块:
project/
├── agent_brain.py # 智能“大脑”:LLM交互,理解用户意图
├── agent_tool.py # 智能“手”:调用天气API,获取数据
└── weather_assistant.py # 主程序:串联大脑和手,管理对话流程
2. 环境搭建与密钥配置:走好第一步
任何涉及第三方服务的开发,第一步永远是准备好“通行证”——也就是API密钥。这一步看似琐碎,却是项目能否跑通的关键。我们会详细说明如何免费获取所需资源。
2.1 获取天气数据API密钥
我们需要一个稳定、准确的天气数据源。这里我们选用提供免费 tier 的天气服务商。
操作步骤如下:
- 注册与登录:访问其开发者网站,使用邮箱完成注册和登录。
- 创建应用:在控制台中找到“应用管理”或类似入口,点击“创建新应用”。
- 填写信息:应用名称可以填写“MyWeatherAssistant”,类型选择“免费开发版”或类似选项。
- 获取密钥:创建成功后,在应用详情页中找到你的
API Key或Secret Key。它通常是一串由字母和数字组成的字符串。请立即妥善保存它,我们稍后会在代码中使用。
注意:免费版本的API通常有每日调用次数的限制(如1000次/天),用于学习和测试完全足够。请勿在公开渠道泄露你的真实API密钥。
2.2 配置大语言模型访问权限
为了让我们的助手能理解自然语言,我们需要接入一个大语言模型。考虑到访问的便利性和成本,我们有多种选择:
- 方案A:使用OpenAI GPT模型(需自行解决网络访问问题)。
- 方案B:使用国内云厂商的LLM API(如阿里云的通义千问、百度的文心一言)。这些服务访问稳定,且新用户通常有免费的额度。
这里我们以方案B为例,假设你选择了某个国内主流服务。
获取LLM API密钥的通用流程:
- 登录对应云服务的控制台(如阿里云、百度智能云)。
- 在产品列表中,找到其大语言模型产品(如“通义千问”、“文心大模型”)。
- 开通服务,并在“API密钥管理”页面创建新的密钥对(
Access Key Id和Access Key Secret)。 - 同样,安全保存这些信息。
2.3 初始化Python开发环境
确保你的电脑上安装了Python(建议版本3.8或以上)。然后,为项目创建一个干净的虚拟环境并安装依赖库。
打开终端(命令行),执行以下命令:
# 1. 创建项目目录并进入
mkdir weather_smart_assistant && cd weather_smart_assistant
# 2. 创建虚拟环境(可选但推荐)
python -m venv venv
# 3. 激活虚拟环境
# 在 Windows 上:
venv\Scripts\activate
# 在 macOS/Linux 上:
source venv/bin/activate
# 4. 安装必要的Python库
pip install requests openai
这里我们安装了两个库:
requests:用于发送HTTP请求,调用天气API和LLM API的利器。openai:OpenAI官方库。注意:即使我们使用国内LLM,这个库的ChatCompletion接口设计已成为一种事实标准,许多国内SDK也兼容类似接口,或者我们可以直接使用requests调用。为了教学清晰,我们先按标准模式编写,后续会说明适配方法。
3. 构建智能“大脑”:让AI理解用户意图
这是整个项目最有趣也最核心的部分。我们将编写 agent_brain.py,它的唯一任务就是:把用户的一句口语化指令,翻译成程序能理解的精确指令。
3.1 设计提示词(Prompt)
与大语言模型沟通,全靠提示词。一个好的提示词要清晰、具体,并给出示例。我们需要模型输出固定的JSON格式,方便后续程序处理。
# agent_brain.py
import json
# 注意:这里我们先使用openai库的通用结构编写,实际调用需适配
# 假设我们有一个名为 `call_llm_api` 的函数来统一处理不同厂商的调用
def parse_user_intent(user_query: str) -> dict:
"""
解析用户查询,提取城市和日期偏移量。
参数:
user_query: 用户输入的自然语言,如“后天上海天气怎么样?”
返回:
一个字典,例如: {"city": "上海", "day_offset": 2}
如果无法解析城市,city默认为“北京”。
如果无法解析日期,day_offset默认为1(明天)。
"""
# 构建系统提示词。这部分定义了AI的角色和任务。
system_prompt = """你是一个专业的天气查询助手解析器。你的任务是从用户的句子中提取出城市名和查询日期。
日期需要转换为相对于今天的天数偏移量。规则如下:
- “今天”、“今日” -> 偏移量 0
- “明天”、“明日” -> 偏移量 1
- “后天” -> 偏移量 2
- “大后天” -> 偏移量 3
如果用户没有明确指定城市,则默认使用“北京”。
如果用户没有明确指定日期,则默认查询“明天”(偏移量1)。
你只输出一个合法的JSON对象,包含两个键:`city` 和 `day_offset`。不要输出任何其他解释或文字。
"""
# 构建用户消息,将用户查询放入上下文。
user_message = f"用户查询:{user_query}"
# 这里是一个示例,模拟LLM返回的固定格式。实际开发中,这里会是API调用。
# 假设LLM完美理解了我们的指令。
llm_response_json_string = '{"city": "上海", "day_offset": 2}'
try:
result = json.loads(llm_response_json_string)
# 确保返回的字典包含我们需要的键,并设置默认值
result.setdefault("city", "北京")
result.setdefault("day_offset", 1)
return result
except json.JSONDecodeError:
# 如果LLM返回的不是合法JSON,则返回默认值
return {"city": "北京", "day_offset": 1}
# 测试函数
if __name__ == "__main__":
test_queries = [
"后天上海天气怎么样?",
"北京明天会下雨吗",
"广州的天气",
"大后天要不要带伞?", # 未指定城市
]
for query in test_queries:
print(f"输入: {query}")
print(f"解析结果: {parse_user_intent(query)}")
print("-" * 30)
运行这个测试,你会看到它成功地将自然语言转换成了结构化的数据。在实际项目中,你需要将 llm_response_json_string = '...' 这行替换为真实的API调用代码。
3.2 集成真实的LLM API调用
以下是如何适配不同LLM服务的示例。我们创建一个统一的调用函数。
# 在 agent_brain.py 中添加或修改
import os
import requests
def call_llm_api(prompt: str, user_input: str) -> str:
"""
统一调用LLM API的函数。
这里以兼容OpenAI接口格式的国内某API为例。
你需要根据自己选择的平台修改URL和参数。
"""
api_key = os.getenv("LLM_API_KEY") # 建议从环境变量读取密钥
api_base = "https://your-llm-provider.com/v1" # 替换为实际API地址
headers = {
"Authorization": f"Bearer {api_key}",
"Content-Type": "application/json"
}
data = {
"model": "qwen-turbo", # 替换为实际模型名,如 qwen-plus, ernie-bot等
"messages": [
{"role": "system", "content": prompt},
{"role": "user", "content": user_input}
],
"temperature": 0.1, # 低温度使输出更确定,适合做解析任务
"max_tokens": 150
}
try:
response = requests.post(f"{api_base}/chat/completions", json=data, headers=headers, timeout=10)
response.raise_for_status() # 如果状态码不是200,抛出异常
result = response.json()
return result["choices"][0]["message"]["content"].strip()
except Exception as e:
print(f"调用LLM API失败: {e}")
return '{"city": "北京", "day_offset": 1}' # 失败时返回默认值
# 然后修改 parse_user_intent 函数,将调用部分替换为:
# llm_response_json_string = call_llm_api(system_prompt, user_message)
关键点:
- 环境变量:强烈建议将
API Key等敏感信息存储在系统环境变量中,而不是硬编码在代码里。可以使用os.getenv("KEY_NAME")读取。 - 错误处理:网络请求可能失败,API可能返回意外格式。健壮的代码必须包含
try...except块和降级方案(如返回默认值)。
4. 打造智能“手”:可靠地获取天气数据
有了明确的结构化参数(城市和天数),下一步就是获取真实的天气数据。我们将编写 agent_tool.py,它封装了与天气API的所有交互细节。
4.1 实现天气API调用函数
天气API通常需要两个步骤:1. 将城市名转换为该API系统内部的城市ID;2. 用城市ID和日期查询具体天气。
# agent_tool.py
import requests
import os
from datetime import datetime, timedelta
class WeatherFetcher:
def __init__(self):
self.api_key = os.getenv("WEATHER_API_KEY") # 从环境变量读取密钥
if not self.api_key:
raise ValueError("未找到天气API密钥,请设置 WEATHER_API_KEY 环境变量。")
# 假设使用的天气API基础URL
self.base_url = "https://api.weather-service.com"
def _get_city_id(self, city_name: str) -> str:
"""根据城市名获取对应的城市ID。"""
url = f"{self.base_url}/city/lookup"
params = {
"location": city_name,
"key": self.api_key,
"adm": "cn", # 限定在中国城市,避免重名
"number": 1 # 只取第一个结果
}
try:
resp = requests.get(url, params=params, timeout=5)
data = resp.json()
if data.get("code") == "200" and data.get("location"):
return data["location"][0]["id"]
else:
print(f"未找到城市: {city_name}, 返回数据: {data}")
return None
except requests.exceptions.RequestException as e:
print(f"查询城市ID时网络错误: {e}")
return None
def fetch_weather(self, city_name: str, day_offset: int) -> dict:
"""
获取指定城市、指定日期的天气。
参数:
city_name: 城市中文名
day_offset: 相对于今天的天数偏移 (0=今天,1=明天...)
返回:
包含天气信息的字典,若失败则包含‘error’键。
"""
city_id = self._get_city_id(city_name)
if not city_id:
return {"error": f"无法定位城市‘{city_name}’,请检查名称是否正确。"}
# 计算目标日期
target_date = (datetime.now() + timedelta(days=day_offset)).strftime("%Y-%m-%d")
url = f"{self.base_url}/weather/7d"
params = {
"location": city_id,
"key": self.api_key
}
try:
resp = requests.get(url, params=params, timeout=5)
data = resp.json()
if data.get("code") != "200":
return {"error": f"天气API返回错误: {data.get('message', '未知错误')}"}
# 从7天预报中找出目标日期那天的数据
daily_forecasts = data.get("daily", [])
for day_data in daily_forecasts:
if day_data.get("fxDate") == target_date:
# 提取并整理我们需要的信息
return {
"city": city_name,
"date": target_date,
"weather_desc": day_data.get("textDay", "未知"),
"temp_min": day_data.get("tempMin"),
"temp_max": day_data.get("tempMax"),
"humidity": day_data.get("humidity"),
"wind_direction": day_data.get("windDirDay"),
"wind_scale": day_data.get("windScaleDay"),
"precip": day_data.get("precip") # 降水量
}
# 如果循环完没找到对应日期
return {"error": f"未找到{target_date}的天气预报数据。"}
except requests.exceptions.RequestException as e:
return {"error": f"网络请求失败: {e}"}
except (KeyError, IndexError, TypeError) as e:
return {"error": f"解析天气数据时出错: {e}"}
# 测试
if __name__ == "__main__":
# 测试前,请在终端设置环境变量:export WEATHER_API_KEY='your_key'
fetcher = WeatherFetcher()
# 测试一个成功案例和一个失败案例
print(fetcher.fetch_weather("北京", 1))
print(fetcher.fetch_weather("一个不存在的城市", 1))
这个类做了几件重要的事:
- 封装密钥管理:通过初始化方法集中管理API密钥。
- 实现城市查询:将用户友好的城市名转换为API需要的ID。
- 处理日期逻辑:根据
day_offset计算出具体的日期字符串。 - 全面的错误处理:涵盖了网络错误、API返回错误、数据解析错误等多种情况,并返回统一的错误信息格式。
4.2 理解API返回的数据结构
不同的天气API返回的数据格式差异很大。在整合数据时,关键在于提取用户关心的核心字段。一个典型的每日天气数据可能包含几十个字段,但我们只需要展示其中最有用的一部分。
以下是一个模拟的API返回数据片段,以及我们如何提取信息:
{
"code": "200",
"daily": [
{
"fxDate": "2023-10-27",
"tempMax": "22",
"tempMin": "15",
"textDay": "多云",
"windDirDay": "东南风",
"windScaleDay": "3-4",
"humidity": "65",
"precip": "0.0"
},
// ... 其他日期的数据
]
}
我们的fetch_weather方法就是从这样的结构中,精准地提取出tempMax、textDay等字段,并重新组织成我们自定义的、更简洁的字典格式。这种“数据清洗和转换”是工具模块的核心价值。
5. 组装与对话:创建主控程序
现在,我们有了能理解语言的“大脑”(agent_brain)和能干活儿的“手”(agent_tool)。最后一步,就是创建一个主程序 weather_assistant.py 来指挥它们协同工作,并管理与用户的交互。
5.1 实现主循环与工作流
主程序需要实现一个简单的命令行交互循环,完成“输入-处理-输出”的完整流程。
# weather_assistant.py
import sys
import os
# 将项目根目录加入Python路径,以便导入自定义模块
sys.path.append(os.path.dirname(os.path.abspath(__file__)))
from agent_brain import parse_user_intent
from agent_tool import WeatherFetcher
def generate_friendly_reply(weather_info: dict) -> str:
"""将结构化的天气数据转化为友好的自然语言回复,并附加建议。"""
if "error" in weather_info:
return f"抱歉,查询天气时遇到了问题:{weather_info['error']}。请稍后再试或检查城市名称。"
city = weather_info["city"]
date = weather_info["date"]
weather = weather_info["weather_desc"]
temp_range = f"{weather_info['temp_min']}℃ ~ {weather_info['temp_max']}℃"
wind = f"{weather_info['wind_direction']} {weather_info['wind_scale']}级"
# 根据天气和温度生成个性化建议
suggestion = ""
if "雨" in weather:
suggestion = "今天有降水可能,建议出门携带雨具。"
elif int(weather_info['temp_min']) < 10:
suggestion = "早晨和夜间气温较低,请注意添衣保暖。"
elif int(weather_info['temp_max']) > 28:
suggestion = "白天天气较热,请注意防暑降温,多补充水分。"
else:
suggestion = "天气条件适宜,适合进行户外活动。"
reply_lines = [
f"【{city} {date} 天气预报】",
f"天气状况:{weather}",
f"气温范围:{temp_range}",
f"风向风力:{wind}",
f"空气湿度:{weather_info.get('humidity', 'N/A')}%",
f"💡 生活提示:{suggestion}"
]
return "\n".join(reply_lines)
def main():
print("=" * 50)
print("欢迎使用智能天气助手!")
print("你可以像和朋友聊天一样问我天气,例如:")
print(" - “上海后天天气如何?”")
print(" - “北京明天要带伞吗?”")
print(" - “广州今天气温多少?”")
print("输入 ‘退出’ 或 ‘quit’ 可以结束程序。")
print("=" * 50)
fetcher = WeatherFetcher() # 初始化天气查询工具
while True:
try:
user_input = input("\n👤 你:").strip()
if user_input.lower() in ["退出", "quit", "exit"]:
print("助手:再见!期待下次为您服务。")
break
if not user_input:
continue
print("🤖 助手:正在分析您的需求...")
# 步骤1:理解意图
params = parse_user_intent(user_input)
city = params["city"]
day_offset = params["day_offset"]
day_mapping = {0: "今天", 1: "明天", 2: "后天", 3: "大后天"}
day_desc = day_mapping.get(day_offset, f"{day_offset}天后")
print(f"🤖 助手:好的,为您查询【{city}】{day_desc}的天气。")
# 步骤2:获取数据
print("🤖 助手:正在获取最新天气数据...")
weather_data = fetcher.fetch_weather(city, day_offset)
# 步骤3:生成并输出回复
reply = generate_friendly_reply(weather_data)
print(f"\n🤖 助手:\n{reply}")
except KeyboardInterrupt:
print("\n\n程序被用户中断。")
break
except Exception as e:
print(f"\n🤖 助手:抱歉,程序运行时出现了意外错误:{e}")
print("您可以尝试重新输入,或检查网络连接。")
if __name__ == "__main__":
main()
5.2 添加实用功能与优化体验
一个基础的助手已经完成,但我们可以让它更贴心、更健壮。以下是几个可以立即加入的优化点:
1. 对话记忆(简易版) 记录用户上一次查询的城市,作为下一次的默认值。
# 在 weather_assistant.py 的 main 函数开始处添加
last_city = None
# 在解析意图后,修改城市逻辑
if not city or city == "北京": # 如果解析出的城市是默认值
if last_city:
print(f"🤖 助手:检测到您上次查询了【{last_city}】,本次将默认查询该城市。")
city = last_city
last_city = city # 更新记忆
2. 更丰富的错误反馈 针对不同的错误类型,给出更具体的引导。
# 在 generate_friendly_reply 函数中细化错误处理
if "error" in weather_info:
error_msg = weather_info["error"]
if "无法定位城市" in error_msg:
return f"抱歉,没有找到‘{city}’这个城市。请检查名称是否正确,或者尝试输入更具体的名称(例如‘北京市’、‘黄浦区’)。"
elif "网络请求失败" in error_msg:
return "网络连接似乎不太稳定,查询失败了。请检查您的网络设置后重试。"
else:
return f"查询过程中遇到技术问题:{error_msg}。"
3. 支持更灵活的时间表达
在agent_brain的提示词中,可以增加更多时间关键词的映射,例如“周末”、“下周一下午”、“除夕”等。这需要更复杂的日期计算逻辑,但原理相通——在提示词中教会LLM如何将这些表达转换为具体的day_offset。
6. 部署与后续迭代:让你的助手真正可用
让代码在本地运行起来只是第一步。如何让它变成一个可以随时访问的服务?如何增加更多功能?
6.1 从命令行到Web服务
本地命令行工具不方便分享。我们可以用极少的代码,将其升级为一个轻量的Web API服务,方便通过浏览器或手机访问。
使用 Flask 或 FastAPI 可以快速实现。以下是一个 FastAPI 的极简示例:
# app.py
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
from agent_brain import parse_user_intent
from agent_tool import WeatherFetcher
from weather_assistant import generate_friendly_reply
app = FastAPI(title="智能天气助手API")
fetcher = WeatherFetcher()
class QueryRequest(BaseModel):
question: str
@app.post("/ask")
async def ask_weather(query: QueryRequest):
"""接收自然语言问题,返回天气答案。"""
try:
params = parse_user_intent(query.question)
weather_data = fetcher.fetch_weather(params["city"], params["day_offset"])
reply = generate_friendly_reply(weather_data)
return {"answer": reply, "params": params}
except Exception as e:
raise HTTPException(status_code=500, detail=f"处理请求时出错: {str(e)}")
@app.get("/")
async def root():
return {"message": "智能天气助手API服务正在运行。请使用 POST /ask 接口进行查询。"}
# 运行命令:uvicorn app:app --reload --host 0.0.0.0 --port 8000
现在,你可以通过向 http://你的IP:8000/ask 发送一个包含 {"question": "上海明天天气"} 的POST请求来获取天气信息。前端网页或移动端App可以轻松调用这个接口。
6.2 扩展智能体的能力边界
天气查询只是一个起点。这个架构的强大之处在于其可扩展性。设想一下,你的助手还能做什么?
- 多工具调度:定义一个新的“工具函数”
fetch_stock_price(),并在提示词中告诉LLM:“当用户询问股票价格时,调用这个工具”。智能体的大脑会自动学会在合适的时候选择正确的工具。 - 长期记忆:集成一个向量数据库(如
ChromaDB),将每次对话的摘要存储起来。当用户说“还记得我上周问你什么吗?”,助手可以从记忆中检索相关信息。 - 自动化工作流:结合定时任务(如
schedule库),让助手每天早上9点主动推送你所在城市的天气和穿衣建议到你的社交软件。
构建这个天气助手的过程,本质上是一次对现代AI应用开发范式的微型实践。你亲手验证了如何将强大的基础模型与精准的垂直数据相结合,创造出解决实际问题的智能体验。代码行数不多,但涉及的思路——模块化设计、提示词工程、API集成、错误处理——正是当前AI应用开发中最核心、最通用的技能。希望这个项目能成为你探索更广阔AI世界的一块坚实跳板。
DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。
更多推荐
所有评论(0)