import json from os import getenv from typing import Optional from phi.tools import Toolkit from phi.utils.log import logger try: from exa_py import Exa except ImportError: raise ImportError("`exa_py` not installed. Please install using `pip install exa_py`") class ExaTools(Toolkit): def __init__( self, api_key: Optional[str] = None, search: bool = False, search_with_contents: bool = True, show_results: bool = False, ): super().__init__(name="exa") self.api_key = api_key or getenv("EXA_API_KEY") if not self.api_key: logger.error("EXA_API_KEY not set. Please set the EXA_API_KEY environment variable.") self.show_results = show_results if search: self.register(self.search_exa) if search_with_contents: self.register(self.search_exa_with_contents) def search_exa(self, query: str, num_results: int = 5) -> str: """Searches Exa for a query. :param query: The query to search for. :param num_results: The number of results to return. :return: Links of relevant documents from exa. """ if not self.api_key: return "Please set the EXA_API_KEY" try: exa = Exa(self.api_key) logger.debug(f"Searching exa for: {query}") exa_results = exa.search(query, num_results=num_results) exa_search_urls = [result.url for result in exa_results.results] parsed_results = "\n".join(exa_search_urls) if self.show_results: logger.info(parsed_results) return parsed_results except Exception as e: logger.error(f"Failed to search exa {e}") return f"Error: {e}" def search_exa_with_contents(self, query: str, num_results: int = 3, text_length_limit: int = 1000) -> str: """Searches Exa for a query and returns the contents from the search results. :param query: The query to search for. :param num_results: The number of results to return. Defaults to 3. :param text_length_limit: The length of the text to return. Defaults to 1000. :return: JSON string of the search results. """ if not self.api_key: return "Please set the EXA_API_KEY" try: exa = Exa(self.api_key) logger.debug(f"Searching exa for: {query}") exa_results = exa.search_and_contents(query, num_results=num_results) exa_results_parsed = [] for result in exa_results.results: result_dict = {"url": result.url} if result.text: result_dict["text"] = result.text[:text_length_limit] if result.author: result_dict["author"] = result.author if result.title: result_dict["title"] = result.title exa_results_parsed.append(result_dict) parsed_results = json.dumps(exa_results_parsed, indent=2) if self.show_results: logger.info(parsed_results) return parsed_results except Exception as e: logger.error(f"Failed to search exa {e}") return f"Error: {e}"