构建智能天气助手:基于大模型API与工具函数的调用实践

构建智能天气助手

前言

在人工智能快速发展的今天,大语言模型(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调用次数

总结

基于这种实现方式,它不仅适用于天气查询,还可以扩展到各种需要结构化数据的场景,如股票查询、新闻摘要、日程管理等。
通过将大模型的自然语言理解能力与专业工具函数结合,我们可以构建出更加智能、实用的应用。
最后希望本文能够帮助读者理解大模型工具调用的原理和实现方法,为构建自己的智能应用提供参考。

相关推荐
爱听歌的周童鞋4 小时前
理解llama.cpp如何进行LLM推理
llm·llama·llama.cpp·inference
Golinie5 小时前
使用Ollama+Langchaingo+Gin通过定义prompt模版实现翻译功能
llm·prompt·gin·langchaingo
movee14 小时前
十分钟从零开始开发一个自己的MCP server(二)
后端·llm·mcp
movee14 小时前
十分钟从零开始开发一个自己的MCP server(一)
后端·llm·mcp
草梅友仁16 小时前
GPT-4o 多模态图像生成功能解析 | 2025 年第 13 周草梅周报
aigc·openai·ai编程
伊织code18 小时前
GPT Actions
gpt·openai·api·action
Martian小小19 小时前
MCP探索
llm·mcp
go4it19 小时前
聊聊Spring AI的Advisors
llm
我爱果汁1 天前
AI学习-1-Ollama+OpenUI+NeutrinoProxy本地搭建Deepseek-R1
人工智能·llm·aigc
MuMu1 天前
Cursor的傻瓜式教程
openai·cursor