|
@@ -0,0 +1,124 @@
|
|
|
|
+import json
|
|
|
|
+from typing import Any
|
|
|
|
+
|
|
|
|
+import requests
|
|
|
|
+
|
|
|
|
+from core.tools.entities.tool_entities import ToolInvokeMessage
|
|
|
|
+from core.tools.tool.builtin_tool import BuiltinTool
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+class SearXNGSearchResults(dict):
|
|
|
|
+ """Wrapper for search results."""
|
|
|
|
+
|
|
|
|
+ def __init__(self, data: str):
|
|
|
|
+ super().__init__(json.loads(data))
|
|
|
|
+ self.__dict__ = self
|
|
|
|
+
|
|
|
|
+ @property
|
|
|
|
+ def results(self) -> Any:
|
|
|
|
+ return self.get("results", [])
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+class SearXNGSearchTool(BuiltinTool):
|
|
|
|
+ """
|
|
|
|
+ Tool for performing a search using SearXNG engine.
|
|
|
|
+ """
|
|
|
|
+
|
|
|
|
+ SEARCH_TYPE = {
|
|
|
|
+ "page": "general",
|
|
|
|
+ "news": "news",
|
|
|
|
+ "image": "images",
|
|
|
|
+ # "video": "videos",
|
|
|
|
+ # "file": "files"
|
|
|
|
+ }
|
|
|
|
+ LINK_FILED = {
|
|
|
|
+ "page": "url",
|
|
|
|
+ "news": "url",
|
|
|
|
+ "image": "img_src",
|
|
|
|
+ # "video": "iframe_src",
|
|
|
|
+ # "file": "magnetlink"
|
|
|
|
+ }
|
|
|
|
+ TEXT_FILED = {
|
|
|
|
+ "page": "content",
|
|
|
|
+ "news": "content",
|
|
|
|
+ "image": "img_src",
|
|
|
|
+ # "video": "iframe_src",
|
|
|
|
+ # "file": "magnetlink"
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ def _invoke_query(self, user_id: str, host: str, query: str, search_type: str, result_type: str, topK: int = 5) -> list[dict]:
|
|
|
|
+ """Run query and return the results."""
|
|
|
|
+
|
|
|
|
+ search_type = search_type.lower()
|
|
|
|
+ if search_type not in self.SEARCH_TYPE.keys():
|
|
|
|
+ search_type= "page"
|
|
|
|
+
|
|
|
|
+ response = requests.get(host, params={
|
|
|
|
+ "q": query,
|
|
|
|
+ "format": "json",
|
|
|
|
+ "categories": self.SEARCH_TYPE[search_type]
|
|
|
|
+ })
|
|
|
|
+
|
|
|
|
+ if response.status_code != 200:
|
|
|
|
+ raise Exception(f'Error {response.status_code}: {response.text}')
|
|
|
|
+
|
|
|
|
+ search_results = SearXNGSearchResults(response.text).results[:topK]
|
|
|
|
+
|
|
|
|
+ if result_type == 'link':
|
|
|
|
+ results = []
|
|
|
|
+ if search_type == "page" or search_type == "news":
|
|
|
|
+ for r in search_results:
|
|
|
|
+ results.append(self.create_text_message(
|
|
|
|
+ text=f'{r["title"]}: {r.get(self.LINK_FILED[search_type], "")}'
|
|
|
|
+ ))
|
|
|
|
+ elif search_type == "image":
|
|
|
|
+ for r in search_results:
|
|
|
|
+ results.append(self.create_image_message(
|
|
|
|
+ image=r.get(self.LINK_FILED[search_type], "")
|
|
|
|
+ ))
|
|
|
|
+ else:
|
|
|
|
+ for r in search_results:
|
|
|
|
+ results.append(self.create_link_message(
|
|
|
|
+ link=r.get(self.LINK_FILED[search_type], "")
|
|
|
|
+ ))
|
|
|
|
+
|
|
|
|
+ return results
|
|
|
|
+ else:
|
|
|
|
+ text = ''
|
|
|
|
+ for i, r in enumerate(search_results):
|
|
|
|
+ text += f'{i+1}: {r["title"]} - {r.get(self.TEXT_FILED[search_type], "")}\n'
|
|
|
|
+
|
|
|
|
+ return self.create_text_message(text=self.summary(user_id=user_id, content=text))
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+ def _invoke(self, user_id: str, tool_parameters: dict[str, Any]) -> ToolInvokeMessage | list[ToolInvokeMessage]:
|
|
|
|
+ """
|
|
|
|
+ Invoke the SearXNG search tool.
|
|
|
|
+
|
|
|
|
+ Args:
|
|
|
|
+ user_id (str): The ID of the user invoking the tool.
|
|
|
|
+ tool_parameters (dict[str, Any]): The parameters for the tool invocation.
|
|
|
|
+
|
|
|
|
+ Returns:
|
|
|
|
+ ToolInvokeMessage | list[ToolInvokeMessage]: The result of the tool invocation.
|
|
|
|
+ """
|
|
|
|
+
|
|
|
|
+ host = self.runtime.credentials.get('searxng_base_url', None)
|
|
|
|
+ if not host:
|
|
|
|
+ raise Exception('SearXNG api is required')
|
|
|
|
+
|
|
|
|
+ query = tool_parameters.get('query', None)
|
|
|
|
+ if not query:
|
|
|
|
+ return self.create_text_message('Please input query')
|
|
|
|
+
|
|
|
|
+ num_results = min(tool_parameters.get('num_results', 5), 20)
|
|
|
|
+ search_type = tool_parameters.get('search_type', 'page') or 'page'
|
|
|
|
+ result_type = tool_parameters.get('result_type', 'text') or 'text'
|
|
|
|
+
|
|
|
|
+ return self._invoke_query(
|
|
|
|
+ user_id=user_id,
|
|
|
|
+ host=host,
|
|
|
|
+ query=query,
|
|
|
|
+ search_type=search_type,
|
|
|
|
+ result_type=result_type,
|
|
|
|
+ topK=num_results)
|