diff --git a/src/crewai_tools/tools/firecrawl_crawl_website_tool/firecrawl_crawl_website_tool.py b/src/crewai_tools/tools/firecrawl_crawl_website_tool/firecrawl_crawl_website_tool.py index cc44e4b39..e46b22f0e 100644 --- a/src/crewai_tools/tools/firecrawl_crawl_website_tool/firecrawl_crawl_website_tool.py +++ b/src/crewai_tools/tools/firecrawl_crawl_website_tool/firecrawl_crawl_website_tool.py @@ -1,12 +1,13 @@ -import os -from typing import TYPE_CHECKING, Any, Dict, Optional, Type +from typing import Any, Dict, Optional, Type from crewai.tools import BaseTool from pydantic import BaseModel, ConfigDict, Field -# Type checking import -if TYPE_CHECKING: + +try: from firecrawl import FirecrawlApp +except ImportError: + FirecrawlApp = Any class FirecrawlCrawlWebsiteToolSchema(BaseModel): @@ -32,34 +33,34 @@ class FirecrawlCrawlWebsiteTool(BaseTool): def __init__(self, api_key: Optional[str] = None, **kwargs): super().__init__(**kwargs) + self.api_key = api_key + self._initialize_firecrawl() + + def _initialize_firecrawl(self) -> None: try: from firecrawl import FirecrawlApp # type: ignore + + self.firecrawl = FirecrawlApp(api_key=self.api_key) except ImportError: import click if click.confirm( - "You are missing the 'firecrawl-py' package. Would you like to install it? (y/N)" + "You are missing the 'firecrawl-py' package. Would you like to install it?" ): import subprocess - subprocess.run(["uv", "add", "firecrawl-py"], check=True) - from firecrawl import ( - FirecrawlApp, - ) + try: + subprocess.run(["uv", "add", "firecrawl-py"], check=True) + from firecrawl import FirecrawlApp + + self.firecrawl = FirecrawlApp(api_key=self.api_key) + except subprocess.CalledProcessError: + raise ImportError("Failed to install firecrawl-py package") else: raise ImportError( "`firecrawl-py` package not found, please run `uv add firecrawl-py`" ) - if not self.firecrawl: - client_api_key = api_key or os.getenv("FIRECRAWL_API_KEY") - if not client_api_key: - raise ValueError( - "FIRECRAWL_API_KEY is not set. Please provide it either via the constructor " - "with the `api_key` argument or by setting the FIRECRAWL_API_KEY environment variable." - ) - self.firecrawl = FirecrawlApp(api_key=client_api_key) - def _run( self, url: str, @@ -79,8 +80,10 @@ class FirecrawlCrawlWebsiteTool(BaseTool): try: from firecrawl import FirecrawlApp - # Must rebuild model after class is defined - FirecrawlCrawlWebsiteTool.model_rebuild() + # Only rebuild if the class hasn't been initialized yet + if not hasattr(FirecrawlCrawlWebsiteTool, "_model_rebuilt"): + FirecrawlCrawlWebsiteTool.model_rebuild() + FirecrawlCrawlWebsiteTool._model_rebuilt = True except ImportError: """ When this tool is not used, then exception can be ignored. diff --git a/src/crewai_tools/tools/firecrawl_scrape_website_tool/firecrawl_scrape_website_tool.py b/src/crewai_tools/tools/firecrawl_scrape_website_tool/firecrawl_scrape_website_tool.py index 7076ad263..5c9a0b759 100644 --- a/src/crewai_tools/tools/firecrawl_scrape_website_tool/firecrawl_scrape_website_tool.py +++ b/src/crewai_tools/tools/firecrawl_scrape_website_tool/firecrawl_scrape_website_tool.py @@ -1,11 +1,12 @@ -from typing import TYPE_CHECKING, Optional, Type +from typing import Any, Optional, Type from crewai.tools import BaseTool from pydantic import BaseModel, ConfigDict, Field -# Type checking import -if TYPE_CHECKING: +try: from firecrawl import FirecrawlApp +except ImportError: + FirecrawlApp = Any class FirecrawlScrapeWebsiteToolSchema(BaseModel): @@ -34,7 +35,7 @@ class FirecrawlScrapeWebsiteTool(BaseTool): import click if click.confirm( - "You are missing the 'firecrawl-py' package. Would you like to install it? (y/N)" + "You are missing the 'firecrawl-py' package. Would you like to install it?" ): import subprocess @@ -70,7 +71,9 @@ try: from firecrawl import FirecrawlApp # Must rebuild model after class is defined - FirecrawlScrapeWebsiteTool.model_rebuild() + if not hasattr(FirecrawlScrapeWebsiteTool, "_model_rebuilt"): + FirecrawlScrapeWebsiteTool.model_rebuild() + FirecrawlScrapeWebsiteTool._model_rebuilt = True except ImportError: """ When this tool is not used, then exception can be ignored. diff --git a/src/crewai_tools/tools/firecrawl_search_tool/firecrawl_search_tool.py b/src/crewai_tools/tools/firecrawl_search_tool/firecrawl_search_tool.py index c10f98c83..2998a5025 100644 --- a/src/crewai_tools/tools/firecrawl_search_tool/firecrawl_search_tool.py +++ b/src/crewai_tools/tools/firecrawl_search_tool/firecrawl_search_tool.py @@ -1,11 +1,13 @@ -from typing import TYPE_CHECKING, Any, Dict, Optional, Type +from typing import Any, Dict, Optional, Type from crewai.tools import BaseTool -from pydantic import BaseModel, Field +from pydantic import BaseModel, Field, ConfigDict # Type checking import -if TYPE_CHECKING: +try: from firecrawl import FirecrawlApp +except ImportError: + FirecrawlApp = Any class FirecrawlSearchToolSchema(BaseModel): @@ -30,6 +32,9 @@ class FirecrawlSearchToolSchema(BaseModel): class FirecrawlSearchTool(BaseTool): + model_config = ConfigDict( + arbitrary_types_allowed=True, validate_assignment=True, frozen=False + ) name: str = "Firecrawl web search tool" description: str = "Search webpages using Firecrawl and return the results" args_schema: Type[BaseModel] = FirecrawlSearchToolSchema @@ -38,27 +43,34 @@ class FirecrawlSearchTool(BaseTool): def __init__(self, api_key: Optional[str] = None, **kwargs): super().__init__(**kwargs) + self.api_key = api_key + self._initialize_firecrawl() + + def _initialize_firecrawl(self) -> None: try: from firecrawl import FirecrawlApp # type: ignore + + self.firecrawl = FirecrawlApp(api_key=self.api_key) except ImportError: import click if click.confirm( - "You are missing the 'firecrawl-py' package. Would you like to install it? (y/N)" + "You are missing the 'firecrawl-py' package. Would you like to install it?" ): import subprocess - subprocess.run(["uv", "add", "firecrawl-py"], check=True) - from firecrawl import ( - FirecrawlApp, - ) + try: + subprocess.run(["uv", "add", "firecrawl-py"], check=True) + from firecrawl import FirecrawlApp + + self.firecrawl = FirecrawlApp(api_key=self.api_key) + except subprocess.CalledProcessError: + raise ImportError("Failed to install firecrawl-py package") else: raise ImportError( "`firecrawl-py` package not found, please run `uv add firecrawl-py`" ) - self.firecrawl = FirecrawlApp(api_key=api_key) - def _run( self, query: str, @@ -69,9 +81,9 @@ class FirecrawlSearchTool(BaseTool): location: Optional[str] = None, timeout: Optional[int] = 60000, scrape_options: Optional[Dict[str, Any]] = None, - ): - if scrape_options is None: - scrape_options = {} + ) -> Any: + if not self.firecrawl: + raise RuntimeError("FirecrawlApp not properly initialized") options = { "query": query, @@ -81,6 +93,20 @@ class FirecrawlSearchTool(BaseTool): "country": country, "location": location, "timeout": timeout, - "scrapeOptions": scrape_options, + "scrapeOptions": scrape_options or {}, } return self.firecrawl.search(**options) + + +try: + from firecrawl import FirecrawlApp # type: ignore + + # Only rebuild if the class hasn't been initialized yet + if not hasattr(FirecrawlSearchTool, "_model_rebuilt"): + FirecrawlSearchTool.model_rebuild() + FirecrawlSearchTool._model_rebuilt = True +except ImportError: + """ + When this tool is not used, then exception can be ignored. + """ + pass diff --git a/src/crewai_tools/tools/linkup/linkup_search_tool.py b/src/crewai_tools/tools/linkup/linkup_search_tool.py index be03750fa..5836c1851 100644 --- a/src/crewai_tools/tools/linkup/linkup_search_tool.py +++ b/src/crewai_tools/tools/linkup/linkup_search_tool.py @@ -31,7 +31,7 @@ class LinkupSearchTool(BaseTool): import click if click.confirm( - "You are missing the 'linkup-sdk' package. Would you like to install it? (y/N)" + "You are missing the 'linkup-sdk' package. Would you like to install it?" ): import subprocess diff --git a/src/crewai_tools/tools/multion_tool/multion_tool.py b/src/crewai_tools/tools/multion_tool/multion_tool.py index b525c4693..299d66bd1 100644 --- a/src/crewai_tools/tools/multion_tool/multion_tool.py +++ b/src/crewai_tools/tools/multion_tool/multion_tool.py @@ -31,7 +31,7 @@ class MultiOnTool(BaseTool): import click if click.confirm( - "You are missing the 'multion' package. Would you like to install it? (y/N)" + "You are missing the 'multion' package. Would you like to install it?" ): import subprocess diff --git a/src/crewai_tools/tools/patronus_eval_tool/patronus_local_evaluator_tool.py b/src/crewai_tools/tools/patronus_eval_tool/patronus_local_evaluator_tool.py index a1b63c790..7a879db1c 100644 --- a/src/crewai_tools/tools/patronus_eval_tool/patronus_local_evaluator_tool.py +++ b/src/crewai_tools/tools/patronus_eval_tool/patronus_local_evaluator_tool.py @@ -60,7 +60,7 @@ class PatronusLocalEvaluatorTool(BaseTool): import click if click.confirm( - "You are missing the 'patronus' package. Would you like to install it? (y/N)" + "You are missing the 'patronus' package. Would you like to install it?" ): import subprocess diff --git a/src/crewai_tools/tools/scrapegraph_scrape_tool/scrapegraph_scrape_tool.py b/src/crewai_tools/tools/scrapegraph_scrape_tool/scrapegraph_scrape_tool.py index 56fe0360a..57c81aabe 100644 --- a/src/crewai_tools/tools/scrapegraph_scrape_tool/scrapegraph_scrape_tool.py +++ b/src/crewai_tools/tools/scrapegraph_scrape_tool/scrapegraph_scrape_tool.py @@ -89,7 +89,7 @@ class ScrapegraphScrapeTool(BaseTool): import click if click.confirm( - "You are missing the 'scrapegraph-py' package. Would you like to install it? (y/N)" + "You are missing the 'scrapegraph-py' package. Would you like to install it?" ): import subprocess diff --git a/src/crewai_tools/tools/scrapfly_scrape_website_tool/scrapfly_scrape_website_tool.py b/src/crewai_tools/tools/scrapfly_scrape_website_tool/scrapfly_scrape_website_tool.py index dd071a61b..4d6b72b61 100644 --- a/src/crewai_tools/tools/scrapfly_scrape_website_tool/scrapfly_scrape_website_tool.py +++ b/src/crewai_tools/tools/scrapfly_scrape_website_tool/scrapfly_scrape_website_tool.py @@ -37,7 +37,7 @@ class ScrapflyScrapeWebsiteTool(BaseTool): import click if click.confirm( - "You are missing the 'scrapfly-sdk' package. Would you like to install it? (y/N)" + "You are missing the 'scrapfly-sdk' package. Would you like to install it?" ): import subprocess diff --git a/src/crewai_tools/tools/selenium_scraping_tool/selenium_scraping_tool.py b/src/crewai_tools/tools/selenium_scraping_tool/selenium_scraping_tool.py index e43a63828..36188fc0e 100644 --- a/src/crewai_tools/tools/selenium_scraping_tool/selenium_scraping_tool.py +++ b/src/crewai_tools/tools/selenium_scraping_tool/selenium_scraping_tool.py @@ -57,6 +57,8 @@ class SeleniumScrapingTool(BaseTool): wait_time: Optional[int] = 3 css_element: Optional[str] = None return_html: Optional[bool] = False + _options: Optional[dict] = None + _by: Optional[Any] = None def __init__( self, @@ -74,7 +76,7 @@ class SeleniumScrapingTool(BaseTool): import click if click.confirm( - "You are missing the 'selenium' and 'webdriver-manager' packages. Would you like to install it? (y/N)" + "You are missing the 'selenium' and 'webdriver-manager' packages. Would you like to install it?" ): import subprocess @@ -90,6 +92,8 @@ class SeleniumScrapingTool(BaseTool): "`selenium` and `webdriver-manager` package not found, please run `uv add selenium webdriver-manager`" ) self.driver = webdriver.Chrome() + self._options = Options() + self._by = By if cookie is not None: self.cookie = cookie @@ -133,7 +137,7 @@ class SeleniumScrapingTool(BaseTool): return css_element is None or css_element.strip() == "" def _get_body_content(self, driver, return_html): - body_element = driver.find_element(By.TAG_NAME, "body") + body_element = driver.find_element(self._by.TAG_NAME, "body") return ( body_element.get_attribute("outerHTML") @@ -144,7 +148,7 @@ class SeleniumScrapingTool(BaseTool): def _get_elements_content(self, driver, css_element, return_html): elements_content = [] - for element in driver.find_elements(By.CSS_SELECTOR, css_element): + for element in driver.find_elements(self._by.CSS_SELECTOR, css_element): elements_content.append( element.get_attribute("outerHTML") if return_html else element.text ) @@ -159,7 +163,7 @@ class SeleniumScrapingTool(BaseTool): if not re.match(r"^https?://", url): raise ValueError("URL must start with http:// or https://") - options = Options() + options = self._options() options.add_argument("--headless") driver = self.driver(options=options) driver.get(url) diff --git a/src/crewai_tools/tools/serpapi_tool/serpapi_base_tool.py b/src/crewai_tools/tools/serpapi_tool/serpapi_base_tool.py index f41f0a596..5dbc52214 100644 --- a/src/crewai_tools/tools/serpapi_tool/serpapi_base_tool.py +++ b/src/crewai_tools/tools/serpapi_tool/serpapi_base_tool.py @@ -19,7 +19,7 @@ class SerpApiBaseTool(BaseTool): import click if click.confirm( - "You are missing the 'serpapi' package. Would you like to install it? (y/N)" + "You are missing the 'serpapi' package. Would you like to install it?" ): import subprocess diff --git a/src/crewai_tools/tools/spider_tool/spider_tool.py b/src/crewai_tools/tools/spider_tool/spider_tool.py index 170e691f9..ff52a35dc 100644 --- a/src/crewai_tools/tools/spider_tool/spider_tool.py +++ b/src/crewai_tools/tools/spider_tool/spider_tool.py @@ -91,7 +91,7 @@ class SpiderTool(BaseTool): import click if click.confirm( - "You are missing the 'spider-client' package. Would you like to install it? (y/N)" + "You are missing the 'spider-client' package. Would you like to install it?" ): import subprocess diff --git a/src/crewai_tools/tools/stagehand_tool/stagehand_tool.py b/src/crewai_tools/tools/stagehand_tool/stagehand_tool.py index 0aac44e86..3b19c514f 100644 --- a/src/crewai_tools/tools/stagehand_tool/stagehand_tool.py +++ b/src/crewai_tools/tools/stagehand_tool/stagehand_tool.py @@ -172,7 +172,7 @@ class StagehandTool(BaseTool): import click if click.confirm( - "You are missing the 'stagehand-sdk' package. Would you like to install it? (y/N)" + "You are missing the 'stagehand-sdk' package. Would you like to install it?" ): import subprocess diff --git a/src/crewai_tools/tools/weaviate_tool/vector_search.py b/src/crewai_tools/tools/weaviate_tool/vector_search.py index 879a950f6..d03d444e1 100644 --- a/src/crewai_tools/tools/weaviate_tool/vector_search.py +++ b/src/crewai_tools/tools/weaviate_tool/vector_search.py @@ -72,7 +72,7 @@ class WeaviateVectorSearchTool(BaseTool): import click if click.confirm( - "You are missing the 'weaviate-client' package. Would you like to install it? (y/N)" + "You are missing the 'weaviate-client' package. Would you like to install it?" ): import subprocess @@ -80,13 +80,13 @@ class WeaviateVectorSearchTool(BaseTool): else: raise ImportError( - "You are missing the 'weaviate-client' package. Would you like to install it? (y/N)" + "You are missing the 'weaviate-client' package. Would you like to install it?" ) def _run(self, query: str) -> str: if not WEAVIATE_AVAILABLE: raise ImportError( - "You are missing the 'weaviate-client' package. Would you like to install it? (y/N)" + "You are missing the 'weaviate-client' package. Would you like to install it?" ) if not self.weaviate_cluster_url or not self.weaviate_api_key: