trip_planner_agent.py 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430
  1. """多智能体旅行规划系统"""
  2. import json
  3. from typing import Dict, Any, List
  4. from hello_agents import SimpleAgent
  5. from hello_agents.tools import MCPTool
  6. from ..services.llm_service import get_llm
  7. from ..models.schemas import TripRequest, TripPlan, DayPlan, Attraction, Meal, WeatherInfo, Location, Hotel
  8. from ..config import get_settings
  9. # ============ Agent提示词 ============
  10. ATTRACTION_AGENT_PROMPT = """你是景点搜索专家。你的任务是根据城市和用户偏好搜索合适的景点。
  11. **重要提示:**
  12. 你必须使用工具来搜索景点!不要自己编造景点信息!
  13. **工具调用格式:**
  14. 使用maps_text_search工具时,必须严格按照以下格式:
  15. `[TOOL_CALL:amap_maps_text_search:keywords=景点关键词,city=城市名]`
  16. **示例:**
  17. 用户: "搜索北京的历史文化景点"
  18. 你的回复: [TOOL_CALL:amap_maps_text_search:keywords=历史文化,city=北京]
  19. 用户: "搜索上海的公园"
  20. 你的回复: [TOOL_CALL:amap_maps_text_search:keywords=公园,city=上海]
  21. **注意:**
  22. 1. 必须使用工具,不要直接回答
  23. 2. 格式必须完全正确,包括方括号和冒号
  24. 3. 参数用逗号分隔
  25. """
  26. WEATHER_AGENT_PROMPT = """你是天气查询专家。你的任务是查询指定城市的天气信息。
  27. **重要提示:**
  28. 你必须使用工具来查询天气!不要自己编造天气信息!
  29. **工具调用格式:**
  30. 使用maps_weather工具时,必须严格按照以下格式:
  31. `[TOOL_CALL:amap_maps_weather:city=城市名]`
  32. **示例:**
  33. 用户: "查询北京天气"
  34. 你的回复: [TOOL_CALL:amap_maps_weather:city=北京]
  35. 用户: "上海的天气怎么样"
  36. 你的回复: [TOOL_CALL:amap_maps_weather:city=上海]
  37. **注意:**
  38. 1. 必须使用工具,不要直接回答
  39. 2. 格式必须完全正确,包括方括号和冒号
  40. """
  41. HOTEL_AGENT_PROMPT = """你是酒店推荐专家。你的任务是根据城市和景点位置推荐合适的酒店。
  42. **重要提示:**
  43. 你必须使用工具来搜索酒店!不要自己编造酒店信息!
  44. **工具调用格式:**
  45. 使用maps_text_search工具搜索酒店时,必须严格按照以下格式:
  46. `[TOOL_CALL:amap_maps_text_search:keywords=酒店,city=城市名]`
  47. **示例:**
  48. 用户: "搜索北京的酒店"
  49. 你的回复: [TOOL_CALL:amap_maps_text_search:keywords=酒店,city=北京]
  50. **注意:**
  51. 1. 必须使用工具,不要直接回答
  52. 2. 格式必须完全正确,包括方括号和冒号
  53. 3. 关键词使用"酒店"或"宾馆"
  54. """
  55. PLANNER_AGENT_PROMPT = """你是行程规划专家。你的任务是根据景点信息和天气信息,生成详细的旅行计划。
  56. 请严格按照以下JSON格式返回旅行计划:
  57. ```json
  58. {
  59. "city": "城市名称",
  60. "start_date": "YYYY-MM-DD",
  61. "end_date": "YYYY-MM-DD",
  62. "days": [
  63. {
  64. "date": "YYYY-MM-DD",
  65. "day_index": 0,
  66. "description": "第1天行程概述",
  67. "transportation": "交通方式",
  68. "accommodation": "住宿类型",
  69. "hotel": {
  70. "name": "酒店名称",
  71. "address": "酒店地址",
  72. "location": {"longitude": 116.397128, "latitude": 39.916527},
  73. "price_range": "300-500元",
  74. "rating": "4.5",
  75. "distance": "距离景点2公里",
  76. "type": "经济型酒店",
  77. "estimated_cost": 400
  78. },
  79. "attractions": [
  80. {
  81. "name": "景点名称",
  82. "address": "详细地址",
  83. "location": {"longitude": 116.397128, "latitude": 39.916527},
  84. "visit_duration": 120,
  85. "description": "景点详细描述",
  86. "category": "景点类别",
  87. "ticket_price": 60
  88. }
  89. ],
  90. "meals": [
  91. {"type": "breakfast", "name": "早餐推荐", "description": "早餐描述", "estimated_cost": 30},
  92. {"type": "lunch", "name": "午餐推荐", "description": "午餐描述", "estimated_cost": 50},
  93. {"type": "dinner", "name": "晚餐推荐", "description": "晚餐描述", "estimated_cost": 80}
  94. ]
  95. }
  96. ],
  97. "weather_info": [
  98. {
  99. "date": "YYYY-MM-DD",
  100. "day_weather": "晴",
  101. "night_weather": "多云",
  102. "day_temp": 25,
  103. "night_temp": 15,
  104. "wind_direction": "南风",
  105. "wind_power": "1-3级"
  106. }
  107. ],
  108. "overall_suggestions": "总体建议",
  109. "budget": {
  110. "total_attractions": 180,
  111. "total_hotels": 1200,
  112. "total_meals": 480,
  113. "total_transportation": 200,
  114. "total": 2060
  115. }
  116. }
  117. ```
  118. **重要提示:**
  119. 1. weather_info数组必须包含每一天的天气信息
  120. 2. 温度必须是纯数字(不要带°C等单位)
  121. 3. 每天安排2-3个景点
  122. 4. 考虑景点之间的距离和游览时间
  123. 5. 每天必须包含早中晚三餐
  124. 6. 提供实用的旅行建议
  125. 7. **必须包含预算信息**:
  126. - 景点门票价格(ticket_price)
  127. - 餐饮预估费用(estimated_cost)
  128. - 酒店预估费用(estimated_cost)
  129. - 预算汇总(budget)包含各项总费用
  130. """
  131. class MultiAgentTripPlanner:
  132. """多智能体旅行规划系统"""
  133. def __init__(self):
  134. """初始化多智能体系统"""
  135. print("🔄 开始初始化多智能体旅行规划系统...")
  136. try:
  137. settings = get_settings()
  138. self.llm = get_llm()
  139. # 创建共享的MCP工具(只创建一次)
  140. print(" - 创建共享MCP工具...")
  141. self.amap_tool = MCPTool(
  142. name="amap",
  143. description="高德地图服务",
  144. server_command=["uvx", "amap-mcp-server"],
  145. env={"AMAP_MAPS_API_KEY": settings.amap_api_key},
  146. auto_expand=True
  147. )
  148. self.amap_tool.expandable=True
  149. # 创建景点搜索Agent
  150. print(" - 创建景点搜索Agent...")
  151. self.attraction_agent = SimpleAgent(
  152. name="景点搜索专家",
  153. llm=self.llm,
  154. system_prompt=ATTRACTION_AGENT_PROMPT
  155. )
  156. self.attraction_agent.add_tool(self.amap_tool)
  157. # 创建天气查询Agent
  158. print(" - 创建天气查询Agent...")
  159. self.weather_agent = SimpleAgent(
  160. name="天气查询专家",
  161. llm=self.llm,
  162. system_prompt=WEATHER_AGENT_PROMPT
  163. )
  164. self.weather_agent.add_tool(self.amap_tool)
  165. # 创建酒店推荐Agent
  166. print(" - 创建酒店推荐Agent...")
  167. self.hotel_agent = SimpleAgent(
  168. name="酒店推荐专家",
  169. llm=self.llm,
  170. system_prompt=HOTEL_AGENT_PROMPT
  171. )
  172. self.hotel_agent.add_tool(self.amap_tool)
  173. # 创建行程规划Agent(不需要工具)
  174. print(" - 创建行程规划Agent...")
  175. self.planner_agent = SimpleAgent(
  176. name="行程规划专家",
  177. llm=self.llm,
  178. system_prompt=PLANNER_AGENT_PROMPT
  179. )
  180. print(f"✅ 多智能体系统初始化成功")
  181. print(f" 景点搜索Agent: {len(self.attraction_agent.list_tools())} 个工具")
  182. print(f" 天气查询Agent: {len(self.weather_agent.list_tools())} 个工具")
  183. print(f" 酒店推荐Agent: {len(self.hotel_agent.list_tools())} 个工具")
  184. except Exception as e:
  185. print(f"❌ 多智能体系统初始化失败: {str(e)}")
  186. import traceback
  187. traceback.print_exc()
  188. raise
  189. def plan_trip(self, request: TripRequest) -> TripPlan:
  190. """
  191. 使用多智能体协作生成旅行计划
  192. Args:
  193. request: 旅行请求
  194. Returns:
  195. 旅行计划
  196. """
  197. try:
  198. print(f"\n{'='*60}")
  199. print(f"🚀 开始多智能体协作规划旅行...")
  200. print(f"目的地: {request.city}")
  201. print(f"日期: {request.start_date} 至 {request.end_date}")
  202. print(f"天数: {request.travel_days}天")
  203. print(f"偏好: {', '.join(request.preferences) if request.preferences else '无'}")
  204. print(f"{'='*60}\n")
  205. # 步骤1: 景点搜索Agent搜索景点
  206. print("📍 步骤1: 搜索景点...")
  207. attraction_query = self._build_attraction_query(request)
  208. attraction_response = self.attraction_agent.run(attraction_query)
  209. print(f"景点搜索结果: {attraction_response[:200]}...\n")
  210. # 步骤2: 天气查询Agent查询天气
  211. print("🌤️ 步骤2: 查询天气...")
  212. weather_query = f"请查询{request.city}的天气信息"
  213. weather_response = self.weather_agent.run(weather_query)
  214. print(f"天气查询结果: {weather_response[:200]}...\n")
  215. # 步骤3: 酒店推荐Agent搜索酒店
  216. print("🏨 步骤3: 搜索酒店...")
  217. hotel_query = f"请搜索{request.city}的{request.accommodation}酒店"
  218. hotel_response = self.hotel_agent.run(hotel_query)
  219. print(f"酒店搜索结果: {hotel_response[:200]}...\n")
  220. # 步骤4: 行程规划Agent整合信息生成计划
  221. print("📋 步骤4: 生成行程计划...")
  222. planner_query = self._build_planner_query(request, attraction_response, weather_response, hotel_response)
  223. planner_response = self.planner_agent.run(planner_query)
  224. print(f"行程规划结果: {planner_response[:300]}...\n")
  225. # 解析最终计划
  226. trip_plan = self._parse_response(planner_response, request)
  227. print(f"{'='*60}")
  228. print(f"✅ 旅行计划生成完成!")
  229. print(f"{'='*60}\n")
  230. return trip_plan
  231. except Exception as e:
  232. print(f"❌ 生成旅行计划失败: {str(e)}")
  233. import traceback
  234. traceback.print_exc()
  235. return self._create_fallback_plan(request)
  236. def _build_attraction_query(self, request: TripRequest) -> str:
  237. """构建景点搜索查询 - 直接包含工具调用"""
  238. keywords = []
  239. if request.preferences:
  240. # 只取第一个偏好作为关键词
  241. keywords = request.preferences[0]
  242. else:
  243. keywords = "景点"
  244. # 直接返回工具调用格式
  245. query = f"请使用amap_maps_text_search工具搜索{request.city}的{keywords}相关景点。\n[TOOL_CALL:amap_maps_text_search:keywords={keywords},city={request.city}]"
  246. return query
  247. def _build_planner_query(self, request: TripRequest, attractions: str, weather: str, hotels: str = "") -> str:
  248. """构建行程规划查询"""
  249. query = f"""请根据以下信息生成{request.city}的{request.travel_days}天旅行计划:
  250. **基本信息:**
  251. - 城市: {request.city}
  252. - 日期: {request.start_date} 至 {request.end_date}
  253. - 天数: {request.travel_days}天
  254. - 交通方式: {request.transportation}
  255. - 住宿: {request.accommodation}
  256. - 偏好: {', '.join(request.preferences) if request.preferences else '无'}
  257. **景点信息:**
  258. {attractions}
  259. **天气信息:**
  260. {weather}
  261. **酒店信息:**
  262. {hotels}
  263. **要求:**
  264. 1. 每天安排2-3个景点
  265. 2. 每天必须包含早中晚三餐
  266. 3. 每天推荐一个具体的酒店(从酒店信息中选择)
  267. 3. 考虑景点之间的距离和交通方式
  268. 4. 返回完整的JSON格式数据
  269. 5. 景点的经纬度坐标要真实准确
  270. """
  271. if request.free_text_input:
  272. query += f"\n**额外要求:** {request.free_text_input}"
  273. return query
  274. def _parse_response(self, response: str, request: TripRequest) -> TripPlan:
  275. """
  276. 解析Agent响应
  277. Args:
  278. response: Agent响应文本
  279. request: 原始请求
  280. Returns:
  281. 旅行计划
  282. """
  283. try:
  284. # 尝试从响应中提取JSON
  285. # 查找JSON代码块
  286. if "```json" in response:
  287. json_start = response.find("```json") + 7
  288. json_end = response.find("```", json_start)
  289. json_str = response[json_start:json_end].strip()
  290. elif "```" in response:
  291. json_start = response.find("```") + 3
  292. json_end = response.find("```", json_start)
  293. json_str = response[json_start:json_end].strip()
  294. elif "{" in response and "}" in response:
  295. # 直接查找JSON对象
  296. json_start = response.find("{")
  297. json_end = response.rfind("}") + 1
  298. json_str = response[json_start:json_end]
  299. else:
  300. raise ValueError("响应中未找到JSON数据")
  301. # 解析JSON
  302. data = json.loads(json_str)
  303. # 转换为TripPlan对象
  304. trip_plan = TripPlan(**data)
  305. return trip_plan
  306. except Exception as e:
  307. print(f"⚠️ 解析响应失败: {str(e)}")
  308. print(f" 将使用备用方案生成计划")
  309. return self._create_fallback_plan(request)
  310. def _create_fallback_plan(self, request: TripRequest) -> TripPlan:
  311. """创建备用计划(当Agent失败时)"""
  312. from datetime import datetime, timedelta
  313. # 解析日期
  314. start_date = datetime.strptime(request.start_date, "%Y-%m-%d")
  315. # 创建每日行程
  316. days = []
  317. for i in range(request.travel_days):
  318. current_date = start_date + timedelta(days=i)
  319. day_plan = DayPlan(
  320. date=current_date.strftime("%Y-%m-%d"),
  321. day_index=i,
  322. description=f"第{i+1}天行程",
  323. transportation=request.transportation,
  324. accommodation=request.accommodation,
  325. attractions=[
  326. Attraction(
  327. name=f"{request.city}景点{j+1}",
  328. address=f"{request.city}市",
  329. location=Location(longitude=116.4 + i*0.01 + j*0.005, latitude=39.9 + i*0.01 + j*0.005),
  330. visit_duration=120,
  331. description=f"这是{request.city}的著名景点",
  332. category="景点"
  333. )
  334. for j in range(2)
  335. ],
  336. meals=[
  337. Meal(type="breakfast", name=f"第{i+1}天早餐", description="当地特色早餐"),
  338. Meal(type="lunch", name=f"第{i+1}天午餐", description="午餐推荐"),
  339. Meal(type="dinner", name=f"第{i+1}天晚餐", description="晚餐推荐")
  340. ]
  341. )
  342. days.append(day_plan)
  343. return TripPlan(
  344. city=request.city,
  345. start_date=request.start_date,
  346. end_date=request.end_date,
  347. days=days,
  348. weather_info=[],
  349. overall_suggestions=f"这是为您规划的{request.city}{request.travel_days}日游行程,建议提前查看各景点的开放时间。"
  350. )
  351. # 全局多智能体系统实例
  352. _multi_agent_planner = None
  353. def get_trip_planner_agent() -> MultiAgentTripPlanner:
  354. """获取多智能体旅行规划系统实例(单例模式)"""
  355. global _multi_agent_planner
  356. if _multi_agent_planner is None:
  357. _multi_agent_planner = MultiAgentTripPlanner()
  358. return _multi_agent_planner