在人工智能技术快速发展的今天,基于大语言模型(LLM)的智能体(Agent)正在改变我们与数字世界的交互方式。这个项目将带您从零开始构建一个实用的智能旅行助手Agent,它能根据用户请求自动查询目的地天气,并基于天气状况推荐合适的旅游景点。
这个智能体的核心工作流程模拟了人类解决问题的思维过程:
与传统程序不同,这个Agent的核心优势在于其决策能力 - 它能够自主判断何时需要获取天气信息、何时需要搜索景点推荐,以及如何将不同来源的信息整合成连贯的回答。这种动态决策能力正是现代AI Agent区别于传统脚本程序的关键特征。
构建AI Agent需要准备以下基础环境:
创建虚拟环境的操作步骤:
bash复制# 创建项目目录
mkdir hello-agents && cd hello-agents
# 创建虚拟环境
python -m venv venv
# 激活虚拟环境
# Windows:
venv\Scripts\activate
# macOS/Linux:
source venv/bin/activate
提示:虚拟环境是Python开发的最佳实践。它能为每个项目创建独立的Python运行环境,避免不同项目间的依赖冲突。所有安装的包都会被隔离在该环境内,不会影响系统全局的Python环境。
本项目需要配置两类API服务:
我们使用AIHubmix提供的兼容OpenAI接口的LLM服务:
coding-glm-4.7-free用于景点推荐功能:
安装项目所需Python包:
bash复制pip install requests>=2.31.0 tavily-python>=0.3.0 openai>=1.0.0 python-dotenv>=1.0.0
配置环境变量:
.env文件ini复制# Tavily API配置
TAVILY_API_KEY=your_tavily_api_key
# LLM API配置
OPENAI_API_KEY=your_aihubmix_api_key
OPENAI_BASE_URL=https://aihubmix.com/v1
MODEL_NAME=coding-glm-4.7-free
注意:
.env文件应添加到.gitignore中,避免敏感信息泄露。环境变量的使用既方便管理配置,又能提高代码安全性。
系统提示词是指导LLM行为的关键,我们的旅行助手使用以下结构化提示:
python复制AGENT_SYSTEM_PROMPT = """
你是一个智能旅行助手。你的任务是分析用户的请求,并使用可用工具一步步地解决问题。
# 可用工具:
- `get_weather(city: str)`: 查询指定城市的实时天气。
- `get_attraction(city: str, weather: str)`: 根据城市和天气搜索推荐的旅游景点。
# 输出格式要求:
你的每次回复必须严格遵循以下格式,包含一对Thought和Action:
Thought: [你的思考过程和下一步计划]
Action: [你要执行的具体行动]
Action的格式必须是以下之一:
1. 调用工具:function_name(arg_name="arg_value")
2. 结束任务:Finish[最终答案]
# 重要提示:
- 每次只输出一对Thought-Action
- Action必须在同一行,不要换行
- 当收集到足够信息可以回答用户问题时,必须使用 Action: Finish[最终答案] 格式结束
"""
这个提示词的设计考虑了以下几个关键点:
python复制import requests
def get_weather(city: str) -> str:
"""
通过wttr.in API查询城市天气
:param city: 城市名称
:return: 格式化天气信息或错误消息
"""
url = f"https://wttr.in/{city}?format=j1"
try:
response = requests.get(url)
response.raise_for_status()
data = response.json()
current_condition = data['current_condition'][0]
weather_desc = current_condition['weatherDesc'][0]['value']
temp_c = current_condition['temp_C']
return f"{city}当前天气:{weather_desc},气温{temp_c}摄氏度"
except requests.exceptions.RequestException as e:
return f"错误:查询天气时遇到网络问题 - {e}"
except (KeyError, IndexError) as e:
return f"错误:解析天气数据失败,可能是城市名称无效 - {e}"
关键实现细节:
wttr.in提供的免费天气APIformat=j1指定返回JSON格式技巧:开发API集成工具时,建议先直接在浏览器中测试API端点(如
https://wttr.in/Beijing?format=j1),了解响应结构后再编写解析代码。
python复制from tavily import TavilyClient
import os
def get_attraction(city: str, weather: str) -> str:
"""
使用Tavily搜索API获取景点推荐
:param city: 城市名称
:param weather: 天气状况
:return: 景点推荐信息或错误消息
"""
api_key = os.environ.get("TAVILY_API_KEY")
if not api_key:
return "错误:未配置TAVILY_API_KEY。"
tavily = TavilyClient(api_key=api_key)
query = f"'{city}' 在'{weather}'天气下最值得去的旅游景点推荐及理由"
try:
response = tavily.search(query=query, search_depth="basic", include_answer=True)
if response.get("answer"):
return response["answer"]
formatted_results = []
for result in response.get("results", []):
formatted_results.append(f"- {result['title']}: {result['content']}")
return "根据搜索,为您找到以下信息:\n" + "\n".join(formatted_results)
except Exception as e:
return f"错误:执行Tavily搜索时出现问题 - {e}"
实现要点:
answer字段)python复制from openai import OpenAI
class OpenAICompatibleClient:
"""
兼容OpenAI接口的LLM客户端封装
"""
def __init__(self, model: str, api_key: str, base_url: str):
self.model = model
self.client = OpenAI(api_key=api_key, base_url=base_url)
def generate(self, prompt: str, system_prompt: str) -> str:
"""调用LLM生成响应"""
print("正在调用大语言模型...")
try:
messages = [
{'role': 'system', 'content': system_prompt},
{'role': 'user', 'content': prompt}
]
response = self.client.chat.completions.create(
model=self.model,
messages=messages,
stream=False
)
answer = response.choices[0].message.content
print("大语言模型响应成功。")
return answer
except Exception as e:
print(f"调用LLM API时发生错误: {e}")
return "错误:调用语言模型服务时出错。"
设计考虑:
Agent的主循环实现了"思考-行动-观察"的经典模式:
python复制import re
from dotenv import load_dotenv
# 初始化配置
load_dotenv()
llm = OpenAICompatibleClient(
model=os.getenv("MODEL_NAME"),
api_key=os.getenv("OPENAI_API_KEY"),
base_url=os.getenv("OPENAI_BASE_URL")
)
# 工具注册表
available_tools = {
"get_weather": get_weather,
"get_attraction": get_attraction,
}
def run_agent(user_prompt: str, max_iterations=5):
"""
运行Agent主循环
:param user_prompt: 用户初始请求
:param max_iterations: 最大循环次数
:return: 最终答案或None
"""
prompt_history = [f"用户请求: {user_prompt}"]
print(f"用户输入: {user_prompt}\n" + "="*40)
for i in range(max_iterations):
print(f"--- 循环 {i+1} ---\n")
# 1. 构建完整Prompt
full_prompt = "\n".join(prompt_history)
# 2. 调用LLM生成响应
llm_output = llm.generate(full_prompt, AGENT_SYSTEM_PROMPT)
# 3. 解析并执行Action
action_str = parse_action(llm_output)
if not action_str:
continue
if action_str.startswith("Finish"):
return extract_final_answer(action_str)
tool_name, kwargs = parse_tool_call(action_str)
if tool_name in available_tools:
observation = available_tools[tool_name](**kwargs)
record_observation(prompt_history, observation)
python复制def parse_action(llm_output: str) -> str:
"""
解析LLM输出中的Action部分
:param llm_output: LLM原始输出
:return: 解析出的Action字符串或None
"""
# 截取第一个有效的Thought-Action对
match = re.search(r'(Thought:.*?Action:.*?)(?=\n\s*(?:Thought:|Action:|Observation:)|\Z)',
llm_output, re.DOTALL)
if match:
truncated = match.group(1).strip()
if truncated != llm_output.strip():
llm_output = truncated
print("已截断多余的 Thought-Action 对")
# 提取Action部分
action_match = re.search(r"Action: (.*)", llm_output, re.DOTALL)
if not action_match:
observation = "错误: 未能解析到 Action 字段。请确保你的回复严格遵循格式要求。"
record_observation(prompt_history, observation)
return None
return action_match.group(1).strip()
python复制def parse_tool_call(action_str: str) -> tuple:
"""
解析工具调用指令
:param action_str: Action字符串
:return: (工具名, 参数字典) 元组
"""
tool_name = re.search(r"(\w+)\(", action_str).group(1)
args_str = re.search(r"\((.*)\)", action_str).group(1)
kwargs = dict(re.findall(r'(\w+)="([^"]*)"', args_str))
return tool_name, kwargs
python复制def record_observation(prompt_history: list, observation: str):
"""
记录观察结果到历史
:param prompt_history: 历史记录列表
:param observation: 观察结果
"""
observation_str = f"Observation: {observation}"
print(f"{observation_str}\n" + "="*40)
prompt_history.append(observation_str)
启动Agent并处理用户请求:
python复制if __name__ == "__main__":
user_request = "你好,请帮我查询一下今天广州的天气,然后根据天气推荐一个合适的旅游景点。"
final_answer = run_agent(user_request)
if final_answer:
print(f"\n最终答案:\n{final_answer}")
典型输出流程:
code复制用户输入: 你好,请帮我查询一下今天广州的天气,然后根据天气推荐一个合适的旅游景点。
========================================
--- 循环 1 ---
正在调用大语言模型...
大语言模型响应成功。
模型输出:
Thought: 用户要求查询广州今天的天气,并根据天气推荐旅游景点。我需要先查询广州的天气情况。
Action: get_weather(city="广州")
Observation: 广州当前天气:Partly cloudy,气温28摄氏度
========================================
--- 循环 2 ---
正在调用大语言模型...
大语言模型响应成功。
模型输出:
Thought: 已经获取广州天气为部分多云,气温28度。现在需要根据这个天气推荐合适的景点。
Action: get_attraction(city="广州", weather="Partly cloudy")
Observation: 广州在部分多云天气下推荐游览:1. 广州塔 - 云层变化能带来独特的城市景观;2. 白云山 - 适中的温度适合登山;3. 沙面岛 - 在舒适天气下欣赏历史建筑
========================================
--- 循环 3 ---
正在调用大语言模型...
大语言模型响应成功。
模型输出:
Thought: 已收集足够信息,可以给用户完整答复。
Action: Finish[今天广州的天气是部分多云,气温28摄氏度。推荐景点:
1. 广州塔 - 云层变化能带来独特的城市景观
2. 白云山 - 适中的温度适合登山
3. 沙面岛 - 在舒适天气下欣赏历史建筑
祝您游玩愉快!]
最终答案:
今天广州的天气是部分多云,气温28摄氏度。推荐景点:
1. 广州塔 - 云层变化能带来独特的城市景观
2. 白云山 - 适中的温度适合登山
3. 沙面岛 - 在舒适天气下欣赏历史建筑
祝您游玩愉快!
LLM输出格式不符合预期
API调用失败
响应解析错误
并行工具调用:当多个工具调用没有依赖关系时,可以使用异步方式并行执行
缓存机制:对天气数据等变化不频繁的信息实现缓存,减少API调用
超时控制:为每个工具调用设置合理的超时时间,避免长时间等待
限流处理:实现简单的速率限制,防止超过API调用配额
多工具集成:增加交通查询、酒店推荐、餐厅推荐等工具
记忆功能:实现对话历史记忆,支持多轮交互
个性化推荐:基于用户历史偏好提供个性化建议
可视化界面:开发Web或移动端界面,提升用户体验
自动化测试:构建测试用例确保核心功能的稳定性
本项目的智能体采用了经典的ReAct(Reasoning and Acting)架构,其核心思想是:
这种架构模拟了人类解决问题的方式,通过迭代的"思考-行动-观察"循环逐步接近问题解决方案。
工具调用是本项目的关键技术点,其实现涉及以下核心概念:
这种设计使得Agent能够灵活扩展 - 只需在工具注册表中添加新工具,并在系统提示词中说明,LLM就能学会在适当的情境下使用新工具。
本项目的提示词设计采用了以下有效策略:
在实际开发中,提示词往往需要多次迭代优化才能达到理想效果。建议使用专门的提示词测试工具进行验证和调优。