当前位置: 首页 > ai >正文

Qwen-Agent的使用示例-天气查询(function calling)

关于心知天气

心知天气(Seniverse)是一家提供气象数据服务的平台。该服务适合开发者或企业集成到应用或系统中。
网址:https://seniverse.yuque.com
心知天气API官方文档

访问频限说明
  • 免费版限制:每分钟最多20次API调用,覆盖国内370个主要城市,可提供3项基础天气数据(如实时天气、预报等)。
  • 适用场景:适合低频需求或测试用途,如需更高频次需升级付费套餐。

关于 function call

function call(函数调用):指的是大模型(如Qwen、GPT等)在推理过程中,自动识别用户意图并调用本地或注册的Python函数(如工具函数、插件等)。调用过程在本地Python环境内完成,数据流不经过外部服务。

本示例的实现方式分析

  • WeatherTool 通过 @register_tool('get_weather') 注册为Qwen-Agent的工具(tool),并在call方法中用asyncio.run(weather_controller.get_weather(args['city']))实现了本地的异步天气查询。
  • 该工具的实现逻辑完全在本地Python进程内完成,虽然底层用到了httpx异步请求外部API(心知天气),但工具的注册、调用和数据流都在本地Python环境内,并未通过MCP协议暴露为独立服务。
  • Qwen-Agent在推理时会自动识别需要调用get_weather,并通过function call机制调用本地注册的WeatherTool

function call调用范式的典型实现:

 - 工具注册在本地  - 工具调用在本地  - 工具内部可访问外部API,但对大模型来说是本地函数- **不是MCP调用**。如果要实现MCP,需要将天气查询功能通过MCP协议暴露为服务,模型通过MCP协议远程调用该服务(如通过FastMCP、mcp-server等框架实现)。

如何区分function call和MCP?

  • function call:模型直接调用本地注册的Python函数/类,调用链不出本地进程。
  • MCP:模型通过协议(如MCP、gRPC、HTTP等)调用外部服务,通常需要单独运行MCP服务进程,模型与服务解耦。

实现代码如下:

