amap_service.py 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269
  1. """高德地图MCP服务封装"""
  2. from typing import List, Dict, Any, Optional
  3. from hello_agents.tools import MCPTool
  4. from ..config import get_settings
  5. from ..models.schemas import Location, POIInfo, WeatherInfo
  6. # 全局MCP工具实例
  7. _amap_mcp_tool = None
  8. def get_amap_mcp_tool() -> MCPTool:
  9. """
  10. 获取高德地图MCP工具实例(单例模式)
  11. Returns:
  12. MCPTool实例
  13. """
  14. global _amap_mcp_tool
  15. if _amap_mcp_tool is None:
  16. settings = get_settings()
  17. if not settings.amap_api_key:
  18. raise ValueError("高德地图API Key未配置,请在.env文件中设置AMAP_API_KEY")
  19. # 创建MCP工具
  20. _amap_mcp_tool = MCPTool(
  21. name="amap",
  22. description="高德地图服务,支持POI搜索、路线规划、天气查询等功能",
  23. server_command=["uvx", "amap-mcp-server"],
  24. env={"AMAP_MAPS_API_KEY": settings.amap_api_key},
  25. auto_expand=True # 自动展开为独立工具
  26. )
  27. print(f"✅ 高德地图MCP工具初始化成功")
  28. print(f" 工具数量: {len(_amap_mcp_tool._available_tools)}")
  29. # 打印可用工具列表
  30. if _amap_mcp_tool._available_tools:
  31. print(" 可用工具:")
  32. for tool in _amap_mcp_tool._available_tools[:5]: # 只打印前5个
  33. print(f" - {tool.get('name', 'unknown')}")
  34. if len(_amap_mcp_tool._available_tools) > 5:
  35. print(f" ... 还有 {len(_amap_mcp_tool._available_tools) - 5} 个工具")
  36. return _amap_mcp_tool
  37. class AmapService:
  38. """高德地图服务封装类"""
  39. def __init__(self):
  40. """初始化服务"""
  41. self.mcp_tool = get_amap_mcp_tool()
  42. def search_poi(self, keywords: str, city: str, citylimit: bool = True) -> List[POIInfo]:
  43. """
  44. 搜索POI
  45. Args:
  46. keywords: 搜索关键词
  47. city: 城市
  48. citylimit: 是否限制在城市范围内
  49. Returns:
  50. POI信息列表
  51. """
  52. try:
  53. # 调用MCP工具
  54. result = self.mcp_tool.run({
  55. "action": "call_tool",
  56. "tool_name": "maps_text_search",
  57. "arguments": {
  58. "keywords": keywords,
  59. "city": city,
  60. "citylimit": str(citylimit).lower()
  61. }
  62. })
  63. # 解析结果
  64. # 注意: MCP工具返回的是字符串,需要解析
  65. # 这里简化处理,实际应该解析JSON
  66. print(f"POI搜索结果: {result[:200]}...") # 打印前200字符
  67. # TODO: 解析实际的POI数据
  68. return []
  69. except Exception as e:
  70. print(f"❌ POI搜索失败: {str(e)}")
  71. return []
  72. def get_weather(self, city: str) -> List[WeatherInfo]:
  73. """
  74. 查询天气
  75. Args:
  76. city: 城市名称
  77. Returns:
  78. 天气信息列表
  79. """
  80. try:
  81. # 调用MCP工具
  82. result = self.mcp_tool.run({
  83. "action": "call_tool",
  84. "tool_name": "maps_weather",
  85. "arguments": {
  86. "city": city
  87. }
  88. })
  89. print(f"天气查询结果: {result[:200]}...")
  90. # TODO: 解析实际的天气数据
  91. return []
  92. except Exception as e:
  93. print(f"❌ 天气查询失败: {str(e)}")
  94. return []
  95. def plan_route(
  96. self,
  97. origin_address: str,
  98. destination_address: str,
  99. origin_city: Optional[str] = None,
  100. destination_city: Optional[str] = None,
  101. route_type: str = "walking"
  102. ) -> Dict[str, Any]:
  103. """
  104. 规划路线
  105. Args:
  106. origin_address: 起点地址
  107. destination_address: 终点地址
  108. origin_city: 起点城市
  109. destination_city: 终点城市
  110. route_type: 路线类型 (walking/driving/transit)
  111. Returns:
  112. 路线信息
  113. """
  114. try:
  115. # 根据路线类型选择工具
  116. tool_map = {
  117. "walking": "maps_direction_walking_by_address",
  118. "driving": "maps_direction_driving_by_address",
  119. "transit": "maps_direction_transit_integrated_by_address"
  120. }
  121. tool_name = tool_map.get(route_type, "maps_direction_walking_by_address")
  122. # 构建参数
  123. arguments = {
  124. "origin_address": origin_address,
  125. "destination_address": destination_address
  126. }
  127. # 公共交通需要城市参数
  128. if route_type == "transit":
  129. if origin_city:
  130. arguments["origin_city"] = origin_city
  131. if destination_city:
  132. arguments["destination_city"] = destination_city
  133. else:
  134. # 其他路线类型也可以提供城市参数提高准确性
  135. if origin_city:
  136. arguments["origin_city"] = origin_city
  137. if destination_city:
  138. arguments["destination_city"] = destination_city
  139. # 调用MCP工具
  140. result = self.mcp_tool.run({
  141. "action": "call_tool",
  142. "tool_name": tool_name,
  143. "arguments": arguments
  144. })
  145. print(f"路线规划结果: {result[:200]}...")
  146. # TODO: 解析实际的路线数据
  147. return {}
  148. except Exception as e:
  149. print(f"❌ 路线规划失败: {str(e)}")
  150. return {}
  151. def geocode(self, address: str, city: Optional[str] = None) -> Optional[Location]:
  152. """
  153. 地理编码(地址转坐标)
  154. Args:
  155. address: 地址
  156. city: 城市
  157. Returns:
  158. 经纬度坐标
  159. """
  160. try:
  161. arguments = {"address": address}
  162. if city:
  163. arguments["city"] = city
  164. result = self.mcp_tool.run({
  165. "action": "call_tool",
  166. "tool_name": "maps_geo",
  167. "arguments": arguments
  168. })
  169. print(f"地理编码结果: {result[:200]}...")
  170. # TODO: 解析实际的坐标数据
  171. return None
  172. except Exception as e:
  173. print(f"❌ 地理编码失败: {str(e)}")
  174. return None
  175. def get_poi_detail(self, poi_id: str) -> Dict[str, Any]:
  176. """
  177. 获取POI详情
  178. Args:
  179. poi_id: POI ID
  180. Returns:
  181. POI详情信息
  182. """
  183. try:
  184. result = self.mcp_tool.run({
  185. "action": "call_tool",
  186. "tool_name": "maps_search_detail",
  187. "arguments": {
  188. "id": poi_id
  189. }
  190. })
  191. print(f"POI详情结果: {result[:200]}...")
  192. # 解析结果并提取图片
  193. import json
  194. import re
  195. # 尝试从结果中提取JSON
  196. json_match = re.search(r'\{.*\}', result, re.DOTALL)
  197. if json_match:
  198. data = json.loads(json_match.group())
  199. return data
  200. return {"raw": result}
  201. except Exception as e:
  202. print(f"❌ 获取POI详情失败: {str(e)}")
  203. return {}
  204. # 创建全局服务实例
  205. _amap_service = None
  206. def get_amap_service() -> AmapService:
  207. """获取高德地图服务实例(单例模式)"""
  208. global _amap_service
  209. if _amap_service is None:
  210. _amap_service = AmapService()
  211. return _amap_service