本文为《React Agent:从零开始构建 AI 智能体》专栏系列文章。 专栏地址:https://blog.csdn.net/suiyingy/category_12933485.html。项目地址:https://gitee.com/fgai/react-agent(含完整代码示例与实战源)。完整介绍:https://blog.csdn.net/suiyingy/article/details/146983582。
在智能助手日益普及的今天,天气查询是用户高频使用的基础功能。传统天气查询工具往往需要用户明确输入地点和日期,交互流程较为机械。而基于大语言模型(LLM)的智能天气查询系统,能够通过自然语言对话理解用户意图,自动处理模糊查询(如“明天天气”),并结合工具调用获取实时数据,提供更人性化的交互体验。本节示例旨在构建一个具备多轮对话能力、工具协同调用的天气查询智能体,实现以下核心功能:
(1)自然语言理解:支持用户以口语化方式提问,如“上海明天热吗”“后天会下雨吗”,系统能自动解析地点和时间参数。
(2)工具协同调用:通过大语言模型动态决定是否调用日期解析工具和天气查询工具,实现“先确定日期、再查询天气”的逻辑闭环。
(3)对话历史管理:维护多轮对话上下文,支持用户在历史对话基础上追问(如“那后天呢”),提升交互连贯性。
(4)错误处理机制:对缺失地点、无效日期等情况进行友好提示,确保系统鲁棒性。
系统采用三层架构设计,各模块通过标准化接口交互:LLM 接口层(LLM Class)封装大语言模型的 API 调用,支持工具调用格式解析(如 function call),并提供统一的响应生成接口;对话管理层(MemoryManager)负责维护对话历史队列和数据缓存,以此实现多轮对话的上下文传递;工具执行层(WeatherAgent)则定义具体工具(日期解析、天气查询)的逻辑,用于处理工具调用请求并返回结构化数据,从而驱动大语言模型生成最终回答。
LLM(Large Language Model)模块承担着生成智能对话的核心功能,它在接收用户输入后,会结合历史对话记录及可用工具列表生成恰当响应;以天气查询场景为例,该模块需具备解析用户意图的能力,例如识别用户是否在询问天气或是否需要查询特定日期的天气等,同时能根据查询内容和所需工具生成对应的天气查询响应,还可通过保持对话上下文来处理多轮对话,确保在多轮交互中生成的回答与之前的对话逻辑一致。
class LLM:
def __init__(self):
api_key = '<用户的 API Key>'
base_url = 'https://dashscope.aliyuncs.com/compatible-mode/v1'
self.model = 'qwen-turbo-latest'
self.client = openai.OpenAI(api_key=api_key, base_url=base_url)
def generate_response(self, messages, tools=None, tool_choice=None):
try:
response = self.client.chat.completions.create(
model=self.model,
messages=messages,
tools=tools,
tool_choice=tool_choice,
temperature=0.5,
max_tokens=2000
)
return response.choices[0].message
except Exception as e:
return f'生成响应时出错:{str(e)}'
内存管理模块负责管理系统的对话历史记录。它利用队列数据结构确保历史记录不超过设定的最大长度(例如5条记录),从而避免内存的过度占用。每次用户输入时,新的对话记录会被加入历史队列,并且可以随时回溯以提供更准确的多轮对话支持。
class MemoryManager:
def __init__(self, max_history=5):
self.history = deque(maxlen=max_history) # 固定长度的对话历史队列
self.data_cache = {} # 数据集缓存字典
def add_history(self, data):
self.history.append(data) # 添加对话记录
def get_history(self):
'''获取对话历史'''
return list(self.history)
天气查询智能体模块是整个系统的核心部分,负责根据用户的查询调用相关工具(如获取日期、获取天气信息等)。模块实现通过初始化定义了大模型与记忆模块,然后通过系统提示词明确角色与任务。提示词设计要点包括明确角色定位,即设定为能够使用工具获取实时天气信息的天气查询助手;定义处理流程,如用户询问天气时先调用工具获取数据再回答、可能需要多次选择工具等;设置默认值,若用户未明确日期则默认查询当天天气;规范响应格式,要求回答包含地点、日期和天气信息;处理边界情况,如用户未提供地点时提醒输入地点,对非天气问题保持回答简洁。
def __init__(self):
self.llm = LLM()
self.memory = MemoryManager()
self.system_prompt = {
'role': 'system',
'content': '''你是一个天气查询助手,能够使用工具获取实时天气信息。请按以下规则处理:
1. 当用户询问天气时,先调用工具获取数据再回答
2. 若用户未明确日期,默认查询当天天气
3. 回答需包含地点、日期和天气信息
4. 如果没有地点,需要提醒用户输入地点
5.可能需要多次选择工具
6. 非天气问题请保持回答简洁'''
}
初始化过程中还定义了日期与查询天气工具。工具定义规范要求对工具的参数进行严格约束以保障调用准确性:参数类型需严格定义,如 get_date 和get_weather 工具中 date、location 等参数均明确标注为 string 类型;通过枚举值限定输入范围,例如 get_date 工具的 date 参数通过 enum 限定只能输入今天、明天、后天;实施必填字段验证,利用 required 数组明确关键参数,如 get_date 必须传入 date,get_weather 必须包含 location 和 date;描述信息需清晰准确,每个参数均配备 description 说明用途,如 location 标注为“城市名称,如:北京、上海”;同时实现参数格式标准化,如 get_weather 的 date 参数规定为 yyyy-mm-dd 格式,通过上述规范确保工具定义的规范性和可操作性。
self.tools = [
{
'type': 'function',
'function': {
'name': 'get_date',
'description': '获取具体查询日期',
'parameters': {
'type': 'object',
'properties': {
'date': {
'type': 'string',
'description': '输入日期',
'enum': ['今天', '明天', '后天']
}
},
'required': ['date']
}
}
},
{
'type': 'function',
'function': {
'name': 'get_weather',
'description': '查询指定地点的实时天气',
'parameters': {
'type': 'object',
'properties': {
'location': {
'type': 'string',
'description': '城市名称,如:北京、上海'
},
'date': {
'type': 'string',
'description': '查询日期,格式为yyyy-mm-dd'
}
},
'required': ['location', 'date']
}
}
}
]
工具调用模块负责根据用户的请求,执行相应的外部工具功能,如获取当前日期、查询指定城市的天气等。工具调用的结果被传回给 LLM 模块,并根据需要生成后续响应。在当前实现中,天气信息是通过模拟数据提供的,但在实际应用中,这一部分可以与实际的天气 API(如OpenWeather、WeatherAPI 等)集成。
def _handle_tool_call(self, tool_call):
'''处理工具调用请求'''
func_name = tool_call.function.name
args = eval(tool_call.function.arguments)
if func_name == 'get_date':
return self._get_date(args)
elif func_name == 'get_weather':
return self._get_weather(args)
return None
def _get_date(self, params):
# 获取今天的日期
today = datetime.now()
today_str = today.strftime('%Y-%m-%d')
# 获取明天的日期
tomorrow = today + timedelta(days=1)
tomorrow_str = tomorrow.strftime('%Y-%m-%d')
# 获取后天的日期
day_after_tomorrow = today + timedelta(days=2)
after_tomorrow_str = day_after_tomorrow.strftime('%Y-%m-%d')
date_str = params.get('date', '今天')
if date_str == '明天':
return {'date': tomorrow_str}
elif date_str == '后天':
return {'date': after_tomorrow_str}
return {'date': today_str}
def _get_weather(self, params):
'''天气查询工具实现'''
# 模拟数据,也可以替换成其他天气查询的 API 接口
# 获取今天的日期
today = datetime.now()
today_str = today.strftime('%Y-%m-%d')
# 获取明天的日期
tomorrow = today + timedelta(days=1)
tomorrow_str = tomorrow.strftime('%Y-%m-%d')
# 获取后天的日期
day_after_tomorrow = today + timedelta(days=2)
after_tomorrow_str = day_after_tomorrow.strftime('%Y-%m-%d')
weather_infos = {
'北京': {today_str: 25, tomorrow_str: 26, after_tomorrow_str: 24},
'上海': {today_str: 26, tomorrow_str: 27, after_tomorrow_str: 28},
'广州': {today_str: 27, tomorrow_str: 28, after_tomorrow_str: 29},
}
location = params.get('location', 'default')
if location not in weather_infos.keys():
return {'temp': '未查询到当前地区天气,当前仅支持北京、上海、广州的天气查询。'}
date = params.get('date', 'default')
if date not in [today_str, tomorrow_str, after_tomorrow_str]:
return {'temp': '未查询到当前时间段天气,当前仅支持今天、明天、后天的天气查询。'}
return {'temp': weather_infos[location][date]}
查询处理主流程 process_query 是天气查询智能体的核心逻辑,用于处理用户的查询请求。首先,它会构建消息历史,将系统提示和内存中存储的对话历史合并,再加入当前用户的查询消息,同时把用户查询添加到内存的历史记录里。接着,调用大语言模型生成响应,并检查响应中是否包含工具调用信息。若不包含,就将响应内容添加到内存历史记录并返回结果。若包含,便进入循环处理。在循环中,先将工具调用请求添加到消息列表,接着遍历每个工具调用,调用内部方法处理并获取工具响应结果,再把结果添加到消息列表。之后根据更新后的消息列表再次调用大语言模型生成响应。循环会持续进行,直到响应中不再包含工具调用信息,或者迭代次数超过 3 次。若响应不再包含工具调用信息,就将响应内容添加到内存历史记录并返回;若迭代次数超过 3 次,为防止死循环,会直接调用大语言模型生成响应,将其添加到内存历史记录后返回。
查询处理主流程 process_query 的程序如下,流程如下图所示。
def process_query(self, query):
'''处理用户查询的完整流程'''
# 构建消息历史
messages = [self.system_prompt] + self.memory.get_history()
messages.append({'role': 'user', 'content': query})
self.memory.add_history({'role': 'user', 'content': query})
# 第一轮调用:获取工具调用请求
# 调用大语言模型(LLM)生成响应,传入消息列表和可用工具列表
response = self.llm.generate_response(messages, tools=self.tools)
# 检查响应中是否包含工具调用信息
if response.tool_calls is None:
# 如果不包含工具调用信息,将响应内容添加到内存的历史记录中,并返回结果
self.memory.add_history({'role': 'assistant', 'content': response.content})
return response.content
# 初始化迭代次数
n_iter = 0
# 大模型可能多次调用工具
# 当响应中包含工具调用信息时,进入循环处理
while response.tool_calls is not None:
n_iter = n_iter + 1
# 保存工具调用请求
# 将工具调用请求以字典形式添加到消息列表中
messages.append({
'role': 'assistant',
'content': '',
'tool_calls': response.tool_calls
})
# 处理工具调用
# 遍历响应中的每个工具调用请求
for i in range(len(response.tool_calls)):
print('调用工具与参数:', response.tool_calls[i].function.name, response.tool_calls[i].function.arguments)
# 调用内部方法处理工具调用,并获取工具的响应结果
tool_response = self._handle_tool_call(response.tool_calls[i])
print('工具响应结果:', tool_response)
# 将工具的响应结果以字典形式添加到消息列表中
messages.append({
'role': 'tool',
'content': json.dumps(tool_response),
'tool_call_id': response.tool_calls[i].id
})
# 根据再次调用工具结果再次调用大语言模型生成响应,传入更新后的消息列表和可用工具列表
response = self.llm.generate_response(messages, tools=self.tools)
# print(messages)
# print(response)
# 如果响应中不再包含工具调用信息,则保存历史并返回当前结果
if response.tool_calls is None:
self.memory.add_history({'role': 'assistant', 'content': response.content})
return response.content
# 防止因反复调用工具陷入死循环
# 最多连续调用3次,超出后不再调用工具,保存历史并返回当前结果
if n_iter > 3:
response = self.llm.generate_response(messages)
self.memory.add_history({'role': 'assistant', 'content': response.content})
return response.content
图1 查询处理主流程 process_query
天气查询智能体完整程序如下:
import json
import openai
from collections import deque
from datetime import datetime, timedelta
class LLM:
def __init__(self):
api_key = '<用户的 API Key>'
base_url = 'https://dashscope.aliyuncs.com/compatible-mode/v1'
self.model = 'qwen-turbo-latest'
self.client = openai.OpenAI(api_key=api_key, base_url=base_url)
def generate_response(self, messages, tools=None, tool_choice=None):
try:
response = self.client.chat.completions.create(
model=self.model,
messages=messages,
tools=tools,
tool_choice=tool_choice,
temperature=0.5,
max_tokens=2000
)
return response.choices[0].message
except Exception as e:
return f'生成响应时出错:{str(e)}'
class MemoryManager:
def __init__(self, max_history=5):
self.history = deque(maxlen=max_history) # 固定长度的对话历史队列
self.data_cache = {} # 数据集缓存字典
def add_history(self, data):
self.history.append(data) # 添加对话记录
def get_history(self):
'''获取对话历史'''
return list(self.history)
class WeatherAgent:
'''天气查询智能体'''
def __init__(self):
self.llm = LLM()
self.memory = MemoryManager()
self.system_prompt = {
'role': 'system',
'content': '''你是一个天气查询助手,能够使用工具获取实时天气信息。请按以下规则处理:
1. 当用户询问天气时,先调用工具获取数据再回答
2. 若用户未明确日期,默认查询当天天气
3. 回答需包含地点、日期和天气信息
4. 如果没有地点,需要提醒用户输入地点
5.可能需要多次选择工具
6. 非天气问题请保持回答简洁'''
}
self.tools = [
{
'type': 'function',
'function': {
'name': 'get_date',
'description': '获取具体查询日期',
'parameters': {
'type': 'object',
'properties': {
'date': {
'type': 'string',
'description': '输入日期',
'enum': ['今天', '明天', '后天']
}
},
'required': ['date']
}
}
},
{
'type': 'function',
'function': {
'name': 'get_weather',
'description': '查询指定地点的实时天气',
'parameters': {
'type': 'object',
'properties': {
'location': {
'type': 'string',
'description': '城市名称,如:北京、上海'
},
'date': {
'type': 'string',
'description': '查询日期,格式为yyyy-mm-dd'
}
},
'required': ['location', 'date']
}
}
}
]
def _handle_tool_call(self, tool_call):
'''处理工具调用请求'''
func_name = tool_call.function.name
args = eval(tool_call.function.arguments)
if func_name == 'get_date':
return self._get_date(args)
elif func_name == 'get_weather':
return self._get_weather(args)
return None
def _get_date(self, params):
# 获取今天的日期
today = datetime.now()
today_str = today.strftime('%Y-%m-%d')
# 获取明天的日期
tomorrow = today + timedelta(days=1)
tomorrow_str = tomorrow.strftime('%Y-%m-%d')
# 获取后天的日期
day_after_tomorrow = today + timedelta(days=2)
after_tomorrow_str = day_after_tomorrow.strftime('%Y-%m-%d')
date_str = params.get('date', '今天')
if date_str == '明天':
return {'date': tomorrow_str}
elif date_str == '后天':
return {'date': after_tomorrow_str}
return {'date': today_str}
def _get_weather(self, params):
'''天气查询工具实现'''
# 模拟数据,也可以替换成其他天气查询的 API 接口
# 获取今天的日期
today = datetime.now()
today_str = today.strftime('%Y-%m-%d')
# 获取明天的日期
tomorrow = today + timedelta(days=1)
tomorrow_str = tomorrow.strftime('%Y-%m-%d')
# 获取后天的日期
day_after_tomorrow = today + timedelta(days=2)
after_tomorrow_str = day_after_tomorrow.strftime('%Y-%m-%d')
weather_infos = {
'北京': {today_str: 25, tomorrow_str: 26, after_tomorrow_str: 24},
'上海': {today_str: 26, tomorrow_str: 27, after_tomorrow_str: 28},
'广州': {today_str: 27, tomorrow_str: 28, after_tomorrow_str: 29},
}
location = params.get('location', 'default')
if location not in weather_infos.keys():
return {'temp': '未查询到当前地区天气,当前仅支持北京、上海、广州的天气查询。'}
date = params.get('date', 'default')
if date not in [today_str, tomorrow_str, after_tomorrow_str]:
return {'temp': '未查询到当前时间段天气,当前仅支持今天、明天、后天的天气查询。'}
return {'temp': weather_infos[location][date]}
def process_query(self, query):
'''处理用户查询的完整流程'''
# 构建消息历史
messages = [self.system_prompt] + self.memory.get_history()
messages.append({'role': 'user', 'content': query})
self.memory.add_history({'role': 'user', 'content': query})
# 第一轮调用:获取工具调用请求
# 调用大语言模型(LLM)生成响应,传入消息列表和可用工具列表
response = self.llm.generate_response(messages, tools=self.tools)
# 检查响应中是否包含工具调用信息
if response.tool_calls is None:
# 如果不包含工具调用信息,将响应内容添加到内存的历史记录中,并返回结果
self.memory.add_history({'role': 'assistant', 'content': response.content})
return response.content
# 初始化迭代次数
n_iter = 0
# 大模型可能多次调用工具
# 当响应中包含工具调用信息时,进入循环处理
while response.tool_calls is not None:
n_iter = n_iter + 1
# 保存工具调用请求
# 将工具调用请求以字典形式添加到消息列表中
messages.append({
'role': 'assistant',
'content': '',
'tool_calls': response.tool_calls
})
# 处理工具调用
# 遍历响应中的每个工具调用请求
for i in range(len(response.tool_calls)):
print('调用工具与参数:', response.tool_calls[i].function.name, response.tool_calls[i].function.arguments)
# 调用内部方法处理工具调用,并获取工具的响应结果
tool_response = self._handle_tool_call(response.tool_calls[i])
print('工具响应结果:', tool_response)
# 将工具的响应结果以字典形式添加到消息列表中
messages.append({
'role': 'tool',
'content': json.dumps(tool_response),
'tool_call_id': response.tool_calls[i].id
})
# 根据再次调用工具结果再次调用大语言模型生成响应,传入更新后的消息列表和可用工具列表
response = self.llm.generate_response(messages, tools=self.tools)
# print(messages)
# print(response)
# 如果响应中不再包含工具调用信息,则保存历史并返回当前结果
if response.tool_calls is None:
self.memory.add_history({'role': 'assistant', 'content': response.content})
return response.content
# 防止因反复调用工具陷入死循环
# 最多连续调用3次,超出后不再调用工具,保存历史并返回当前结果
if n_iter > 3:
response = self.llm.generate_response(messages)
self.memory.add_history({'role': 'assistant', 'content': response.content})
return response.content
# 大模型回复测试
def llm_test():
llm = LLM()
messages = [{'role': 'user', 'content': '你好'}]
print(llm.generate_response(messages))
# 模拟数据测试
def weather_data_test():
# 获取今天的日期
today = datetime.now()
today_str = today.strftime('%Y-%m-%d')
# 获取明天的日期
tomorrow = today + timedelta(days=1)
tomorrow_str = tomorrow.strftime('%Y-%m-%d')
# 获取后天的日期
day_after_tomorrow = today + timedelta(days=2)
after_tomorrow_str = day_after_tomorrow.strftime('%Y-%m-%d')
weather_infos = {
'北京': {today_str: 25, tomorrow_str: 26, after_tomorrow_str: 24},
'上海': {today_str: 26, tomorrow_str: 27, after_tomorrow_str: 28},
'广州': {today_str: 27, tomorrow_str: 28, after_tomorrow_str: 29},
}
print(f'天气模拟数据\n{weather_infos}')
# 测试多轮对话
def main():
'''主运行程序'''
agent = WeatherAgent()
queries = [
'上海的天气怎么样?',
'那明天呢?',
'后天北京的气温多少?'
]
for query in queries:
print(f'[用户] {query}')
response = agent.process_query(query)
print(f'[助手] {response}')
print('-' * 60)
if __name__ == '__main__':
# 测试大模型
# llm_test()
# 模拟数据测试
weather_data_test()
# 多轮对话
print('进入天气查询智能体多轮对话:\n')
main()
完整运行结果如下:
天气模拟数据
{'北京': {'2025-04-29': 25, '2025-04-30': 26, '2025-05-01': 24}, '上海': {'2025-04-29': 26, '2025-04-30': 27, '2025-05-01': 28}, '广州': {'2025-04-29': 27, '2025-04-30': 28, '2025-05-01': 29}}
进入天气查询智能体多轮对话:
[用户] 上海的天气怎么样?
调用工具与参数: get_date {"date": "今天"}
工具响应结果: {'date': '2025-04-29'}
调用工具与参数: get_weather {"date": "2025-04-29", "location": "上海"}
工具响应结果: {'temp': 26}
[助手] 上海今天的天气温度是26摄氏度。
------------------------------------------------------------
[用户] 那明天呢?
调用工具与参数: get_date {"date": "明天"}
工具响应结果: {'date': '2025-04-30'}
调用工具与参数: get_weather {"date": "2025-04-30", "location": "上海"}
工具响应结果: {'temp': 27}
[助手] 上海明天的天气温度是27摄氏度。
------------------------------------------------------------
[用户] 后天北京的气温多少?
调用工具与参数: get_date {"date": "后天"}
工具响应结果: {'date': '2025-05-01'}
调用工具与参数: get_weather {"date": "2025-05-01", "location": "北京"}
工具响应结果: {'temp': 24}
[助手] 北京后天的气温是24摄氏度。
图2 天气查询智能体运行结果
立即关注获取最新动态
点击订阅《React Agent 开发专栏》,每周获取智能体开发深度教程。项目代码持续更新至React Agent 开源仓库,欢迎 Star 获取实时更新通知!FGAI 人工智能平台:FGAI 人工智能平台 FGAI 人工智能平台