trip_planner_agent.py 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429
  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. # 创建景点搜索Agent
  149. print(" - 创建景点搜索Agent...")
  150. self.attraction_agent = SimpleAgent(
  151. name="景点搜索专家",
  152. llm=self.llm,
  153. system_prompt=ATTRACTION_AGENT_PROMPT
  154. )
  155. self.attraction_agent.add_tool(self.amap_tool)
  156. # 创建天气查询Agent
  157. print(" - 创建天气查询Agent...")
  158. self.weather_agent = SimpleAgent(
  159. name="天气查询专家",
  160. llm=self.llm,
  161. system_prompt=WEATHER_AGENT_PROMPT
  162. )
  163. self.weather_agent.add_tool(self.amap_tool)
  164. # 创建酒店推荐Agent
  165. print(" - 创建酒店推荐Agent...")
  166. self.hotel_agent = SimpleAgent(
  167. name="酒店推荐专家",
  168. llm=self.llm,
  169. system_prompt=HOTEL_AGENT_PROMPT
  170. )
  171. self.hotel_agent.add_tool(self.amap_tool)
  172. # 创建行程规划Agent(不需要工具)
  173. print(" - 创建行程规划Agent...")
  174. self.planner_agent = SimpleAgent(
  175. name="行程规划专家",
  176. llm=self.llm,
  177. system_prompt=PLANNER_AGENT_PROMPT
  178. )
  179. print(f"✅ 多智能体系统初始化成功")
  180. print(f" 景点搜索Agent: {len(self.attraction_agent.list_tools())} 个工具")
  181. print(f" 天气查询Agent: {len(self.weather_agent.list_tools())} 个工具")
  182. print(f" 酒店推荐Agent: {len(self.hotel_agent.list_tools())} 个工具")
  183. except Exception as e:
  184. print(f"❌ 多智能体系统初始化失败: {str(e)}")
  185. import traceback
  186. traceback.print_exc()
  187. raise
  188. def plan_trip(self, request: TripRequest) -> TripPlan:
  189. """
  190. 使用多智能体协作生成旅行计划
  191. Args:
  192. request: 旅行请求
  193. Returns:
  194. 旅行计划
  195. """
  196. try:
  197. print(f"\n{'='*60}")
  198. print(f"🚀 开始多智能体协作规划旅行...")
  199. print(f"目的地: {request.city}")
  200. print(f"日期: {request.start_date} 至 {request.end_date}")
  201. print(f"天数: {request.travel_days}天")
  202. print(f"偏好: {', '.join(request.preferences) if request.preferences else '无'}")
  203. print(f"{'='*60}\n")
  204. # 步骤1: 景点搜索Agent搜索景点
  205. print("📍 步骤1: 搜索景点...")
  206. attraction_query = self._build_attraction_query(request)
  207. attraction_response = self.attraction_agent.run(attraction_query)
  208. print(f"景点搜索结果: {attraction_response[:200]}...\n")
  209. # 步骤2: 天气查询Agent查询天气
  210. print("🌤️ 步骤2: 查询天气...")
  211. weather_query = f"请查询{request.city}的天气信息"
  212. weather_response = self.weather_agent.run(weather_query)
  213. print(f"天气查询结果: {weather_response[:200]}...\n")
  214. # 步骤3: 酒店推荐Agent搜索酒店
  215. print("🏨 步骤3: 搜索酒店...")
  216. hotel_query = f"请搜索{request.city}的{request.accommodation}酒店"
  217. hotel_response = self.hotel_agent.run(hotel_query)
  218. print(f"酒店搜索结果: {hotel_response[:200]}...\n")
  219. # 步骤4: 行程规划Agent整合信息生成计划
  220. print("📋 步骤4: 生成行程计划...")
  221. planner_query = self._build_planner_query(request, attraction_response, weather_response, hotel_response)
  222. planner_response = self.planner_agent.run(planner_query)
  223. print(f"行程规划结果: {planner_response[:300]}...\n")
  224. # 解析最终计划
  225. trip_plan = self._parse_response(planner_response, request)
  226. print(f"{'='*60}")
  227. print(f"✅ 旅行计划生成完成!")
  228. print(f"{'='*60}\n")
  229. return trip_plan
  230. except Exception as e:
  231. print(f"❌ 生成旅行计划失败: {str(e)}")
  232. import traceback
  233. traceback.print_exc()
  234. return self._create_fallback_plan(request)
  235. def _build_attraction_query(self, request: TripRequest) -> str:
  236. """构建景点搜索查询 - 直接包含工具调用"""
  237. keywords = []
  238. if request.preferences:
  239. # 只取第一个偏好作为关键词
  240. keywords = request.preferences[0]
  241. else:
  242. keywords = "景点"
  243. # 直接返回工具调用格式
  244. query = f"请使用amap_maps_text_search工具搜索{request.city}的{keywords}相关景点。\n[TOOL_CALL:amap_maps_text_search:keywords={keywords},city={request.city}]"
  245. return query
  246. def _build_planner_query(self, request: TripRequest, attractions: str, weather: str, hotels: str = "") -> str:
  247. """构建行程规划查询"""
  248. query = f"""请根据以下信息生成{request.city}的{request.travel_days}天旅行计划:
  249. **基本信息:**
  250. - 城市: {request.city}
  251. - 日期: {request.start_date} 至 {request.end_date}
  252. - 天数: {request.travel_days}天
  253. - 交通方式: {request.transportation}
  254. - 住宿: {request.accommodation}
  255. - 偏好: {', '.join(request.preferences) if request.preferences else '无'}
  256. **景点信息:**
  257. {attractions}
  258. **天气信息:**
  259. {weather}
  260. **酒店信息:**
  261. {hotels}
  262. **要求:**
  263. 1. 每天安排2-3个景点
  264. 2. 每天必须包含早中晚三餐
  265. 3. 每天推荐一个具体的酒店(从酒店信息中选择)
  266. 3. 考虑景点之间的距离和交通方式
  267. 4. 返回完整的JSON格式数据
  268. 5. 景点的经纬度坐标要真实准确
  269. """
  270. if request.free_text_input:
  271. query += f"\n**额外要求:** {request.free_text_input}"
  272. return query
  273. def _parse_response(self, response: str, request: TripRequest) -> TripPlan:
  274. """
  275. 解析Agent响应
  276. Args:
  277. response: Agent响应文本
  278. request: 原始请求
  279. Returns:
  280. 旅行计划
  281. """
  282. try:
  283. # 尝试从响应中提取JSON
  284. # 查找JSON代码块
  285. if "```json" in response:
  286. json_start = response.find("```json") + 7
  287. json_end = response.find("```", json_start)
  288. json_str = response[json_start:json_end].strip()
  289. elif "```" in response:
  290. json_start = response.find("```") + 3
  291. json_end = response.find("```", json_start)
  292. json_str = response[json_start:json_end].strip()
  293. elif "{" in response and "}" in response:
  294. # 直接查找JSON对象
  295. json_start = response.find("{")
  296. json_end = response.rfind("}") + 1
  297. json_str = response[json_start:json_end]
  298. else:
  299. raise ValueError("响应中未找到JSON数据")
  300. # 解析JSON
  301. data = json.loads(json_str)
  302. # 转换为TripPlan对象
  303. trip_plan = TripPlan(**data)
  304. return trip_plan
  305. except Exception as e:
  306. print(f"⚠️ 解析响应失败: {str(e)}")
  307. print(f" 将使用备用方案生成计划")
  308. return self._create_fallback_plan(request)
  309. def _create_fallback_plan(self, request: TripRequest) -> TripPlan:
  310. """创建备用计划(当Agent失败时)"""
  311. from datetime import datetime, timedelta
  312. # 解析日期
  313. start_date = datetime.strptime(request.start_date, "%Y-%m-%d")
  314. # 创建每日行程
  315. days = []
  316. for i in range(request.travel_days):
  317. current_date = start_date + timedelta(days=i)
  318. day_plan = DayPlan(
  319. date=current_date.strftime("%Y-%m-%d"),
  320. day_index=i,
  321. description=f"第{i+1}天行程",
  322. transportation=request.transportation,
  323. accommodation=request.accommodation,
  324. attractions=[
  325. Attraction(
  326. name=f"{request.city}景点{j+1}",
  327. address=f"{request.city}市",
  328. location=Location(longitude=116.4 + i*0.01 + j*0.005, latitude=39.9 + i*0.01 + j*0.005),
  329. visit_duration=120,
  330. description=f"这是{request.city}的著名景点",
  331. category="景点"
  332. )
  333. for j in range(2)
  334. ],
  335. meals=[
  336. Meal(type="breakfast", name=f"第{i+1}天早餐", description="当地特色早餐"),
  337. Meal(type="lunch", name=f"第{i+1}天午餐", description="午餐推荐"),
  338. Meal(type="dinner", name=f"第{i+1}天晚餐", description="晚餐推荐")
  339. ]
  340. )
  341. days.append(day_plan)
  342. return TripPlan(
  343. city=request.city,
  344. start_date=request.start_date,
  345. end_date=request.end_date,
  346. days=days,
  347. weather_info=[],
  348. overall_suggestions=f"这是为您规划的{request.city}{request.travel_days}日游行程,建议提前查看各景点的开放时间。"
  349. )
  350. # 全局多智能体系统实例
  351. _multi_agent_planner = None
  352. def get_trip_planner_agent() -> MultiAgentTripPlanner:
  353. """获取多智能体旅行规划系统实例(单例模式)"""
  354. global _multi_agent_planner
  355. if _multi_agent_planner is None:
  356. _multi_agent_planner = MultiAgentTripPlanner()
  357. return _multi_agent_planner