|  | @@ -1,39 +1,20 @@
 | 
	
		
			
				|  |  | -import os
 | 
	
		
			
				|  |  | -import sys
 | 
	
		
			
				|  |  |  from typing import Any, Union
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -from serpapi import GoogleSearch
 | 
	
		
			
				|  |  | +import requests
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  from core.tools.entities.tool_entities import ToolInvokeMessage
 | 
	
		
			
				|  |  |  from core.tools.tool.builtin_tool import BuiltinTool
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -class HiddenPrints:
 | 
	
		
			
				|  |  | -    """Context manager to hide prints."""
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -    def __enter__(self) -> None:
 | 
	
		
			
				|  |  | -        """Open file to pipe stdout to."""
 | 
	
		
			
				|  |  | -        self._original_stdout = sys.stdout
 | 
	
		
			
				|  |  | -        sys.stdout = open(os.devnull, "w")
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -    def __exit__(self, *_: Any) -> None:
 | 
	
		
			
				|  |  | -        """Close file that stdout was piped to."""
 | 
	
		
			
				|  |  | -        sys.stdout.close()
 | 
	
		
			
				|  |  | -        sys.stdout = self._original_stdout
 | 
	
		
			
				|  |  | +SERP_API_URL = "https://serpapi.com/search"
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  class SerpAPI:
 | 
	
		
			
				|  |  |      """
 | 
	
		
			
				|  |  |      SerpAPI tool provider.
 | 
	
		
			
				|  |  |      """
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -    search_engine: Any  #: :meta private:
 | 
	
		
			
				|  |  | -    serpapi_api_key: str = None
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  |      def __init__(self, api_key: str) -> None:
 | 
	
		
			
				|  |  |          """Initialize SerpAPI tool provider."""
 | 
	
		
			
				|  |  |          self.serpapi_api_key = api_key
 | 
	
		
			
				|  |  | -        self.search_engine = GoogleSearch
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |      def run(self, query: str, **kwargs: Any) -> str:
 | 
	
		
			
				|  |  |          """Run query through SerpAPI and parse result."""
 | 
	
	
		
			
				|  | @@ -43,117 +24,76 @@ class SerpAPI:
 | 
	
		
			
				|  |  |      def results(self, query: str) -> dict:
 | 
	
		
			
				|  |  |          """Run query through SerpAPI and return the raw result."""
 | 
	
		
			
				|  |  |          params = self.get_params(query)
 | 
	
		
			
				|  |  | -        with HiddenPrints():
 | 
	
		
			
				|  |  | -            search = self.search_engine(params)
 | 
	
		
			
				|  |  | -            res = search.get_dict()
 | 
	
		
			
				|  |  | -        return res
 | 
	
		
			
				|  |  | +        response = requests.get(url=SERP_API_URL, params=params)
 | 
	
		
			
				|  |  | +        response.raise_for_status()
 | 
	
		
			
				|  |  | +        return response.json()
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |      def get_params(self, query: str) -> dict[str, str]:
 | 
	
		
			
				|  |  |          """Get parameters for SerpAPI."""
 | 
	
		
			
				|  |  | -        _params = {
 | 
	
		
			
				|  |  | +        params = {
 | 
	
		
			
				|  |  |              "api_key": self.serpapi_api_key,
 | 
	
		
			
				|  |  |              "q": query,
 | 
	
		
			
				|  |  | -        }
 | 
	
		
			
				|  |  | -        params = {
 | 
	
		
			
				|  |  |              "engine": "google",
 | 
	
		
			
				|  |  |              "google_domain": "google.com",
 | 
	
		
			
				|  |  |              "gl": "us",
 | 
	
		
			
				|  |  | -            "hl": "en",
 | 
	
		
			
				|  |  | -            **_params
 | 
	
		
			
				|  |  | +            "hl": "en"
 | 
	
		
			
				|  |  |          }
 | 
	
		
			
				|  |  |          return params
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |      @staticmethod
 | 
	
		
			
				|  |  |      def _process_response(res: dict, typ: str) -> str:
 | 
	
		
			
				|  |  | -        """Process response from SerpAPI."""
 | 
	
		
			
				|  |  | -        if "error" in res.keys():
 | 
	
		
			
				|  |  | +        """
 | 
	
		
			
				|  |  | +        Process response from SerpAPI.
 | 
	
		
			
				|  |  | +        SerpAPI doc: https://serpapi.com/search-api
 | 
	
		
			
				|  |  | +        Google search main results are called organic results
 | 
	
		
			
				|  |  | +        """
 | 
	
		
			
				|  |  | +        if "error" in res:
 | 
	
		
			
				|  |  |              raise ValueError(f"Got error from SerpAPI: {res['error']}")
 | 
	
		
			
				|  |  | -        
 | 
	
		
			
				|  |  | +        toret = ""
 | 
	
		
			
				|  |  |          if typ == "text":
 | 
	
		
			
				|  |  | -            toret = ""
 | 
	
		
			
				|  |  | -            if "answer_box" in res.keys() and type(res["answer_box"]) == list:
 | 
	
		
			
				|  |  | -                res["answer_box"] = res["answer_box"][0] + "\n"
 | 
	
		
			
				|  |  | -            if "answer_box" in res.keys() and "answer" in res["answer_box"].keys():
 | 
	
		
			
				|  |  | -                toret += res["answer_box"]["answer"] + "\n"
 | 
	
		
			
				|  |  | -            if "answer_box" in res.keys() and "snippet" in res["answer_box"].keys():
 | 
	
		
			
				|  |  | -                toret += res["answer_box"]["snippet"] + "\n"
 | 
	
		
			
				|  |  | -            if (
 | 
	
		
			
				|  |  | -                "answer_box" in res.keys()
 | 
	
		
			
				|  |  | -                and "snippet_highlighted_words" in res["answer_box"].keys()
 | 
	
		
			
				|  |  | -            ):
 | 
	
		
			
				|  |  | -                for item in res["answer_box"]["snippet_highlighted_words"]:
 | 
	
		
			
				|  |  | -                    toret += item + "\n"
 | 
	
		
			
				|  |  | -            if (
 | 
	
		
			
				|  |  | -                "sports_results" in res.keys()
 | 
	
		
			
				|  |  | -                and "game_spotlight" in res["sports_results"].keys()
 | 
	
		
			
				|  |  | -            ):
 | 
	
		
			
				|  |  | -                toret += res["sports_results"]["game_spotlight"] + "\n"
 | 
	
		
			
				|  |  | -            if (
 | 
	
		
			
				|  |  | -                "shopping_results" in res.keys()
 | 
	
		
			
				|  |  | -                and "title" in res["shopping_results"][0].keys()
 | 
	
		
			
				|  |  | -            ):
 | 
	
		
			
				|  |  | -                toret += res["shopping_results"][:3] + "\n"
 | 
	
		
			
				|  |  | -            if (
 | 
	
		
			
				|  |  | -                "knowledge_graph" in res.keys()
 | 
	
		
			
				|  |  | -                and "description" in res["knowledge_graph"].keys()
 | 
	
		
			
				|  |  | -            ):
 | 
	
		
			
				|  |  | -                toret = res["knowledge_graph"]["description"] + "\n"
 | 
	
		
			
				|  |  | -            if "snippet" in res["organic_results"][0].keys():
 | 
	
		
			
				|  |  | -                toret = "\n".join(
 | 
	
		
			
				|  |  | -                    f"content: {item['snippet']}\nlink: {item['link']}"
 | 
	
		
			
				|  |  | +            if "knowledge_graph" in res and "description" in res["knowledge_graph"]:
 | 
	
		
			
				|  |  | +                toret += res["knowledge_graph"]["description"] + "\n"
 | 
	
		
			
				|  |  | +            if "organic_results" in res:
 | 
	
		
			
				|  |  | +                snippets = [
 | 
	
		
			
				|  |  | +                    f"content: {item.get('snippet')}\nlink: {item.get('link')}"
 | 
	
		
			
				|  |  |                      for item in res["organic_results"]
 | 
	
		
			
				|  |  | -                    if "snippet" in item and "link" in item
 | 
	
		
			
				|  |  | -                )
 | 
	
		
			
				|  |  | -            if (
 | 
	
		
			
				|  |  | -                "images_results" in res.keys()
 | 
	
		
			
				|  |  | -                and "thumbnail" in res["images_results"][0].keys()
 | 
	
		
			
				|  |  | -            ):
 | 
	
		
			
				|  |  | -                thumbnails = [item["thumbnail"] for item in res["images_results"][:10]]
 | 
	
		
			
				|  |  | -                toret = thumbnails
 | 
	
		
			
				|  |  | -            if toret == "":
 | 
	
		
			
				|  |  | -                toret = "No good search result found"
 | 
	
		
			
				|  |  | +                    if "snippet" in item
 | 
	
		
			
				|  |  | +                ]
 | 
	
		
			
				|  |  | +                toret += "\n".join(snippets)
 | 
	
		
			
				|  |  |          elif typ == "link":
 | 
	
		
			
				|  |  | -            if "knowledge_graph" in res.keys() and "title" in res["knowledge_graph"].keys() \
 | 
	
		
			
				|  |  | -                    and "description_link" in res["knowledge_graph"].keys():
 | 
	
		
			
				|  |  | -                toret = res["knowledge_graph"]["description_link"]
 | 
	
		
			
				|  |  | -            elif "knowledge_graph" in res.keys() and "see_results_about" in res["knowledge_graph"].keys() \
 | 
	
		
			
				|  |  | -                and len(res["knowledge_graph"]["see_results_about"]) > 0:
 | 
	
		
			
				|  |  | -                see_result_about = res["knowledge_graph"]["see_results_about"]
 | 
	
		
			
				|  |  | -                toret = ""
 | 
	
		
			
				|  |  | -                for item in see_result_about:
 | 
	
		
			
				|  |  | -                    if "name" not in item.keys() or "link" not in item.keys():
 | 
	
		
			
				|  |  | -                        continue
 | 
	
		
			
				|  |  | -                    toret += f"[{item['name']}]({item['link']})\n"
 | 
	
		
			
				|  |  | -            elif "organic_results" in res.keys() and len(res["organic_results"]) > 0:
 | 
	
		
			
				|  |  | -                organic_results = res["organic_results"]
 | 
	
		
			
				|  |  | -                toret = ""
 | 
	
		
			
				|  |  | -                for item in organic_results:
 | 
	
		
			
				|  |  | -                    if "title" not in item.keys() or "link" not in item.keys():
 | 
	
		
			
				|  |  | -                        continue
 | 
	
		
			
				|  |  | -                    toret += f"[{item['title']}]({item['link']})\n"
 | 
	
		
			
				|  |  | -            elif "related_questions" in res.keys() and len(res["related_questions"]) > 0:
 | 
	
		
			
				|  |  | -                related_questions = res["related_questions"]
 | 
	
		
			
				|  |  | -                toret = ""
 | 
	
		
			
				|  |  | -                for item in related_questions:
 | 
	
		
			
				|  |  | -                    if "question" not in item.keys() or "link" not in item.keys():
 | 
	
		
			
				|  |  | -                        continue
 | 
	
		
			
				|  |  | -                    toret += f"[{item['question']}]({item['link']})\n"
 | 
	
		
			
				|  |  | -            elif "related_searches" in res.keys() and len(res["related_searches"]) > 0:
 | 
	
		
			
				|  |  | -                related_searches = res["related_searches"]
 | 
	
		
			
				|  |  | -                toret = ""
 | 
	
		
			
				|  |  | -                for item in related_searches:
 | 
	
		
			
				|  |  | -                    if "query" not in item.keys() or "link" not in item.keys():
 | 
	
		
			
				|  |  | -                        continue
 | 
	
		
			
				|  |  | -                    toret += f"[{item['query']}]({item['link']})\n"
 | 
	
		
			
				|  |  | -            else:
 | 
	
		
			
				|  |  | -                toret = "No good search result found"
 | 
	
		
			
				|  |  | +            if "knowledge_graph" in res and "source" in res["knowledge_graph"]:
 | 
	
		
			
				|  |  | +                toret += res["knowledge_graph"]["source"]["link"]
 | 
	
		
			
				|  |  | +            elif "organic_results" in res:
 | 
	
		
			
				|  |  | +                links = [
 | 
	
		
			
				|  |  | +                    f"[{item['title']}]({item['link']})\n"
 | 
	
		
			
				|  |  | +                    for item in res["organic_results"]
 | 
	
		
			
				|  |  | +                    if "title" in item and "link" in item
 | 
	
		
			
				|  |  | +                ]
 | 
	
		
			
				|  |  | +                toret += "\n".join(links)
 | 
	
		
			
				|  |  | +            elif "related_questions" in res:
 | 
	
		
			
				|  |  | +                questions = [
 | 
	
		
			
				|  |  | +                    f"[{item['question']}]({item['link']})\n"
 | 
	
		
			
				|  |  | +                    for item in res["related_questions"]
 | 
	
		
			
				|  |  | +                    if "question" in item and "link" in item
 | 
	
		
			
				|  |  | +                ]
 | 
	
		
			
				|  |  | +                toret += "\n".join(questions)
 | 
	
		
			
				|  |  | +            elif "related_searches" in res:
 | 
	
		
			
				|  |  | +                searches = [
 | 
	
		
			
				|  |  | +                    f"[{item['query']}]({item['link']})\n"
 | 
	
		
			
				|  |  | +                    for item in res["related_searches"]
 | 
	
		
			
				|  |  | +                    if "query" in item and "link" in item
 | 
	
		
			
				|  |  | +                ]
 | 
	
		
			
				|  |  | +                toret += "\n".join(searches)
 | 
	
		
			
				|  |  | +        if not toret:
 | 
	
		
			
				|  |  | +            toret = "No good search result found"
 | 
	
		
			
				|  |  |          return toret
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |  class GoogleSearchTool(BuiltinTool):
 | 
	
		
			
				|  |  | -    def _invoke(self, 
 | 
	
		
			
				|  |  | +    def _invoke(self,
 | 
	
		
			
				|  |  |                  user_id: str,
 | 
	
		
			
				|  |  | -               tool_parameters: dict[str, Any], 
 | 
	
		
			
				|  |  | -        ) -> Union[ToolInvokeMessage, list[ToolInvokeMessage]]:
 | 
	
		
			
				|  |  | +                tool_parameters: dict[str, Any],
 | 
	
		
			
				|  |  | +                ) -> Union[ToolInvokeMessage, list[ToolInvokeMessage]]:
 | 
	
		
			
				|  |  |          """
 | 
	
		
			
				|  |  |              invoke tools
 | 
	
		
			
				|  |  |          """
 | 
	
	
		
			
				|  | @@ -164,4 +104,3 @@ class GoogleSearchTool(BuiltinTool):
 | 
	
		
			
				|  |  |          if result_type == 'text':
 | 
	
		
			
				|  |  |              return self.create_text_message(text=result)
 | 
	
		
			
				|  |  |          return self.create_link_message(link=result)
 | 
	
		
			
				|  |  | -    
 |