构建智能天气助手
前言
在人工智能快速发展的今天,大语言模型(LLM)已经成为构建智能应用的重要基础设施。本文将介绍如何利用大模型API和工具函数集成,构建一个能够理解自然语言并提供精准天气信息的智能助手。
这一示例不仅仅是构建智能天气查询助手,还可扩展到多种需要结构化数据的智能应用场景,为开发者提供实用参考。
概述
智能天气助手具有以下能力:
arduino
理解用户的自然语言查询(如"北京今天天气怎么样")
自动调用相应的工具函数获取实时天气数据
返回格式化的、易于理解的天气信息
主要包含以下几个核心设计:
天气工具模块:提供天气数据查询功能
工具注册模块:管理工具函数的注册和定义
OpenAI客户端模块:负责与大模型API通信
天气聊天机器人模块:处理用户查询并调用工具函数
主程序模块:提供用户交互界面
工作流程:
将用户查询发送给OpenAI模型
模型识别查询意图并决定调用哪个工具函数
程序执行工具函数,获取实际的天气数据
将天气数据返回给模型
模型生成最终的自然语言回复
示例对话:
使用OpenAI模式时,用户可以用更自然的方式询问天气:
arduino
"我想知道明天上海会不会下雨?"
"北京接下来几天的天气如何?"
"广州现在的空气质量怎么样?"
"我周末去杭州旅游,那边天气怎么样?"
OpenAI模型会理解这些查询,并调用适当的工具函数获取天气数据,然后生成自然、友好的回复。
工具函数调用流程
大模型的工具调用(Function Calling)是一项强大的功能,让模型能够调用外部函数获取信息。
对于OpenAI格式的工具函数(Function Calling)规范,请查看OpenAI官方文档: function-calling
整个智能天气助手实现流程如下:
scss
用户输入查询:如"北京今天天气怎么样?"
发送给大模型:将用户查询和工具定义一起发送给大模型
模型决定调用工具:模型分析查询内容,决定调用某工具,如get_weather()函数
解析工具参数:从模型返回的结果中提取工具名称和参数
执行工具函数:调用相应的工具函数获取天气数据
返回工具结果:将工具执行结果返回给模型
生成最终回复:模型根据工具结果生成自然语言回复
展示给用户:将格式化的回复展示给用户
这个流程实现了从自然语言到结构化数据再到自然语言的完整转换,使得用户可以用自然语言获取精准的天气信息。
天气查询工具
创建
weather_tool.py
,实现一个天气查询工具模块,当大模型询问天气时可以自动调用该函数。模块使用requests
库来调用和风天气API
,并使用缓存机制来优化性能。
会使用到如下和风天气API:
bash
城市查询API: https://geoapi.qweather.com/v2/city/lookup
天气查询API: https://devapi.qweather.com/v7/weather
空气质量API: https://devapi.qweather.com/v7/air/now
该模块提供以下功能:
获取当前天气信息
获取天气预报
获取空气质量数据
缓存机制减少API调用
详细的错误处理和日志记录
python
import json
import logging
import os
import time
from datetime import datetime
from typing import Dict, Any, Optional, List
import requests
# 配置日志
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
logger = logging.getLogger("WeatherTool")
class WeatherTool:
"""
天气查询工具类
提供天气查询功能,支持查询当前天气和天气预报。
使用缓存机制减少API调用,提高响应速度。
Attributes:
api_key (str): 和风天气API的密钥
geo_base_url (str): 和风天气地理位置API的基础URL
weather_base_url (str): 和风天气天气API的基础URL
cache_duration (int): 缓存有效期(秒)
_cache (dict): 内部缓存字典
_city_id_cache (dict): 城市ID缓存字典
"""
def __init__(self, api_key: Optional[str] = None, cache_duration: int = 1800):
"""
初始化天气工具实例
Args:
api_key (str, optional): 和风天气API的密钥,如果不提供则尝试从环境变量获取
cache_duration (int, optional): 缓存有效期,默认30分钟(1800秒)
"""
# 优先使用传入的API密钥,否则尝试从环境变量获取
self.api_key = api_key
# 和风天气API的URL
self.geo_base_url = "https://geoapi.qweather.com/v2/city/lookup"
self.weather_base_url = "https://devapi.qweather.com/v7/weather"
self.air_base_url = "https://devapi.qweather.com/v7/air/now"
# 缓存设置
self.cache_duration = cache_duration
self._cache = {}
self._city_id_cache = {} # 城市ID缓存
logger.info("WeatherTool已初始化,缓存有效期: % s秒", cache_duration)
def _get_cache_key(self, endpoint: str, params: Dict[str, Any]) -> str:
"""
生成缓存键
Args:
endpoint (str): API端点
params (Dict[str, Any]): 请求参数
Returns:
str: 缓存键
"""
# 将参数排序并转换为字符串,确保相同参数的不同顺序生成相同的键
param_str = json.dumps(sorted(params.items()))
return f"{endpoint}:{param_str}"
def _is_cache_valid(self, cache_time: float) -> bool:
"""
检查缓存是否有效
Args:
cache_time (float): 缓存时间戳
Returns:
bool: 缓存是否有效
"""
return (time.time() - cache_time) < self.cache_duration
def _make_request(self, url: str, params: Dict[str, Any]) -> Dict[str, Any]:
"""
发送API请求并处理响应
Args:
url (str): 完整的API URL
params (Dict[str, Any]): 请求参数
Returns:
Dict[str, Any]: API响应数据
Raises:
ConnectionError: 连接错误
TimeoutError: 请求超时
ValueError: 无效的响应数据
"""
# 添加API密钥到参数
params["key"] = self.api_key
# 检查缓存
cache_key = self._get_cache_key(url, params)
if cache_key in self._cache:
cache_time, cache_data = self._cache[cache_key]
if self._is_cache_valid(cache_time):
logger.debug("使用缓存数据 %s", cache_key)
return cache_data
try:
logger.info("请求URL: %s 请求参数: %s", url, params)
response = requests.get(url, params=params, timeout=10)
response.raise_for_status() # 如果响应状态码不是200,抛出异常
data = response.json()
# 检查API返回的状态码
if data.get("code") != "200":
error_msg = f"API返回错误: {data.get('code')}"
logger.error(error_msg)
raise ValueError(error_msg)
# 更新缓存
self._cache[cache_key] = (time.time(), data)
return data
except requests.exceptions.ConnectionError as e:
logger.error("连接错误: %s", str(e))
raise ConnectionError(f"无法连接到天气服务: {str(e)}")
except requests.exceptions.Timeout as e:
logger.error("请求超时: %s", str(e))
raise TimeoutError(f"天气查询请求超时: {str(e)}")
except requests.exceptions.HTTPError as e:
logger.error("HTTP错误: %s", str(e))
if response.status_code == 404:
raise ValueError(f"API端点不存在: {url}")
else:
raise ValueError(f"API请求错误: {response.status_code}")
except json.JSONDecodeError as e:
logger.error("JSON解析错误: %s", str(e))
raise ValueError(f"无法解析天气数据: {str(e)}")
def _get_city_id(self, city: str) -> str:
"""
获取城市ID
Args:
city (str): 城市名称
Returns:
str: 城市ID
Raises:
ValueError: 找不到城市
"""
# 检查缓存
if city in self._city_id_cache:
return self._city_id_cache[city]
# 构建查询参数
params = {"location": city}
# 发送请求
data = self._make_request(self.geo_base_url, params)
# 检查是否有结果
if not data.get("location") or len(data["location"]) == 0:
raise ValueError(f"找不到城市: {city}")
# 获取第一个结果的ID
city_id = data["location"][0]["id"]
# 更新缓存
self._city_id_cache[city] = city_id
return city_id
def get_current_weather(self, city: str, country_code: Optional[str] = None,
units: str = "metric", lang: str = "zh_cn") -> Dict[str, Any]:
"""
获取当前天气信息
Args:
city (str): 城市名称
country_code (str, optional): 国家代码,在和风天气API中不使用
units (str, optional): 单位制,在和风天气API中不使用
lang (str, optional): 返回数据的语言,默认中文
Returns:
Dict[str, Any]: 当前天气数据
Raises:
ValueError: 参数无效或城市不存在
"""
# 参数验证
if not city:
raise ValueError("城市名称不能为空")
try:
# 获取城市ID
city_id = self._get_city_id(city)
# 构建查询参数
params = {"location": city_id}
# 发送请求
url = f"{self.weather_base_url}/now"
data = self._make_request(url, params)
# 获取城市信息
city_info = self._city_id_cache.get(city, {})
if isinstance(city_info, str):
# 如果缓存中只有ID,重新获取城市信息
geo_data = self._make_request(self.geo_base_url, {"location": city})
city_info = geo_data["location"][0] if geo_data.get("location") else {}
# 处理并格式化返回数据
now_data = data["now"]
result = {
"城市": city,
"地区": city_info.get("adm1", "") if isinstance(city_info, dict) else "",
"国家": city_info.get("country", "中国") if isinstance(city_info, dict) else "中国",
"天气": now_data["text"],
"温度": f"{now_data['temp']}°C",
"体感温度": f"{now_data['feelsLike']}°C",
"湿度": f"{now_data['humidity']}%",
"气压": f"{now_data['pressure']}hPa",
"风速": f"{now_data['windSpeed']}km/h",
"风向": f"{now_data['windDir']}",
"风力等级": f"{now_data['windScale']}级",
"能见度": f"{now_data['vis']}km",
"云量": f"{now_data.get('cloud', '未知')}%",
"降水量": f"{now_data['precip']}mm",
"更新时间": now_data["obsTime"]
}
return result
except Exception as e:
logger.error("获取当前天气时出错: %s", str(e))
raise
def get_weather_forecast(self, city: str, country_code: Optional[str] = None,
days: int = 5, units: str = "metric",
lang: str = "zh_cn") -> List[Dict[str, Any]]:
"""
获取天气预报
Args:
city (str): 城市名称
country_code (str, optional): 国家代码,在和风天气API中不使用
days (int, optional): 预报天数,默认5天
units (str, optional): 单位制,在和风天气API中不使用
lang (str, optional): 返回数据的语言,默认中文
Returns:
List[Dict[str, Any]]: 天气预报数据列表
Raises:
ValueError: 参数无效或城市不存在
"""
# 参数验证
if not city:
raise ValueError("城市名称不能为空")
if days < 1 or days > 7:
raise ValueError("预报天数必须在1到7之间")
try:
# 获取城市ID
city_id = self._get_city_id(city)
# 构建查询参数
params = {
"location": city_id
}
# 发送请求 - 获取3天预报
url = f"{self.weather_base_url}/3d"
if days > 3:
# 如果需要更多天数,获取7天预报
url = f"{self.weather_base_url}/7d"
data = self._make_request(url, params)
# 处理数据
result = []
for day_data in data["daily"][:days]:
formatted = {
"日期": day_data["fxDate"],
"城市": city,
"天气": f"{day_data['textDay']}转{day_data['textNight']}",
"最高温度": f"{day_data['tempMax']}°C",
"最低温度": f"{day_data['tempMin']}°C",
"湿度": f"{day_data.get('humidity', '未知')}%",
"风速": f"{day_data['windSpeedDay']}km/h",
"风向": day_data['windDirDay'],
"风力等级": f"{day_data['windScaleDay']}级",
"降水概率": f"{day_data.get('precip', '0')}%",
"日出": day_data.get("sunrise", "未知"),
"日落": day_data.get("sunset", "未知")
}
result.append(formatted)
return result
except Exception as e:
logger.error("获取天气预报时出错: %s", str(e))
raise
def _get_wind_direction(self, degrees: float) -> str:
"""
根据角度获取风向描述
Args:
degrees (float): 风向角度
Returns:
str: 风向描述
"""
directions = [
"北风", "东北风", "东风", "东南风",
"南风", "西南风", "西风", "西北风"
]
index = round(degrees / 45) % 8
return directions[index]
def get_air_quality(self, city: str, country_code: Optional[str] = None) -> Dict[str, Any]:
"""
获取空气质量信息
Args:
city (str): 城市名称
country_code (str, optional): 国家代码,在和风天气API中不使用
Returns:
Dict[str, Any]: 空气质量数据
Raises:
ValueError: 参数无效或城市不存在
"""
# 参数验证
if not city:
raise ValueError("城市名称不能为空")
try:
# 获取城市ID
city_id = self._get_city_id(city)
# 构建查询参数
params = {
"location": city_id
}
# 发送请求
data = self._make_request(self.air_base_url, params)
# 处理数据
air_data = data["now"]
# 根据AQI值确定空气质量等级
aqi = int(air_data["aqi"])
quality = "优"
if aqi > 50:
quality = "良"
if aqi > 100:
quality = "轻度污染"
if aqi > 150:
quality = "中度污染"
if aqi > 200:
quality = "重度污染"
if aqi > 300:
quality = "严重污染"
return {
"城市": city,
"空气质量指数": f"{air_data['aqi']} ({quality})",
"PM2.5": f"{air_data['pm2p5']}μg/m³",
"PM10": f"{air_data['pm10']}μg/m³",
"臭氧": f"{air_data['o3']}μg/m³",
"二氧化氮": f"{air_data['no2']}μg/m³",
"二氧化硫": f"{air_data['so2']}μg/m³",
"一氧化碳": f"{air_data['co']}mg/m³",
"主要污染物": air_data.get("primary", "无"),
"更新时间": air_data["pubTime"],
"空气质量等级": air_data["category"]
}
except Exception as e:
logger.error("获取空气质量时出错: %s", str(e))
# 如果API不支持或出错,返回模拟数据
return {
"城市": city,
"空气质量指数": "良好",
"PM2.5": "25μg/m³",
"PM10": "48μg/m³",
"臭氧": "42μg/m³",
"二氧化氮": "15μg/m³",
"二氧化硫": "8μg/m³",
"一氧化碳": "0.8mg/m³",
"更新时间": datetime.now().strftime('%Y-%m-%d %H:%M:%S'),
"注意": "这是模拟数据,仅用于演示"
}
@staticmethod
def format_response(data: Dict[str, Any]) -> str:
"""
格式化天气数据为易读的字符串
Args:
data (Dict[str, Any]): 天气数据
Returns:
str: 格式化后的字符串
"""
result = []
for key, value in data.items():
result.append(f"{key}: {value}")
return "\n".join(result)
def clear_cache(self) -> None:
"""
清除所有缓存数据
"""
self._cache.clear()
self._city_id_cache.clear()
logger.info("缓存清空")
def get_cache_stats(self) -> Dict[str, int]:
"""
获取缓存统计信息
Returns:
Dict[str, int]: 缓存统计数据
"""
return {
"缓存项数量": len(self._cache),
"城市ID缓存数量": len(self._city_id_cache),
"缓存有效期(秒)": self.cache_duration
}
天气查询测试
可以通过以下方式使用这个工具:
bash
直接查询当前天气:weather_tool.get_current_weather("北京")
查询天气预报:weather_tool.get_weather_forecast("上海", days=3)
查询空气质量:weather_tool.get_air_quality("广州")
具体测试代码如下:
bash
if __name__ == "__main__":
# 创建天气工具实例
weather_tool = WeatherTool(api_key="xxxxxxxxxx")
try:
# 获取北京的当前天气
current_weather = weather_tool.get_current_weather("北京")
print("北京当前天气:")
print(WeatherTool.format_response(current_weather))
print("\n" + "-" * 50 + "\n")
# 获取上海的天气预报
forecast = weather_tool.get_weather_forecast("上海", days=3)
print("上海未来3天天气预报:")
for day_forecast in forecast:
print(WeatherTool.format_response(day_forecast))
print("-" * 30)
# 获取广州的空气质量
air_quality = weather_tool.get_air_quality("广州")
print("\n广州空气质量:")
print(WeatherTool.format_response(air_quality))
except Exception as e:
print(f"发生错误: {str(e)}")
bash
#>python weather_tool.py
2025-03-13 16:24:26,933 - WeatherTool - INFO - WeatherTool已初始化,缓存有效期: 1800秒
2025-03-13 16:24:26,934 - WeatherTool - INFO - 请求URL: https://geoapi.qweather.com/v2/city/lookup 请求参数: {'location': '北京', 'key': 'xxxxxxxxxx' }
2025-03-13 16:24:27,142 - WeatherTool - INFO - 请求URL: https://devapi.qweather.com/v7/weather/now 请求参数: {'location': '101010100', 'key': 'xxxxxxxxxx'}
北京当前天气:
城市: 北京
地区: 北京市
国家: 中国
天气: 霾
温度: 18°C
体感温度: 15°C
湿度: 30%
气压: 1014hPa
风速: 14km/h
风向: 东风
风力等级: 3级
能见度: 6km
云量: 100%
降水量: 0.0mm
更新时间: 2025-03-13T16:16+08:00
--------------------------------------------------
2025-03-13 16:24:27,334 - WeatherTool - INFO - 请求URL: https://geoapi.qweather.com/v2/city/lookup 请求参数: {'location': '上海', 'key': 'xxxxxxxxxx'}
2025-03-13 16:24:27,510 - WeatherTool - INFO - 请求URL: https://devapi.qweather.com/v7/weather/3d 请求参数: {'location': '101020100', 'key': 'xxxxxxxxxx'}
上海未来3天天气预报:
日期: 2025-03-13
城市: 上海
天气: 小雨转小雨
最高温度: 13°C
最低温度: 9°C
湿度: 84%
风速: 16km/h
风向: 东北风
风力等级: 1-3级
降水概率: 0.0%
日出: 06:08
日落: 18:01
------------------------------
日期: 2025-03-14
城市: 上海
天气: 小雨转中雨
最高温度: 13°C
最低温度: 9°C
湿度: 97%
风速: 16km/h
风向: 东风
风力等级: 1-3级
降水概率: 5.9%
日出: 06:07
日落: 18:02
------------------------------
日期: 2025-03-15
城市: 上海
天气: 小雨转小雨
最高温度: 11°C
最低温度: 7°C
湿度: 81%
风速: 16km/h
风向: 北风
风力等级: 1-3级
降水概率: 6.3%
日出: 06:05
日落: 18:02
------------------------------
2025-03-13 16:24:27,677 - WeatherTool - INFO - 请求URL: https://geoapi.qweather.com/v2/city/lookup 请求参数: {'location': '广州', 'key': 'xxxxxxxxxx'}
2025-03-13 16:24:27,868 - WeatherTool - INFO - 请求URL: https://devapi.qweather.com/v7/air/now 请求参数: {'location': '101280101', 'key': 'xxxxxxxxxx'}
广州空气质量:
城市: 广州
空气质量指数: 40 (优)
PM2.5: 19μg/m³
PM10: 40μg/m³
臭氧: 125μg/m³
二氧化氮: 21μg/m³
二氧化硫: 5μg/m³
一氧化碳: 0.5mg/m³
主要污染物: NA
更新时间: 2025-03-13T15:00+08:00
工具函数注册
创建
tool_registry.py
,提供了工具函数的注册、管理和定义功能,用于大模型工具调用。
该模块包含所有与工具函数注册相关的代码:
bash
工具函数注册装饰器: register_tool
工具函数注册表: TOOLS
获取已注册工具函数的方法: get_registered_tools
获取工具定义的方法: get_tools_definition
注册天气工具函数的方法: register_weather_tools
主要使用装饰器模式实现了工具函数的注册,非常优雅且易于扩展。
python
from typing import Dict, Any, List, Callable, Optional
from weather_tool import WeatherTool
# 工具函数注册表
TOOLS = {}
def register_tool(func: Callable) -> Callable:
"""工具函数注册装饰器
将函数注册到工具函数表中,以便大模型调用
Args:
func: 要注册的函数
Returns:
原函数
"""
TOOLS[func.__name__] = func
return func
def get_registered_tools() -> Dict[str, Callable]:
"""获取所有已注册的工具函数
Returns:
工具函数字典,键为函数名,值为函数对象
"""
return TOOLS
def get_tools_definition() -> List[Dict[str, Any]]:
"""获取工具定义
生成符合OpenAI格式的工具定义列表,用于API调用
Returns:
工具定义列表
"""
return [
{
"type": "function",
"function": {
"name": "get_weather",
"description": "获取指定城市的当前天气信息",
"parameters": {
"type": "object",
"properties": {
"city": {
"type": "string",
"description": "城市名称,例如'北京'、'上海'、'广州'"
},
"country_code": {
"type": "string",
"description": "国家代码,例如'CN'表示中国"
},
"units": {
"type": "string",
"enum": ["metric", "imperial"],
"description": "单位制,metric表示摄氏度,imperial表示华氏度"
}
},
"required": ["city"]
}
}
},
{
"type": "function",
"function": {
"name": "get_forecast",
"description": "获取指定城市的天气预报",
"parameters": {
"type": "object",
"properties": {
"city": {
"type": "string",
"description": "城市名称,例如'北京'、'上海'、'广州'"
},
"days": {
"type": "integer",
"description": "预报天数,范围1-7"
},
"country_code": {
"type": "string",
"description": "国家代码,例如'CN'表示中国"
},
"units": {
"type": "string",
"enum": ["metric", "imperial"],
"description": "单位制,metric表示摄氏度,imperial表示华氏度"
}
},
"required": ["city"]
}
}
},
{
"type": "function",
"function": {
"name": "get_air_quality",
"description": "获取指定城市的空气质量信息",
"parameters": {
"type": "object",
"properties": {
"city": {
"type": "string",
"description": "城市名称,例如'北京'、'上海'、'广州'"
},
"country_code": {
"type": "string",
"description": "国家代码,例如'CN'表示中国"
}
},
"required": ["city"]
}
}
}
]
# 注册天气工具函数
def register_weather_tools(weather_tool: WeatherTool):
"""注册天气相关的工具函数
Args:
weather_tool: 天气工具实例
"""
@register_tool
def get_weather(city: str, country_code: Optional[str] = None, units: str = "metric") -> Dict[str, Any]:
"""获取当前天气信息"""
return weather_tool.get_current_weather(city, country_code, units)
@register_tool
def get_forecast(city: str, days: int = 3, country_code: Optional[str] = None, units: str = "metric") -> List[Dict[str, Any]]:
"""获取天气预报"""
return weather_tool.get_weather_forecast(city, country_code, days, units)
@register_tool
def get_air_quality(city: str, country_code: Optional[str] = None) -> Dict[str, Any]:
"""获取空气质量信息"""
return weather_tool.get_air_quality(city, country_code)
OpenAI客户端
创建
openai_client.py
,该模块提供了与OpenAI兼容API进行交互的客户端类, 支持聊天完成请求和工具调用功能。
python
import json
from typing import Dict, Any, List, Optional
import requests
class OpenAIClient:
"""OpenAI API客户端"""
def __init__(self, api_url: str, api_key: str, model: str, timeout: int = 30):
"""初始化OpenAI客户端
Args:
api_url: API端点URL
api_key: API密钥
model: 模型名称
timeout: 请求超时时间(秒)
"""
self.api_url = api_url
self.api_key = api_key
self.model = model
self.timeout = timeout
self.headers = {
"Content-Type": "application/json",
"Authorization": f"Bearer {self.api_key}"
}
def chat_completion(self, messages: List[Dict[str, str]],
tools: Optional[List[Dict[str, Any]]] = None,
tool_choice: Optional[str] = None,
temperature: float = 0.7,
max_tokens: int = 1000) -> Dict[str, Any]:
"""创建聊天完成请求
Args:
messages: 对话消息列表
tools: 工具定义列表
tool_choice: 工具选择策略
temperature: 温度参数
max_tokens: 最大生成token数
Returns:
API响应或错误信息
"""
payload = {
"model": self.model,
"messages": messages,
"temperature": temperature,
"max_tokens": max_tokens
}
if tools:
payload["tools"] = tools
if tool_choice:
payload["tool_choice"] = tool_choice
try:
response = requests.post(
self.api_url,
headers=self.headers,
json=payload,
timeout=self.timeout
)
if response.status_code == 200:
return response.json()
else:
return {"error": f"API调用失败: 状态码 {response.status_code}"}
except requests.RequestException as e:
return {"error": f"网络错误: {str(e)}"}
天气聊天机器人
创建
weather_chatbot.py
,它是整个应用的核心,提供了一个天气聊天机器人类,负责处理用户的天气查询请求,并通过大模型API和调用相应工具函数返回自然语言回复
python
import json
import traceback
from typing import Dict, Any, List, Optional
from openai_client import OpenAIClient
from tool_registry import get_tools_definition, get_registered_tools
class WeatherChatbot:
"""天气聊天机器人类"""
def __init__(self, api_url: str, api_key: str, model: str):
"""初始化天气聊天机器人
Args:
api_url: API端点URL
api_key: API密钥
model: 模型名称
"""
self.client = OpenAIClient(api_url, api_key, model)
self.messages = [
{"role": "system",
"content": "你是一个天气助手,可以查询天气信息。当用户询问天气相关问题时,请使用提供的工具函数获取数据。如果用户询问的内容与天气无关,请礼貌地告诉他们你只能回答天气相关的问题。"}
]
self.tools = get_tools_definition()
self.registered_tools = get_registered_tools()
def process_query(self, query: str) -> str:
"""处理用户查询
Args:
query: 用户输入的查询文本
Returns:
模型回复或错误信息
"""
try:
# 添加用户消息
self.messages.append({"role": "user", "content": query})
# 第一次调用API,获取模型响应
response = self.client.chat_completion(
messages=self.messages,
tools=self.tools,
tool_choice="auto"
)
# 检查API错误
if "error" in response:
return f"API调用出错: {response['error']}"
# 提取模型回复
try:
message = response["choices"][0]["message"]
self.messages.append(message)
# 检查是否有工具调用
if "tool_calls" in message and message["tool_calls"]:
# 处理工具调用
return self._handle_tool_call(message["tool_calls"][0])
else:
# 如果没有工具调用,直接返回模型回复
return message["content"]
except (KeyError, IndexError) as e:
print(f"响应解析错误: {str(e)}")
return "解析响应时出错,请重试"
except Exception as e:
traceback.print_exc()
return f"处理请求时出错: {str(e)}"
def _handle_tool_call(self, tool_call: Dict[str, Any]) -> str:
"""处理工具调用
Args:
tool_call: 工具调用信息
Returns:
处理结果
"""
function_name = tool_call["function"]["name"]
try:
function_args = json.loads(tool_call["function"]["arguments"])
except json.JSONDecodeError:
return "解析工具参数时出错,请重试"
# 检查工具是否存在
if function_name not in self.registered_tools:
return f"找不到工具函数: {function_name}"
# 调用工具函数
try:
result = self.registered_tools[function_name](**function_args)
except Exception as e:
return f"天气查询失败: {str(e)},请检查城市名称是否正确"
# 添加工具调用结果
self.messages.append({
"role": "tool",
"tool_call_id": tool_call["id"],
"name": function_name,
"content": json.dumps(result, ensure_ascii=False)
})
# 第二次调用API,获取最终回复
second_response = self.client.chat_completion(messages=self.messages)
# 检查API错误
if "error" in second_response:
return f"API调用出错: {second_response['error']}"
# 提取最终回复
final_message = second_response["choices"][0]["message"]
self.messages.append(final_message)>
return final_message["content"]
def clear_history(self):
"""清除对话历史"""
self.messages = [self.messages[0]]
主程序入口
创建
run.py
模块,它是整个程序启动的入口,它实现一个简单的天气查询助手,通过大模型API和天气查询工具函数的集成,使用户可以用自然语言查询天气信息。
python
from tool_registry import get_registered_tools, register_weather_tools
from weather_tool import WeatherTool
from weather_chatbot import WeatherChatbot
# ====================== 配置区域 ======================
# 只需修改这里的URL和API_KEY即可连接不同的大模型
API_URL = "https://api.xxxx.com/v1/chat/completions" # OpenAI格式的API地址
API_KEY = "xxxxxxxxxx" # API密钥
MODEL = "deepseek/deepseek-v3" # 模型名称
# 和风天气API密钥
WEATHER_API_KEY = "xxxxxxxxxxxxx"
# =====================================================
# 创建天气工具实例并注册工具函数
weather_tool = WeatherTool(api_key=WEATHER_API_KEY)
register_weather_tools(weather_tool)
def main():
"""主函数"""
print("\n===== 天气助手配置 =====")
print(f"API地址: {API_URL}")
print(f"模型名称: {MODEL}")
print(f"已注册工具: {', '.join(get_registered_tools().keys())}")
print("========================\n")
# 创建聊天机器人实例
chatbot = WeatherChatbot(API_URL, API_KEY, MODEL)
print("\n欢迎使用天气助手!输入'退出'或'exit'结束程序,输入'清除'或'clear'清除对话历史。")
print("示例查询: '北京今天天气怎么样'、'上海未来3天天气预报'、'广州的空气质量'")
while True:
query = input("\n请输入您的问题: ")
if query.lower() in ["退出", "exit", "quit", "q"]:
print("谢谢使用,再见!")
break
if query.lower() in ["清除", "clear", "c"]:#
chatbot.clear_history()
print("对话历史已清除")
continue
print("正在查询,请稍候...")
response = chatbot.process_query(query)
print(f"\n{response}")
if __name__ == "__main__":
main()
实际应用效果
执行python run.py
,启动智能天气助手,然后用户就可以用自然语言来查询天气信息
python
>python run.py
2025-03-13 16:44:11,977 - WeatherTool - INFO - WeatherTool已初始化,缓存有效期: 1800秒
===== 天气助手配置 =====
API地址: https://api.xxxx.com/v1/chat/completions
模型名称: deepseek/deepseek-v3
已注册工具: get_weather, get_forecast, get_air_quality
========================
欢迎使用天气助手!输入'退出'或'exit'结束程序,输入'清除'或'clear'清除对话历史。
示例查询: '北京今天天气怎么样'、'上海未来3天天气预报'、'广州的空气质量'
请输入您的问题: 成都天气
正在查询,请稍候...
2025-03-13 16:44:23,153 - WeatherTool - INFO - 请求URL: https://geoapi.qweather.com/v2/city/lookup 请求参数: {'location': '成都', 'key': 'xxxxxxxxxx'}
2025-03-13 16:44:23,342 - WeatherTool - INFO - 请求URL: https://devapi.qweather.com/v7/weather/now 请求参数: {'location': '101270101', 'key': 'xxxxxxxxxx'}
成都当前的天气情况如下:
- 天气:阴
- 温度:16°C
- 体感温度:14°C
- 湿度:54%
- 气压:956hPa
- 风速:9km/h
- 风向:南风
- 风力等级:2级
- 能见度:16km
- 云量:91%
- 降水量:0.0mm
- 更新时间:2025年3月13日16:38
请注意,这些信息可能会随时间变化,建议查看最新的天气预报以获取更准确的信息。
扩展与优化
上述示例简单的构建了一个智能天气助手,它还有很多扩展和优化空间,例如以下几点:
支持更多天气数据:添加更多天气相关功能,如极端天气预警、旅游建议等
多模型支持:添加对Claude、Gemini等其他大模型的支持
用户偏好设置:记住用户的位置和单位偏好
多语言支持:添加多语言交互能力
图形化界面:开发Web或移动应用界面
缓存优化:优化缓存策略,减少API调用次数
总结
基于这种实现方式,它不仅适用于天气查询,还可以扩展到各种需要结构化数据的场景,如股票查询、新闻摘要、日程管理等。
通过将大模型的自然语言理解能力与专业工具函数结合,我们可以构建出更加智能、实用的应用。
最后希望本文能够帮助读者理解大模型工具调用的原理和实现方法,为构建自己的智能应用提供参考。