#!/usr/bin/env python
# -*- coding: utf-8 -*-"""
Qwen-Agent的使用示例 - 心知天气MCP实现
"""import os
import json
import time
import hmac
import base64
import urllib
import asyncio
from typing import Dict, Any, Optional
from dataclasses import dataclass
from abc import ABC, abstractmethod
import httpx
from qwen_agent.agents import Assistant# ============ Model Layer ============
@dataclass
class WeatherInfo:"""天气信息数据模型"""temperature: strcondition: strcity: strhumidity: strwind_direction: strwind_speed: strdef to_dict(self) -> Dict[str, str]:return {"temperature": self.temperature,"condition": self.condition,"city": self.city,"humidity": self.humidity,"wind_direction": self.wind_direction,"wind_speed": self.wind_speed}# ============ Provider Layer ============
class WeatherProvider(ABC):"""天气数据提供者抽象基类"""@abstractmethodasync def get_weather(self, city: str) -> Optional[WeatherInfo]:passclass SeniverseWeatherProvider(WeatherProvider):"""心知天气数据提供者"""def __init__(self):self.api_secret = "Sh7XVAAnSWd5TNcET"  # 替换为您的心知天气API私钥self.base_url = "https://api.seniverse.com/v3"self.unit = "c"  # 温度单位: c 摄氏度, f 华氏度self.language = "zh-Hans"  # 语言: zh-Hans 简体中文, en 英文def _generate_signature(self, params: Dict[str, str]) -> str:"""生成API签名"""params_str = '&'.join([f"{k}={params[k]}" for k in sorted(params.keys())])message = 'GET\napi.seniverse.com\n/v3/weather/now.json\n' + params_strsignature = base64.b64encode(hmac.new(self.api_secret.encode(),message.encode(),digestmod='sha1').digest()).decode()return signatureasync def get_weather(self, city: str) -> Optional[WeatherInfo]:"""获取天气信息"""try:params = {'location': city,'key': self.api_secret,'unit': self.unit,'language': self.language}# 生成签名params['sig'] = self._generate_signature(params)# 构建请求URLurl = f"{self.base_url}/weather/now.json?{urllib.parse.urlencode(params)}"print(f"Request URL: {url}")async with httpx.AsyncClient() as client:response = await client.get(url)response.raise_for_status()data = response.json()if 'results' in data and len(data['results']) > 0:result = data['results'][0]now = result['now']return WeatherInfo(temperature=f"{now['temperature']}°C",condition=now['text'],city=result['location']['name'],humidity=now.get('humidity', 'N/A'),wind_direction=now.get('wind_direction', 'N/A'),wind_speed=f"{now.get('wind_speed', 'N/A')}km/h")return Noneexcept Exception as e:print(f"获取天气数据失败: {str(e)}")return None# ============ Controller Layer ============
class WeatherController:"""天气查询控制器"""def __init__(self, provider: WeatherProvider):self._provider = providerasync def get_weather(self, city: str) -> Dict[str, Any]:"""获取天气信息"""weather_info = await self._provider.get_weather(city)if weather_info:return weather_info.to_dict()return {"error": f"没有找到{city}的天气信息"}# 初始化天气控制器
weather_controller = WeatherController(SeniverseWeatherProvider())# ============ Tool Layer ============
from qwen_agent.tools.base import BaseTool, register_tool@register_tool('get_weather')
class WeatherTool(BaseTool):description = "获取指定城市的天气信息"parameters = {'type': 'object','properties': {'city': {'type': 'string','description': '城市名称'}},'required': ['city']}def call(self, params: str, **kwargs) -> Dict[str, Any]:"""同步调用接口"""try:args = json.loads(params)# 使用asyncio运行异步函数return asyncio.run(weather_controller.get_weather(args['city']))except json.JSONDecodeError:return {"error": "参数解析失败"}except Exception as e:return {"error": f"查询天气失败: {str(e)}"}# 创建Assistant实例
assistant = Assistant(llm={'model': 'qwen-max','model_type': 'qwen_dashscope','api_key': os.getenv("DASHSCOPE_API_KEY")   # 若没有配置环境变量,请用百炼API Key替换},function_list=['get_weather']
)if __name__ == "__main__":messages = []# 测试Agent对话user_query = "我应该带伞出门吗?我在广州。"print(f"\n用户: {user_query}")messages.append({"role": "user", "content": user_query})final_response = Nonefor response in assistant.run(messages):if isinstance(response, list):final_response = response[-1]if isinstance(final_response, dict) and "content" in final_response:messages.append(final_response)print(f"Agent: {final_response['content']}")

代码详解

1. 启动与初始化阶段

  • 1.1 脚本入口

    • 代码从if __name__ == "__main__":开始执行,初始化对话消息列表messages = []
  • 1.2 工具注册

    • 通过@register_tool('get_weather'),将WeatherTool注册为Qwen-Agent可调用的工具(function call工具)。
    • 工具描述、参数schema等信息被Qwen-Agent框架收集,用于后续大模型推理时的函数调用决策。
  • 1.3 Assistant实例化

    • 创建Assistant对象,指定大模型(如qwen-max)、API Key等参数,并声明可用工具function_list=['get_weather']

2. 用户输入与对话流程

  • 2.1 用户输入

    • 用户输入一句自然语言问题(如“我应该带伞出门吗?我在广州。”),被加入messages列表。
  • 2.2 Assistant.run(messages)

    • 调用assistant.run(messages),进入主对话流程。

3. 大模型推理与function call决策

  • 3.1 LLM理解意图

    • Qwen大模型分析用户输入,判断需要调用天气查询工具(get_weather)。
  • 3.2 生成function call请求

    • 大模型自动生成function call请求,参数为{"city": "广州"},并调用本地注册的WeatherTool

4. 工具调用与数据获取

  • 4.1 WeatherTool.call方法执行

    • Qwen-Agent框架调用WeatherTool.call(params),params为{"city": "广州"}的JSON字符串。
  • 4.2 参数解析

    • call方法内部用json.loads(params)解析参数,提取城市名。
  • 4.3 异步天气查询

    • 通过asyncio.run(weather_controller.get_weather(args['city']))同步调用异步天气查询控制器。
  • 4.4 Controller调度Provider

    • WeatherController.get_weather调用SeniverseWeatherProvider.get_weather,实际发起HTTP请求。
  • 4.5 HTTP请求心知天气API

    • 构造API请求参数(含签名),用httpx.AsyncClient异步请求心知天气API,获取实时天气数据。
  • 4.6 数据解析与模型化

    • 解析API返回的JSON,提取温度、天气预报等,封装为WeatherInfo数据对象。
  • 4.7 数据返回

    • WeatherInfo.to_dict()转为字典,逐层返回到WeatherTool.call,再返回给Qwen-Agent主流程。

5. 大模型生成回复

  • 5.1 工具结果注入上下文

    • 工具返回的天气信息被注入到大模型的上下文中。
  • 5.2 LLM生成自然语言回复

    • Qwen大模型基于工具返回结果,生成最终的自然语言回复(如“广州当前天气为小雨,温度30°C,建议带伞出门。”)。

6. 输出与对话循环

  • 6.1 回复输出

    • 最终回复通过print(f"Agent: {final_response['content']}")输出到终端。
  • 6.2 多轮对话(可选)

    • 若有多轮对话需求,可继续追加用户输入与Agent回复,循环上述流程。

总结流程图

  1. 用户输入 → 2. Assistant.run → 3. LLM推理 → 4. function call触发 → 5. WeatherTool.call → 6. Controller/Provider异步API → 7. 数据返回 → 8. LLM生成回复 → 9. 输出

系统思维补充

  • 本流程实现了“自然语言→智能工具调用→外部API→结构化数据→自然语言”的完整闭环。
  • function call机制极大提升了大模型的工具能力和可扩展性。
  • 代码结构清晰,便于后续扩展更多工具或对接不同API。
http://www.xdnf.cn/news/9133.html

相关文章:

  • 电子电路原理第十七章(线性运算放大器电路的应用)
  • 【登录优化】redis删除旧token
  • AI测试进入智能体时代:AutoGen 、 Coze、CrewAI 谁主沉浮?
  • C++ STL map multimap 查找操作详解
  • 2025-5-26Vue3快速上手
  • Nginx location匹配模式详解
  • 解锁 MCP 中的 JSON-RPC:跨平台通信的奥秘
  • nfs下载镜像报错File lookup fail,TTTTTTTTTTTTTTT,内核 6.11.0降到5.15.0
  • JAVA面试复习知识点
  • 【沉浸式解决问题】基于泛型递归,Java中实体类基类开启MybatisPlus的ActiveRecord模式
  • PID控制学习(位置式,增量式,算法优化,多环串级PID)
  • LitCTF 2025 Robbie Wanna Revenge
  • 并发的产生及对应的解决方案之实例举证
  • Java 中经常犯的错误
  • 2025年5月26日第一轮
  • 【springboot项目部署】打包部署
  • 矩阵链乘法问题
  • vae 视频截图 复习 gans和vae的原理区别
  • JVM垃圾回收器详细介绍
  • 注解的使用和自定义
  • Composer 常规操作说明与问题处理
  • 【部署】读取制度类txt文件导入dify的父子分段知识库
  • Kubernetes 1.33您需要了解的和升级新功能
  • 爬虫学习-Scrape Center spa6 超简单 JS 逆向
  • 二叉树遍历
  • 打破壁垒:国内软件业产品与技术割裂困局及工程师产品思维重塑
  • 无网络docker镜像迁移
  • OSC协议简介、工作原理、特点、数据的接收和发送
  • 5月26日day37打卡
  • 【大模型Pre-Training实战总结】实现Qwen3增量预训练,Lora训练与合并