search.py 9.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264
  1. """搜索工具 - HelloAgents原生搜索实现"""
  2. import os
  3. from typing import Optional, Dict, Any, List
  4. from ..base import Tool, ToolParameter
  5. class SearchTool(Tool):
  6. """
  7. 智能混合搜索工具
  8. 支持多种搜索引擎后端,智能选择最佳搜索源:
  9. 1. 混合模式 (hybrid) - 智能选择TAVILY或SERPAPI
  10. 2. Tavily API (tavily) - 专业AI搜索
  11. 3. SerpApi (serpapi) - 传统Google搜索
  12. """
  13. def __init__(self, backend: str = "hybrid", tavily_key: Optional[str] = None, serpapi_key: Optional[str] = None):
  14. super().__init__(
  15. name="search",
  16. description="一个智能网页搜索引擎。支持混合搜索模式,自动选择最佳搜索源。当你需要回答关于时事、事实以及在你的知识库中找不到的信息时,应使用此工具。"
  17. )
  18. self.backend = backend
  19. self.tavily_key = tavily_key or os.getenv("TAVILY_API_KEY")
  20. self.serpapi_key = serpapi_key or os.getenv("SERPAPI_API_KEY")
  21. self.available_backends = []
  22. self._setup_backends()
  23. def _setup_backends(self):
  24. """设置搜索后端"""
  25. # 检查Tavily可用性
  26. if self.tavily_key:
  27. try:
  28. from tavily import TavilyClient
  29. self.tavily_client = TavilyClient(api_key=self.tavily_key)
  30. self.available_backends.append("tavily")
  31. print("✅ Tavily搜索引擎已初始化")
  32. except ImportError:
  33. print("⚠️ Tavily未安装,无法使用Tavily搜索")
  34. else:
  35. print("⚠️ TAVILY_API_KEY未设置")
  36. # 检查SerpApi可用性
  37. if self.serpapi_key:
  38. try:
  39. import serpapi
  40. self.available_backends.append("serpapi")
  41. print("✅ SerpApi搜索引擎已初始化")
  42. except ImportError:
  43. print("⚠️ SerpApi未安装,无法使用SerpApi搜索")
  44. else:
  45. print("⚠️ SERPAPI_API_KEY未设置")
  46. # 确定最终使用的后端
  47. if self.backend == "hybrid":
  48. if self.available_backends:
  49. print(f"🔧 混合搜索模式已启用,可用后端: {', '.join(self.available_backends)}")
  50. else:
  51. print("⚠️ 没有可用的搜索后端,请配置API密钥")
  52. elif self.backend == "tavily" and "tavily" not in self.available_backends:
  53. print("⚠️ Tavily不可用,请检查TAVILY_API_KEY配置")
  54. elif self.backend == "serpapi" and "serpapi" not in self.available_backends:
  55. print("⚠️ SerpApi不可用,请检查SERPAPI_API_KEY配置")
  56. elif self.backend not in ["tavily", "serpapi", "hybrid"]:
  57. print("⚠️ 不支持的搜索后端,将使用hybrid模式")
  58. self.backend = "hybrid"
  59. def run(self, parameters: Dict[str, Any]) -> str:
  60. """
  61. 执行搜索
  62. Args:
  63. parameters: 包含input参数的字典
  64. Returns:
  65. 搜索结果
  66. """
  67. query = parameters.get("input", "").strip()
  68. if not query:
  69. return "错误:搜索查询不能为空"
  70. print(f"🔍 正在执行搜索: {query}")
  71. try:
  72. if self.backend == "hybrid":
  73. return self._search_hybrid(query)
  74. elif self.backend == "tavily":
  75. if "tavily" not in self.available_backends:
  76. return self._get_api_config_message()
  77. return self._search_tavily(query)
  78. elif self.backend == "serpapi":
  79. if "serpapi" not in self.available_backends:
  80. return self._get_api_config_message()
  81. return self._search_serpapi(query)
  82. else:
  83. return self._get_api_config_message()
  84. except Exception as e:
  85. return f"搜索时发生错误: {str(e)}"
  86. def _search_hybrid(self, query: str) -> str:
  87. """混合搜索 - 智能选择最佳搜索源"""
  88. # 检查是否有可用的搜索源
  89. if not self.available_backends:
  90. return self._get_api_config_message()
  91. # 优先使用Tavily(AI优化的搜索)
  92. if "tavily" in self.available_backends:
  93. try:
  94. print("🎯 使用Tavily进行AI优化搜索")
  95. return self._search_tavily(query)
  96. except Exception as e:
  97. print(f"⚠️ Tavily搜索失败: {e}")
  98. # 如果Tavily失败,尝试SerpApi
  99. if "serpapi" in self.available_backends:
  100. print("🔄 切换到SerpApi搜索")
  101. return self._search_serpapi(query)
  102. # 如果Tavily不可用,使用SerpApi
  103. elif "serpapi" in self.available_backends:
  104. try:
  105. print("🎯 使用SerpApi进行Google搜索")
  106. return self._search_serpapi(query)
  107. except Exception as e:
  108. print(f"⚠️ SerpApi搜索失败: {e}")
  109. # 如果都失败了,返回API配置提示
  110. return "❌ 所有搜索源都失败了,请检查网络连接和API密钥配置"
  111. def _search_tavily(self, query: str) -> str:
  112. """使用Tavily搜索"""
  113. response = self.tavily_client.search(
  114. query=query,
  115. search_depth="basic",
  116. include_answer=True,
  117. max_results=3
  118. )
  119. result = f"🎯 Tavily AI搜索结果:{response.get('answer', '未找到直接答案')}\n\n"
  120. for i, item in enumerate(response.get('results', [])[:3], 1):
  121. result += f"[{i}] {item.get('title', '')}\n"
  122. result += f" {item.get('content', '')[:200]}...\n"
  123. result += f" 来源: {item.get('url', '')}\n\n"
  124. return result
  125. def _search_serpapi(self, query: str) -> str:
  126. """使用SerpApi搜索"""
  127. try:
  128. from serpapi import SerpApiClient
  129. except ImportError:
  130. return "错误:SerpApi未安装,请运行 pip install serpapi"
  131. params = {
  132. "engine": "google",
  133. "q": query,
  134. "api_key": self.serpapi_key,
  135. "gl": "cn",
  136. "hl": "zh-cn",
  137. }
  138. client = SerpApiClient(params)
  139. results = client.get_dict()
  140. result_text = "🔍 SerpApi Google搜索结果:\n\n"
  141. # 智能解析:优先寻找最直接的答案
  142. if "answer_box" in results and "answer" in results["answer_box"]:
  143. result_text += f"💡 直接答案:{results['answer_box']['answer']}\n\n"
  144. if "knowledge_graph" in results and "description" in results["knowledge_graph"]:
  145. result_text += f"📖 知识图谱:{results['knowledge_graph']['description']}\n\n"
  146. if "organic_results" in results and results["organic_results"]:
  147. result_text += "🔗 相关结果:\n"
  148. for i, res in enumerate(results["organic_results"][:3], 1):
  149. result_text += f"[{i}] {res.get('title', '')}\n"
  150. result_text += f" {res.get('snippet', '')}\n"
  151. result_text += f" 来源: {res.get('link', '')}\n\n"
  152. return result_text
  153. return f"对不起,没有找到关于 '{query}' 的信息。"
  154. def _get_api_config_message(self) -> str:
  155. """获取API配置提示信息"""
  156. tavily_key = os.getenv("TAVILY_API_KEY")
  157. serpapi_key = os.getenv("SERPAPI_API_KEY")
  158. message = "❌ 没有可用的搜索源,请检查以下配置:\n\n"
  159. # 检查Tavily
  160. message += "1. Tavily API:\n"
  161. if not tavily_key:
  162. message += " ❌ 环境变量 TAVILY_API_KEY 未设置\n"
  163. message += " 📝 获取地址: https://tavily.com/\n"
  164. else:
  165. try:
  166. import tavily
  167. message += " ✅ API密钥已配置,包已安装\n"
  168. except ImportError:
  169. message += " ❌ API密钥已配置,但需要安装包: pip install tavily-python\n"
  170. message += "\n"
  171. # 检查SerpAPI
  172. message += "2. SerpAPI:\n"
  173. if not serpapi_key:
  174. message += " ❌ 环境变量 SERPAPI_API_KEY 未设置\n"
  175. message += " 📝 获取地址: https://serpapi.com/\n"
  176. else:
  177. try:
  178. import serpapi
  179. message += " ✅ API密钥已配置,包已安装\n"
  180. except ImportError:
  181. message += " ❌ API密钥已配置,但需要安装包: pip install google-search-results\n"
  182. message += "\n配置方法:\n"
  183. message += "- 在.env文件中添加: TAVILY_API_KEY=your_key_here\n"
  184. message += "- 或在环境变量中设置: export TAVILY_API_KEY=your_key_here\n"
  185. message += "\n配置后重新运行程序。"
  186. return message
  187. def get_parameters(self) -> List[ToolParameter]:
  188. """获取工具参数定义"""
  189. return [
  190. ToolParameter(
  191. name="input",
  192. type="string",
  193. description="搜索查询关键词",
  194. required=True
  195. )
  196. ]
  197. # 便捷函数
  198. def search(query: str, backend: str = "hybrid") -> str:
  199. """
  200. 便捷的搜索函数
  201. Args:
  202. query: 搜索查询关键词
  203. backend: 搜索后端 ("hybrid", "tavily", "serpapi")
  204. Returns:
  205. 搜索结果
  206. """
  207. tool = SearchTool(backend=backend)
  208. return tool.run({"input": query})
  209. # 专用搜索函数
  210. def search_tavily(query: str) -> str:
  211. """使用Tavily进行AI优化搜索"""
  212. tool = SearchTool(backend="tavily")
  213. return tool.run({"input": query})
  214. def search_serpapi(query: str) -> str:
  215. """使用SerpApi进行Google搜索"""
  216. tool = SearchTool(backend="serpapi")
  217. return tool.run({"input": query})
  218. def search_hybrid(query: str) -> str:
  219. """智能混合搜索,自动选择最佳搜索源"""
  220. tool = SearchTool(backend="hybrid")
  221. return tool.run({"input": query})