From a51a7000c5e84c9612f33e5d7fc378b1efdc033a Mon Sep 17 00:00:00 2001 From: rafaelsideguide <150964962+rafaelsideguide@users.noreply.github.com> Date: Thu, 16 May 2024 11:20:36 -0300 Subject: [PATCH 01/32] added Firecrawl tools --- .../firecrawl_crawl_website_tool/README.md | 42 +++++++++++++++++++ .../firecrawl_crawl_website_tool.py | 33 +++++++++++++++ .../firecrawl_scrape_website_tool/README.md | 38 +++++++++++++++++ .../firecrawl_scrape_website_tool.py | 35 ++++++++++++++++ .../tools/firecrawl_search_tool/README.md | 35 ++++++++++++++++ .../firecrawl_search_tool.py | 33 +++++++++++++++ 6 files changed, 216 insertions(+) create mode 100644 src/crewai_tools/tools/firecrawl_crawl_website_tool/README.md create mode 100644 src/crewai_tools/tools/firecrawl_crawl_website_tool/firecrawl_crawl_website_tool.py create mode 100644 src/crewai_tools/tools/firecrawl_scrape_website_tool/README.md create mode 100644 src/crewai_tools/tools/firecrawl_scrape_website_tool/firecrawl_scrape_website_tool.py create mode 100644 src/crewai_tools/tools/firecrawl_search_tool/README.md create mode 100644 src/crewai_tools/tools/firecrawl_search_tool/firecrawl_search_tool.py diff --git a/src/crewai_tools/tools/firecrawl_crawl_website_tool/README.md b/src/crewai_tools/tools/firecrawl_crawl_website_tool/README.md new file mode 100644 index 000000000..46d011602 --- /dev/null +++ b/src/crewai_tools/tools/firecrawl_crawl_website_tool/README.md @@ -0,0 +1,42 @@ +# FirecrawlCrawlWebsiteTool + +## Description + +[Firecrawl](https://firecrawl.dev) is a platform for crawling and convert any website into clean markdown or structured data. + +## Installation + +- Get an API key from [firecrawl.dev](https://firecrawl.dev) and set it in environment variables (`FIRECRAWL_API_KEY`). +- Install the [Firecrawl SDK](https://github.com/mendableai/firecrawl) along with `crewai[tools]` package: + +``` +pip install firecrawl-py 'crewai[tools]' +``` + +## Example + +Utilize the FirecrawlScrapeFromWebsiteTool as follows to allow your agent to load websites: + +```python +from crewai_tools import FirecrawlCrawlWebsiteTool + +tool = FirecrawlCrawlWebsiteTool(url='firecrawl.dev') +``` + +## Arguments + +- `api_key`: Optional. Specifies Firecrawl API key. Defaults is the `FIRECRAWL_API_KEY` environment variable. +- `url`: The base URL to start crawling from. +- `page_options`: Optional. + - `onlyMainContent`: Optional. Only return the main content of the page excluding headers, navs, footers, etc. + - `includeHtml`: Optional. Include the raw HTML content of the page. Will output a html key in the response. +- `crawler_options`: Optional. Options for controlling the crawling behavior. + - `includes`: Optional. URL patterns to include in the crawl. + - `exclude`: Optional. URL patterns to exclude from the crawl. + - `generateImgAltText`: Optional. Generate alt text for images using LLMs (requires a paid plan). + - `returnOnlyUrls`: Optional. If true, returns only the URLs as a list in the crawl status. Note: the response will be a list of URLs inside the data, not a list of documents. + - `maxDepth`: Optional. Maximum depth to crawl. Depth 1 is the base URL, depth 2 includes the base URL and its direct children, and so on. + - `mode`: Optional. The crawling mode to use. Fast mode crawls 4x faster on websites without a sitemap but may not be as accurate and shouldn't be used on heavily JavaScript-rendered websites. + - `limit`: Optional. Maximum number of pages to crawl. + - `timeout`: Optional. Timeout in milliseconds for the crawling operation. + 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 new file mode 100644 index 000000000..5c796189a --- /dev/null +++ b/src/crewai_tools/tools/firecrawl_crawl_website_tool/firecrawl_crawl_website_tool.py @@ -0,0 +1,33 @@ +from typing import Optional, Any, Type, Dict, List +from pydantic.v1 import BaseModel, Field +from crewai_tools.tools.base_tool import BaseTool + +class FirecrawlCrawlWebsiteToolSchema(BaseModel): + url: str = Field(description="Website URL") + crawler_options: Optional[Dict[str, Any]] = Field(default=None, description="Options for crawling") + page_options: Optional[Dict[str, Any]] = Field(default=None, description="Options for page") + +class FirecrawlCrawlWebsiteTool(BaseTool): + name: str = "Firecrawl web crawl tool" + description: str = "Crawl webpages using Firecrawl and return the contents" + args_schema: Type[BaseModel] = FirecrawlCrawlWebsiteToolSchema + api_key: Optional[str] = None + firecrawl: Optional[Any] = None + + def __init__(self, api_key: Optional[str] = None, **kwargs): + super().__init__(**kwargs) + try: + from firecrawl import FirecrawlApp # type: ignore + except ImportError: + raise ImportError( + "`firecrawl` package not found, please run `pip install firecrawl-py`" + ) + + self.firecrawl = FirecrawlApp(api_key=api_key) + + def _run(self, url: str, crawler_options: Optional[Dict[str, Any]] = None, page_options: Optional[Dict[str, Any]] = None): + options = { + "crawlerOptions": crawler_options, + "pageOptions": page_options + } + return self.firecrawl.crawl_url(url, options) \ No newline at end of file diff --git a/src/crewai_tools/tools/firecrawl_scrape_website_tool/README.md b/src/crewai_tools/tools/firecrawl_scrape_website_tool/README.md new file mode 100644 index 000000000..93570f06b --- /dev/null +++ b/src/crewai_tools/tools/firecrawl_scrape_website_tool/README.md @@ -0,0 +1,38 @@ +# FirecrawlScrapeWebsiteTool + +## Description + +[Firecrawl](https://firecrawl.dev) is a platform for crawling and convert any website into clean markdown or structured data. + +## Installation + +- Get an API key from [firecrawl.dev](https://firecrawl.dev) and set it in environment variables (`FIRECRAWL_API_KEY`). +- Install the [Firecrawl SDK](https://github.com/mendableai/firecrawl) along with `crewai[tools]` package: + +``` +pip install firecrawl-py 'crewai[tools]' +``` + +## Example + +Utilize the FirecrawlScrapeWebsiteTool as follows to allow your agent to load websites: + +```python +from crewai_tools import FirecrawlScrapeWebsiteTool + +tool = FirecrawlScrapeWebsiteTool(url='firecrawl.dev') +``` + +## Arguments + +- `api_key`: Optional. Specifies Firecrawl API key. Defaults is the `FIRECRAWL_API_KEY` environment variable. +- `url`: The URL to scrape. +- `page_options`: Optional. + - `onlyMainContent`: Optional. Only return the main content of the page excluding headers, navs, footers, etc. + - `includeHtml`: Optional. Include the raw HTML content of the page. Will output a html key in the response. +- `extractor_options`: Optional. Options for LLM-based extraction of structured information from the page content + - `mode`: The extraction mode to use, currently supports 'llm-extraction' + - `extractionPrompt`: Optional. A prompt describing what information to extract from the page + - `extractionSchema`: Optional. The schema for the data to be extracted +- `timeout`: Optional. Timeout in milliseconds for the request + 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 new file mode 100644 index 000000000..8540b13ff --- /dev/null +++ b/src/crewai_tools/tools/firecrawl_scrape_website_tool/firecrawl_scrape_website_tool.py @@ -0,0 +1,35 @@ +from typing import Optional, Any, Type, Dict +from pydantic.v1 import BaseModel, Field +from crewai_tools.tools.base_tool import BaseTool + +class FirecrawlScrapeWebsiteToolSchema(BaseModel): + url: str = Field(description="Website URL") + page_options: Optional[Dict[str, Any]] = Field(default=None, description="Options for page scraping") + extractor_options: Optional[Dict[str, Any]] = Field(default=None, description="Options for data extraction") + timeout: Optional[int] = Field(default=None, description="Timeout for the scraping operation") + +class FirecrawlScrapeWebsiteTool(BaseTool): + name: str = "Firecrawl web scrape tool" + description: str = "Scrape webpages url using Firecrawl and return the contents" + args_schema: Type[BaseModel] = FirecrawlScrapeWebsiteToolSchema + api_key: Optional[str] = None + firecrawl: Optional[Any] = None + + def __init__(self, api_key: Optional[str] = None, **kwargs): + super().__init__(**kwargs) + try: + from firecrawl import FirecrawlApp # type: ignore + except ImportError: + raise ImportError( + "`firecrawl` package not found, please run `pip install firecrawl-py`" + ) + + self.firecrawl = FirecrawlApp(api_key=api_key) + + def _run(self, url: str, page_options: Optional[Dict[str, Any]] = None, extractor_options: Optional[Dict[str, Any]] = None, timeout: Optional[int] = None): + options = { + "pageOptions": page_options, + "extractorOptions": extractor_options, + "timeout": timeout + } + return self.firecrawl.scrape_url(url, options) \ No newline at end of file diff --git a/src/crewai_tools/tools/firecrawl_search_tool/README.md b/src/crewai_tools/tools/firecrawl_search_tool/README.md new file mode 100644 index 000000000..effb3f3d4 --- /dev/null +++ b/src/crewai_tools/tools/firecrawl_search_tool/README.md @@ -0,0 +1,35 @@ +# FirecrawlSearchTool + +## Description + +[Firecrawl](https://firecrawl.dev) is a platform for crawling and convert any website into clean markdown or structured data. + +## Installation + +- Get an API key from [firecrawl.dev](https://firecrawl.dev) and set it in environment variables (`FIRECRAWL_API_KEY`). +- Install the [Firecrawl SDK](https://github.com/mendableai/firecrawl) along with `crewai[tools]` package: + +``` +pip install firecrawl-py 'crewai[tools]' +``` + +## Example + +Utilize the FirecrawlSearchTool as follows to allow your agent to load websites: + +```python +from crewai_tools import FirecrawlSearchTool + +tool = FirecrawlSearchTool(query='what is firecrawl?') +``` + +## Arguments + +- `api_key`: Optional. Specifies Firecrawl API key. Defaults is the `FIRECRAWL_API_KEY` environment variable. +- `query`: The search query string to be used for searching. +- `page_options`: Optional. Options for result formatting. + - `onlyMainContent`: Optional. Only return the main content of the page excluding headers, navs, footers, etc. + - `includeHtml`: Optional. Include the raw HTML content of the page. Will output a html key in the response. + - `fetchPageContent`: Optional. Fetch the full content of the page. +- `search_options`: Optional. Options for controlling the crawling behavior. + - `limit`: Optional. Maximum number of pages to crawl. \ No newline at end of file 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 new file mode 100644 index 000000000..89843f797 --- /dev/null +++ b/src/crewai_tools/tools/firecrawl_search_tool/firecrawl_search_tool.py @@ -0,0 +1,33 @@ +from typing import Optional, Any, Type, Dict, List +from pydantic.v1 import BaseModel, Field +from crewai_tools.tools.base_tool import BaseTool + +class FirecrawlSearchToolSchema(BaseModel): + query: str = Field(description="Search query") + page_options: Optional[Dict[str, Any]] = Field(default=None, description="Options for result formatting") + search_options: Optional[Dict[str, Any]] = Field(default=None, description="Options for searching") + +class FirecrawlSearchTool(BaseTool): + name: str = "Firecrawl web search tool" + description: str = "Search webpages using Firecrawl and return the results" + args_schema: Type[BaseModel] = FirecrawlSearchToolSchema + api_key: Optional[str] = None + firecrawl: Optional[Any] = None + + def __init__(self, api_key: Optional[str] = None, **kwargs): + super().__init__(**kwargs) + try: + from firecrawl import FirecrawlApp # type: ignore + except ImportError: + raise ImportError( + "`firecrawl` package not found, please run `pip install firecrawl-py`" + ) + + self.firecrawl = FirecrawlApp(api_key=api_key) + + def _run(self, query: str, page_options: Optional[Dict[str, Any]] = None, result_options: Optional[Dict[str, Any]] = None): + options = { + "pageOptions": page_options, + "resultOptions": result_options + } + return self.firecrawl.search(query, options) From 5e8e711170f8b003e65a46dc141f3974621dca9f Mon Sep 17 00:00:00 2001 From: Rip&Tear <84775494+theCyberTech@users.noreply.github.com> Date: Thu, 13 Jun 2024 12:53:35 +0800 Subject: [PATCH 02/32] Update serper_dev_tool.py Added two additional functionalities: 1) added the ability to save the server results to a file 2) added the ability to set the number of results returned Can be used as follows: serper_tool = SerperDevTool(file_save=True, n_results=20) --- .../tools/serper_dev_tool/serper_dev_tool.py | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/src/crewai_tools/tools/serper_dev_tool/serper_dev_tool.py b/src/crewai_tools/tools/serper_dev_tool/serper_dev_tool.py index 927c0e3b3..bd6eaab54 100644 --- a/src/crewai_tools/tools/serper_dev_tool/serper_dev_tool.py +++ b/src/crewai_tools/tools/serper_dev_tool/serper_dev_tool.py @@ -6,6 +6,14 @@ from typing import Type, Any from pydantic.v1 import BaseModel, Field from crewai_tools.tools.base_tool import BaseTool +def _save_results_to_file(content: str) -> None: + """Saves the search results to a file.""" + filename = f"search_results_{datetime.now().strftime('%Y-%m-%d_%H-%M-%S')}.txt" + with open(filename, 'w') as file: + file.write(content) + print(f"Results saved to {filename}") + + class SerperDevToolSchema(BaseModel): """Input for SerperDevTool.""" search_query: str = Field(..., description="Mandatory search query you want to use to search the internet") @@ -15,17 +23,22 @@ class SerperDevTool(BaseTool): description: str = "A tool that can be used to search the internet with a search_query." args_schema: Type[BaseModel] = SerperDevToolSchema search_url: str = "https://google.serper.dev/search" - n_results: int = 10 + n_results: int = Field(default=10, description="Number of search results to return") + save_file: bool = Field(default=False, description="Flag to determine whether to save the results to a file") def _run( self, **kwargs: Any, ) -> Any: + save_file = kwargs.get('save_file', self.save_file) + + n_results = kwargs.get('n_results', self.n_results) + search_query = kwargs.get('search_query') if search_query is None: search_query = kwargs.get('query') - payload = json.dumps({"q": search_query}) + payload = json.dumps({"q": search_query, "num": n_results}) headers = { 'X-API-KEY': os.environ['SERPER_API_KEY'], 'content-type': 'application/json' @@ -47,6 +60,8 @@ class SerperDevTool(BaseTool): next content = '\n'.join(string) + if save_file: + _save_results_to_file(content) return f"\nSearch results: {content}\n" else: return results From 2b47377a7849be73c8ecac19338aaa7811bdbd27 Mon Sep 17 00:00:00 2001 From: Eduardo Chiarotti Date: Wed, 19 Jun 2024 20:45:04 -0300 Subject: [PATCH 03/32] feat: add code-interpreter tool --- .../tools/code_interpreter_tool/Dockerfile | 21 +++++++ .../tools/code_interpreter_tool/README.md | 0 .../code_interpreter_tool.py | 57 +++++++++++++++++++ 3 files changed, 78 insertions(+) create mode 100644 src/crewai_tools/tools/code_interpreter_tool/Dockerfile create mode 100644 src/crewai_tools/tools/code_interpreter_tool/README.md create mode 100644 src/crewai_tools/tools/code_interpreter_tool/code_interpreter_tool.py diff --git a/src/crewai_tools/tools/code_interpreter_tool/Dockerfile b/src/crewai_tools/tools/code_interpreter_tool/Dockerfile new file mode 100644 index 000000000..b72a51a88 --- /dev/null +++ b/src/crewai_tools/tools/code_interpreter_tool/Dockerfile @@ -0,0 +1,21 @@ +# Use an official Ubuntu as a parent image +FROM ubuntu:20.04 + +# Set environment variables +ENV DEBIAN_FRONTEND=noninteractive + +# Install common utilities +RUN apt-get update && apt-get install -y \ + build-essential \ + curl \ + wget \ + software-properties-common + +# Install Python +RUN apt-get install -y python3 python3-pip + +# Clean up +RUN apt-get clean && rm -rf /var/lib/apt/lists/* + +# Set the working directory +WORKDIR /workspace diff --git a/src/crewai_tools/tools/code_interpreter_tool/README.md b/src/crewai_tools/tools/code_interpreter_tool/README.md new file mode 100644 index 000000000..e69de29bb diff --git a/src/crewai_tools/tools/code_interpreter_tool/code_interpreter_tool.py b/src/crewai_tools/tools/code_interpreter_tool/code_interpreter_tool.py new file mode 100644 index 000000000..a2066ca03 --- /dev/null +++ b/src/crewai_tools/tools/code_interpreter_tool/code_interpreter_tool.py @@ -0,0 +1,57 @@ +from typing import Optional, Type + +import docker +from crewai_tools.tools.base_tool import BaseTool +from pydantic.v1 import BaseModel, Field + + +class FixedCodeInterpreterSchemaSchema(BaseModel): + """Input for DirectoryReadTool.""" + + pass + + +class CodeInterpreterSchema(FixedCodeInterpreterSchemaSchema): + """Input for DirectoryReadTool.""" + + code: str = Field( + ..., + description="Python3 code used to be interpreted in the Docker container and output the result", + ) + + +class CodeInterpreterTool(BaseTool): + name: str = "Code Interpreter" + description: str = "Interprets Python code in a Docker container" + args_schema: Type[BaseModel] = CodeInterpreterSchema + code: Optional[str] = None + + def __init__(self, code: Optional[str] = None, **kwargs): + super().__init__(**kwargs) + if code is not None: + self.code = code + self.description = ( + "A tool that can be used to run Python code in a Docker container" + ) + self.args_schema = FixedCodeInterpreterSchemaSchema + self._generate_description() + + def _run(self, **kwargs): + code = kwargs.get("code", self.code) + return self.run_code_in_docker(code) + + def run_code_in_docker(self, code): + client = docker.from_env() + container = client.containers.run( + "code-interpreter", + command=f'python3 -c "{code}"', + detach=True, + working_dir="/workspace", + ) + + result = container.logs().decode("utf-8") + + container.stop() + container.remove() + + return result From bd13b55afd229fa830a944063fa90b33a2371b6d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Moura?= Date: Mon, 20 May 2024 11:02:20 -0300 Subject: [PATCH 04/32] Adding new PDFTextWritingTool --- .../pdf_text_writing_tool.py | 66 +++++++++++++++++++ 1 file changed, 66 insertions(+) create mode 100644 src/crewai_tools/tools/pdf_text_writing_tool/pdf_text_writing_tool.py diff --git a/src/crewai_tools/tools/pdf_text_writing_tool/pdf_text_writing_tool.py b/src/crewai_tools/tools/pdf_text_writing_tool/pdf_text_writing_tool.py new file mode 100644 index 000000000..c3a686b14 --- /dev/null +++ b/src/crewai_tools/tools/pdf_text_writing_tool/pdf_text_writing_tool.py @@ -0,0 +1,66 @@ +from typing import Any, Optional, Type +from pydantic import BaseModel, Field +from pypdf import PdfReader, PdfWriter, PageObject, ContentStream, NameObject, Font +from pathlib import Path + + +class PDFTextWritingToolSchema(BaseModel): + """Input schema for PDFTextWritingTool.""" + pdf_path: str = Field(..., description="Path to the PDF file to modify") + text: str = Field(..., description="Text to add to the PDF") + position: tuple = Field(..., description="Tuple of (x, y) coordinates for text placement") + font_size: int = Field(default=12, description="Font size of the text") + font_color: str = Field(default="0 0 0 rg", description="RGB color code for the text") + font_name: Optional[str] = Field(default="F1", description="Font name for standard fonts") + font_file: Optional[str] = Field(None, description="Path to a .ttf font file for custom font usage") + page_number: int = Field(default=0, description="Page number to add text to") + + +class PDFTextWritingTool(RagTool): + """A tool to add text to specific positions in a PDF, with custom font support.""" + name: str = "PDF Text Writing Tool" + description: str = "A tool that can write text to a specific position in a PDF document, with optional custom font embedding." + args_schema: Type[BaseModel] = PDFTextWritingToolSchema + + def run(self, pdf_path: str, text: str, position: tuple, font_size: int, font_color: str, + font_name: str = "F1", font_file: Optional[str] = None, page_number: int = 0, **kwargs) -> str: + reader = PdfReader(pdf_path) + writer = PdfWriter() + + if page_number >= len(reader.pages): + return "Page number out of range." + + page: PageObject = reader.pages[page_number] + content = ContentStream(page["/Contents"].data, reader) + + if font_file: + # Check if the font file exists + if not Path(font_file).exists(): + return "Font file does not exist." + + # Embed the custom font + font_name = self.embed_font(writer, font_file) + + # Prepare text operation with the custom or standard font + x_position, y_position = position + text_operation = f"BT /{font_name} {font_size} Tf {x_position} {y_position} Td ({text}) Tj ET" + content.operations.append([font_color]) # Set color + content.operations.append([text_operation]) # Add text + + # Replace old content with new content + page[NameObject("/Contents")] = content + writer.add_page(page) + + # Save the new PDF + output_pdf_path = "modified_output.pdf" + with open(output_pdf_path, "wb") as out_file: + writer.write(out_file) + + return f"Text added to {output_pdf_path} successfully." + + def embed_font(self, writer: PdfWriter, font_file: str) -> str: + """Embeds a TTF font into the PDF and returns the font name.""" + with open(font_file, "rb") as file: + font = Font.true_type(file.read()) + font_ref = writer.add_object(font) + return font_ref \ No newline at end of file From da75d51fe8431aae6b7333cdeee0ecf5d9e91843 Mon Sep 17 00:00:00 2001 From: Eduardo Chiarotti Date: Thu, 20 Jun 2024 20:24:26 -0300 Subject: [PATCH 05/32] feat: add Dockerfile, Makefile and update version of code --- .../tools/code_interpreter_tool/Dockerfile | 9 +--- .../tools/code_interpreter_tool/Makefile | 6 +++ .../code_interpreter_tool.py | 51 ++++++++++++------- 3 files changed, 41 insertions(+), 25 deletions(-) create mode 100644 src/crewai_tools/tools/code_interpreter_tool/Makefile diff --git a/src/crewai_tools/tools/code_interpreter_tool/Dockerfile b/src/crewai_tools/tools/code_interpreter_tool/Dockerfile index b72a51a88..ae9b2ffd6 100644 --- a/src/crewai_tools/tools/code_interpreter_tool/Dockerfile +++ b/src/crewai_tools/tools/code_interpreter_tool/Dockerfile @@ -1,8 +1,4 @@ -# Use an official Ubuntu as a parent image -FROM ubuntu:20.04 - -# Set environment variables -ENV DEBIAN_FRONTEND=noninteractive +FROM python:3.11-slim # Install common utilities RUN apt-get update && apt-get install -y \ @@ -11,9 +7,6 @@ RUN apt-get update && apt-get install -y \ wget \ software-properties-common -# Install Python -RUN apt-get install -y python3 python3-pip - # Clean up RUN apt-get clean && rm -rf /var/lib/apt/lists/* diff --git a/src/crewai_tools/tools/code_interpreter_tool/Makefile b/src/crewai_tools/tools/code_interpreter_tool/Makefile new file mode 100644 index 000000000..5a514db0a --- /dev/null +++ b/src/crewai_tools/tools/code_interpreter_tool/Makefile @@ -0,0 +1,6 @@ +# Makefile +IMAGE_NAME=code-interpreter +TAG=latest + +build: + docker build -t $(IMAGE_NAME):$(TAG) . diff --git a/src/crewai_tools/tools/code_interpreter_tool/code_interpreter_tool.py b/src/crewai_tools/tools/code_interpreter_tool/code_interpreter_tool.py index a2066ca03..f497a7c96 100644 --- a/src/crewai_tools/tools/code_interpreter_tool/code_interpreter_tool.py +++ b/src/crewai_tools/tools/code_interpreter_tool/code_interpreter_tool.py @@ -6,23 +6,27 @@ from pydantic.v1 import BaseModel, Field class FixedCodeInterpreterSchemaSchema(BaseModel): - """Input for DirectoryReadTool.""" + """Input for CodeInterpreterTool.""" pass class CodeInterpreterSchema(FixedCodeInterpreterSchemaSchema): - """Input for DirectoryReadTool.""" + """Input for CodeInterpreterTool.""" code: str = Field( ..., - description="Python3 code used to be interpreted in the Docker container and output the result", + description="Python3 code used to be interpreted in the Docker container. ALWAYS PRINT the final result and the output of the code", + ) + libraries_used: Optional[str] = Field( + None, + description="List of libraries used in the code with proper installing names separated by commas. Example: numpy,pandas,beautifulsoup4", ) class CodeInterpreterTool(BaseTool): name: str = "Code Interpreter" - description: str = "Interprets Python code in a Docker container" + description: str = "Interprets Python code in a Docker container. ALWAYS PRINT the final result and the output of the code" args_schema: Type[BaseModel] = CodeInterpreterSchema code: Optional[str] = None @@ -30,28 +34,41 @@ class CodeInterpreterTool(BaseTool): super().__init__(**kwargs) if code is not None: self.code = code - self.description = ( - "A tool that can be used to run Python code in a Docker container" - ) + self.description = "Interprets Python code in a Docker container. ALWAYS PRINT the final result and the output of the code" self.args_schema = FixedCodeInterpreterSchemaSchema self._generate_description() def _run(self, **kwargs): code = kwargs.get("code", self.code) - return self.run_code_in_docker(code) + libraries_used = kwargs.get("libraries_used", None) + return self.run_code_in_docker(code, libraries_used) - def run_code_in_docker(self, code): + def run_code_in_docker(self, code, libraries_used): client = docker.from_env() - container = client.containers.run( - "code-interpreter", - command=f'python3 -c "{code}"', - detach=True, - working_dir="/workspace", - ) - result = container.logs().decode("utf-8") + def run_code(container, code): + cmd_to_run = f'python3 -c "{code}"' + exec_result = container.exec_run(cmd_to_run) + return exec_result + + container = client.containers.run( + "code-interpreter", detach=True, tty=True, working_dir="/workspace" + ) + if libraries_used: + self._install_libraries(container, libraries_used.split(",")) + + exec_result = run_code(container, code) container.stop() container.remove() + if exec_result.exit_code != 0: + return f"Something went wrong while running the code: \n{exec_result.output.decode('utf-8')}" - return result + return exec_result.output.decode("utf-8") + + def _install_libraries(self, container, libraries): + """ + Install missing libraries in the Docker container + """ + for library in libraries: + container.exec_run(f"pip install {library}") From 1a4ac76b1eb11ec70a11784a4f6b1d33948e9171 Mon Sep 17 00:00:00 2001 From: Eduardo Chiarotti Date: Thu, 20 Jun 2024 20:24:47 -0300 Subject: [PATCH 06/32] feat: update code --- .../code_interpreter_tool.py | 52 +++++++++---------- 1 file changed, 26 insertions(+), 26 deletions(-) diff --git a/src/crewai_tools/tools/code_interpreter_tool/code_interpreter_tool.py b/src/crewai_tools/tools/code_interpreter_tool/code_interpreter_tool.py index f497a7c96..caafd44e3 100644 --- a/src/crewai_tools/tools/code_interpreter_tool/code_interpreter_tool.py +++ b/src/crewai_tools/tools/code_interpreter_tool/code_interpreter_tool.py @@ -30,7 +30,7 @@ class CodeInterpreterTool(BaseTool): args_schema: Type[BaseModel] = CodeInterpreterSchema code: Optional[str] = None - def __init__(self, code: Optional[str] = None, **kwargs): + def __init__(self, code: Optional[str] = None, **kwargs) -> None: super().__init__(**kwargs) if code is not None: self.code = code @@ -38,37 +38,37 @@ class CodeInterpreterTool(BaseTool): self.args_schema = FixedCodeInterpreterSchemaSchema self._generate_description() - def _run(self, **kwargs): + def _run(self, **kwargs) -> str: code = kwargs.get("code", self.code) libraries_used = kwargs.get("libraries_used", None) return self.run_code_in_docker(code, libraries_used) - def run_code_in_docker(self, code, libraries_used): - client = docker.from_env() - - def run_code(container, code): - cmd_to_run = f'python3 -c "{code}"' - exec_result = container.exec_run(cmd_to_run) - return exec_result - - container = client.containers.run( - "code-interpreter", detach=True, tty=True, working_dir="/workspace" - ) - if libraries_used: - self._install_libraries(container, libraries_used.split(",")) - - exec_result = run_code(container, code) - - container.stop() - container.remove() - if exec_result.exit_code != 0: - return f"Something went wrong while running the code: \n{exec_result.output.decode('utf-8')}" - - return exec_result.output.decode("utf-8") - - def _install_libraries(self, container, libraries): + def _install_libraries( + self, container: docker.models.containers.Container, libraries: list[str] + ) -> None: """ Install missing libraries in the Docker container """ for library in libraries: container.exec_run(f"pip install {library}") + + def _init_docker_container(self) -> docker.models.containers.Container: + client = docker.from_env() + return client.containers.run( + "code-interpreter", detach=True, tty=True, working_dir="/workspace" + ) + + def run_code_in_docker(self, code: str, libraries_used: str) -> str: + container = self._init_docker_container() + + if libraries_used: + self._install_libraries(container, libraries_used.split(",")) + + cmd_to_run = f'python3 -c "{code}"' + exec_result = container.exec_run(cmd_to_run) + + container.stop().remove() + + if exec_result.exit_code != 0: + return f"Something went wrong while running the code: \n{exec_result.output.decode('utf-8')}" + return exec_result.output.decode("utf-8") From 94e6651b55cbebd85b0610787de5f4429e0778b1 Mon Sep 17 00:00:00 2001 From: Eduardo Chiarotti Date: Thu, 20 Jun 2024 20:43:19 -0300 Subject: [PATCH 07/32] feat: add code-interpreter tool to init and add unit tests --- src/crewai_tools/tools/__init__.py | 17 ++++++---- tests/tools/test_code_interpreter_tool.py | 38 +++++++++++++++++++++++ 2 files changed, 49 insertions(+), 6 deletions(-) create mode 100644 tests/tools/test_code_interpreter_tool.py diff --git a/src/crewai_tools/tools/__init__.py b/src/crewai_tools/tools/__init__.py index 4da0c0337..35b81396b 100644 --- a/src/crewai_tools/tools/__init__.py +++ b/src/crewai_tools/tools/__init__.py @@ -1,24 +1,29 @@ from .browserbase_load_tool.browserbase_load_tool import BrowserbaseLoadTool from .code_docs_search_tool.code_docs_search_tool import CodeDocsSearchTool +from .code_interpreter_tool.code_interpreter_tool import CodeInterpreterTool from .csv_search_tool.csv_search_tool import CSVSearchTool -from .directory_search_tool.directory_search_tool import DirectorySearchTool from .directory_read_tool.directory_read_tool import DirectoryReadTool +from .directory_search_tool.directory_search_tool import DirectorySearchTool from .docx_search_tool.docx_search_tool import DOCXSearchTool from .exa_tools.exa_search_tool import EXASearchTool from .file_read_tool.file_read_tool import FileReadTool from .github_search_tool.github_search_tool import GithubSearchTool -from .serper_dev_tool.serper_dev_tool import SerperDevTool -from .txt_search_tool.txt_search_tool import TXTSearchTool from .json_search_tool.json_search_tool import JSONSearchTool +from .llamaindex_tool.llamaindex_tool import LlamaIndexTool from .mdx_seach_tool.mdx_search_tool import MDXSearchTool from .pdf_search_tool.pdf_search_tool import PDFSearchTool from .pg_seach_tool.pg_search_tool import PGSearchTool from .rag.rag_tool import RagTool -from .scrape_element_from_website.scrape_element_from_website import ScrapeElementFromWebsiteTool +from .scrape_element_from_website.scrape_element_from_website import ( + ScrapeElementFromWebsiteTool, +) from .scrape_website_tool.scrape_website_tool import ScrapeWebsiteTool from .selenium_scraping_tool.selenium_scraping_tool import SeleniumScrapingTool +from .serper_dev_tool.serper_dev_tool import SerperDevTool +from .txt_search_tool.txt_search_tool import TXTSearchTool from .website_search.website_search_tool import WebsiteSearchTool from .xml_search_tool.xml_search_tool import XMLSearchTool -from .youtube_channel_search_tool.youtube_channel_search_tool import YoutubeChannelSearchTool +from .youtube_channel_search_tool.youtube_channel_search_tool import ( + YoutubeChannelSearchTool, +) from .youtube_video_search_tool.youtube_video_search_tool import YoutubeVideoSearchTool -from .llamaindex_tool.llamaindex_tool import LlamaIndexTool diff --git a/tests/tools/test_code_interpreter_tool.py b/tests/tools/test_code_interpreter_tool.py new file mode 100644 index 000000000..a9ffb9dbc --- /dev/null +++ b/tests/tools/test_code_interpreter_tool.py @@ -0,0 +1,38 @@ +import unittest +from unittest.mock import patch + +from crewai_tools.tools.code_interpreter_tool.code_interpreter_tool import ( + CodeInterpreterTool, +) + + +class TestCodeInterpreterTool(unittest.TestCase): + @patch("crewai_tools.tools.code_interpreter_tool.code_interpreter_tool.docker") + def test_run_code_in_docker(self, docker_mock): + tool = CodeInterpreterTool() + code = "print('Hello, World!')" + libraries_used = "numpy,pandas" + expected_output = "Hello, World!\n" + + docker_mock.from_env().containers.run().exec_run().exit_code = 0 + docker_mock.from_env().containers.run().exec_run().output = ( + expected_output.encode() + ) + result = tool.run_code_in_docker(code, libraries_used) + + self.assertEqual(result, expected_output) + + @patch("crewai_tools.tools.code_interpreter_tool.code_interpreter_tool.docker") + def test_run_code_in_docker_with_error(self, docker_mock): + tool = CodeInterpreterTool() + code = "print(1/0)" + libraries_used = "numpy,pandas" + expected_output = "Something went wrong while running the code: \nZeroDivisionError: division by zero\n" + + docker_mock.from_env().containers.run().exec_run().exit_code = 1 + docker_mock.from_env().containers.run().exec_run().output = ( + b"ZeroDivisionError: division by zero\n" + ) + result = tool.run_code_in_docker(code, libraries_used) + + self.assertEqual(result, expected_output) From 61cce93fd020fa4fce74cc80f64e07f2a777215d Mon Sep 17 00:00:00 2001 From: Eduardo Chiarotti Date: Thu, 20 Jun 2024 21:41:12 -0300 Subject: [PATCH 08/32] feat: remove unused Makefile, update README and update code --- .../tools/code_interpreter_tool/Makefile | 6 ----- .../tools/code_interpreter_tool/README.md | 27 +++++++++++++++++++ .../code_interpreter_tool.py | 22 ++++++++++++++- 3 files changed, 48 insertions(+), 7 deletions(-) delete mode 100644 src/crewai_tools/tools/code_interpreter_tool/Makefile diff --git a/src/crewai_tools/tools/code_interpreter_tool/Makefile b/src/crewai_tools/tools/code_interpreter_tool/Makefile deleted file mode 100644 index 5a514db0a..000000000 --- a/src/crewai_tools/tools/code_interpreter_tool/Makefile +++ /dev/null @@ -1,6 +0,0 @@ -# Makefile -IMAGE_NAME=code-interpreter -TAG=latest - -build: - docker build -t $(IMAGE_NAME):$(TAG) . diff --git a/src/crewai_tools/tools/code_interpreter_tool/README.md b/src/crewai_tools/tools/code_interpreter_tool/README.md index e69de29bb..672c86f21 100644 --- a/src/crewai_tools/tools/code_interpreter_tool/README.md +++ b/src/crewai_tools/tools/code_interpreter_tool/README.md @@ -0,0 +1,27 @@ +# CodeInterpreterTool + +## Description +This tool is used to give the Agent the ability to run code (Python3) from the code generated by the Agent itself. The code is executed in a sandboxed environment, so it is safe to run any code. + +## Requirements + +- Docker + +## Installation +Install the crewai_tools package +```shell +pip install 'crewai[tools]' +``` + +## Example + +Remember that when using this tool, the code must be generated by the Agent itself. The code must be a Python3 code. And it will take some time for the first time to run because it needs to build the Docker image. + +```python +from crewai_tools import CodeInterpreterTool + +Agent( + ... + tools=[CodeInterpreterTool()], +) +``` diff --git a/src/crewai_tools/tools/code_interpreter_tool/code_interpreter_tool.py b/src/crewai_tools/tools/code_interpreter_tool/code_interpreter_tool.py index caafd44e3..06cb081f0 100644 --- a/src/crewai_tools/tools/code_interpreter_tool/code_interpreter_tool.py +++ b/src/crewai_tools/tools/code_interpreter_tool/code_interpreter_tool.py @@ -1,3 +1,4 @@ +import os from typing import Optional, Type import docker @@ -30,9 +31,27 @@ class CodeInterpreterTool(BaseTool): args_schema: Type[BaseModel] = CodeInterpreterSchema code: Optional[str] = None + def _verify_docker_image(self) -> None: + """ + Verify if the Docker image is available + """ + image_tag = "code-interpreter:latest" + + client = docker.from_env() + images = client.images.list() + all_tags = [tag for image in images for tag in image.tags] + + if image_tag not in all_tags: + client.images.build( + path=os.path.dirname(os.path.abspath(__file__)), + tag=image_tag, + rm=True, + ) + def __init__(self, code: Optional[str] = None, **kwargs) -> None: super().__init__(**kwargs) if code is not None: + self._verify_docker_image() self.code = code self.description = "Interprets Python code in a Docker container. ALWAYS PRINT the final result and the output of the code" self.args_schema = FixedCodeInterpreterSchemaSchema @@ -67,7 +86,8 @@ class CodeInterpreterTool(BaseTool): cmd_to_run = f'python3 -c "{code}"' exec_result = container.exec_run(cmd_to_run) - container.stop().remove() + container.stop() + container.remove() if exec_result.exit_code != 0: return f"Something went wrong while running the code: \n{exec_result.output.decode('utf-8')}" From 161c72b29f553f6b3758b9408ff9423349c5bba7 Mon Sep 17 00:00:00 2001 From: Eduardo Chiarotti Date: Thu, 20 Jun 2024 21:55:25 -0300 Subject: [PATCH 09/32] feat: update README --- src/crewai_tools/tools/code_interpreter_tool/README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/crewai_tools/tools/code_interpreter_tool/README.md b/src/crewai_tools/tools/code_interpreter_tool/README.md index 672c86f21..e66a82e39 100644 --- a/src/crewai_tools/tools/code_interpreter_tool/README.md +++ b/src/crewai_tools/tools/code_interpreter_tool/README.md @@ -3,6 +3,8 @@ ## Description This tool is used to give the Agent the ability to run code (Python3) from the code generated by the Agent itself. The code is executed in a sandboxed environment, so it is safe to run any code. +It is incredible useful since it allows the Agent to generate code, run it in the same environment, get the result and use it to make decisions. + ## Requirements - Docker From 2f80840c748e5b501bf688cff95713c1c5c18ac2 Mon Sep 17 00:00:00 2001 From: Jakub Strnad Date: Fri, 21 Jun 2024 15:06:17 +0200 Subject: [PATCH 10/32] fix: Ensure tools handle parameters passed post-creation correctly (#3) - Fixed an issue where multiple tools failed to function if parameters were provided after tool creation. - Updated tools to correctly process source file/URL passed by the agent post-creation as per documentation. Closes #<47> --- .../tools/code_docs_search_tool/code_docs_search_tool.py | 2 +- src/crewai_tools/tools/csv_search_tool/csv_search_tool.py | 2 +- .../tools/directory_search_tool/directory_search_tool.py | 2 +- src/crewai_tools/tools/docx_search_tool/docx_search_tool.py | 2 +- src/crewai_tools/tools/github_search_tool/github_search_tool.py | 2 +- src/crewai_tools/tools/json_search_tool/json_search_tool.py | 2 +- src/crewai_tools/tools/mdx_seach_tool/mdx_search_tool.py | 2 +- src/crewai_tools/tools/pg_seach_tool/pg_search_tool.py | 2 +- src/crewai_tools/tools/txt_search_tool/txt_search_tool.py | 2 +- src/crewai_tools/tools/website_search/website_search_tool.py | 2 +- src/crewai_tools/tools/xml_search_tool/xml_search_tool.py | 2 +- .../youtube_channel_search_tool/youtube_channel_search_tool.py | 2 +- .../youtube_video_search_tool/youtube_video_search_tool.py | 2 +- 13 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/crewai_tools/tools/code_docs_search_tool/code_docs_search_tool.py b/src/crewai_tools/tools/code_docs_search_tool/code_docs_search_tool.py index a69ea7eb4..899943511 100644 --- a/src/crewai_tools/tools/code_docs_search_tool/code_docs_search_tool.py +++ b/src/crewai_tools/tools/code_docs_search_tool/code_docs_search_tool.py @@ -57,4 +57,4 @@ class CodeDocsSearchTool(RagTool): search_query: str, **kwargs: Any, ) -> Any: - return super()._run(query=search_query) + return super()._run(query=search_query, **kwargs) diff --git a/src/crewai_tools/tools/csv_search_tool/csv_search_tool.py b/src/crewai_tools/tools/csv_search_tool/csv_search_tool.py index a04f227ca..9d0509f88 100644 --- a/src/crewai_tools/tools/csv_search_tool/csv_search_tool.py +++ b/src/crewai_tools/tools/csv_search_tool/csv_search_tool.py @@ -57,4 +57,4 @@ class CSVSearchTool(RagTool): search_query: str, **kwargs: Any, ) -> Any: - return super()._run(query=search_query) + return super()._run(query=search_query, **kwargs) diff --git a/src/crewai_tools/tools/directory_search_tool/directory_search_tool.py b/src/crewai_tools/tools/directory_search_tool/directory_search_tool.py index 9a988a7fa..a06229081 100644 --- a/src/crewai_tools/tools/directory_search_tool/directory_search_tool.py +++ b/src/crewai_tools/tools/directory_search_tool/directory_search_tool.py @@ -57,4 +57,4 @@ class DirectorySearchTool(RagTool): search_query: str, **kwargs: Any, ) -> Any: - return super()._run(query=search_query) + return super()._run(query=search_query, **kwargs) diff --git a/src/crewai_tools/tools/docx_search_tool/docx_search_tool.py b/src/crewai_tools/tools/docx_search_tool/docx_search_tool.py index 96bb4721b..b60dfd0f5 100644 --- a/src/crewai_tools/tools/docx_search_tool/docx_search_tool.py +++ b/src/crewai_tools/tools/docx_search_tool/docx_search_tool.py @@ -63,4 +63,4 @@ class DOCXSearchTool(RagTool): docx = kwargs.get("docx") if docx is not None: self.add(docx) - return super()._run(query=search_query) + return super()._run(query=search_query, **kwargs) diff --git a/src/crewai_tools/tools/github_search_tool/github_search_tool.py b/src/crewai_tools/tools/github_search_tool/github_search_tool.py index 5bfa65542..2ec39c8c0 100644 --- a/src/crewai_tools/tools/github_search_tool/github_search_tool.py +++ b/src/crewai_tools/tools/github_search_tool/github_search_tool.py @@ -68,4 +68,4 @@ class GithubSearchTool(RagTool): search_query: str, **kwargs: Any, ) -> Any: - return super()._run(query=search_query) + return super()._run(query=search_query, **kwargs) diff --git a/src/crewai_tools/tools/json_search_tool/json_search_tool.py b/src/crewai_tools/tools/json_search_tool/json_search_tool.py index 102cd89ad..930438c88 100644 --- a/src/crewai_tools/tools/json_search_tool/json_search_tool.py +++ b/src/crewai_tools/tools/json_search_tool/json_search_tool.py @@ -57,4 +57,4 @@ class JSONSearchTool(RagTool): search_query: str, **kwargs: Any, ) -> Any: - return super()._run(query=search_query) + return super()._run(query=search_query, **kwargs) diff --git a/src/crewai_tools/tools/mdx_seach_tool/mdx_search_tool.py b/src/crewai_tools/tools/mdx_seach_tool/mdx_search_tool.py index 99bd37348..69572140b 100644 --- a/src/crewai_tools/tools/mdx_seach_tool/mdx_search_tool.py +++ b/src/crewai_tools/tools/mdx_seach_tool/mdx_search_tool.py @@ -57,4 +57,4 @@ class MDXSearchTool(RagTool): search_query: str, **kwargs: Any, ) -> Any: - return super()._run(query=search_query) + return super()._run(query=search_query, **kwargs) diff --git a/src/crewai_tools/tools/pg_seach_tool/pg_search_tool.py b/src/crewai_tools/tools/pg_seach_tool/pg_search_tool.py index 226fb1ddd..6f9ea2901 100644 --- a/src/crewai_tools/tools/pg_seach_tool/pg_search_tool.py +++ b/src/crewai_tools/tools/pg_seach_tool/pg_search_tool.py @@ -41,4 +41,4 @@ class PGSearchTool(RagTool): search_query: str, **kwargs: Any, ) -> Any: - return super()._run(query=search_query) + return super()._run(query=search_query, **kwargs) diff --git a/src/crewai_tools/tools/txt_search_tool/txt_search_tool.py b/src/crewai_tools/tools/txt_search_tool/txt_search_tool.py index 921e633e8..5dbaed4d4 100644 --- a/src/crewai_tools/tools/txt_search_tool/txt_search_tool.py +++ b/src/crewai_tools/tools/txt_search_tool/txt_search_tool.py @@ -57,4 +57,4 @@ class TXTSearchTool(RagTool): search_query: str, **kwargs: Any, ) -> Any: - return super()._run(query=search_query) + return super()._run(query=search_query, **kwargs) diff --git a/src/crewai_tools/tools/website_search/website_search_tool.py b/src/crewai_tools/tools/website_search/website_search_tool.py index cfe163ae8..1ff587f00 100644 --- a/src/crewai_tools/tools/website_search/website_search_tool.py +++ b/src/crewai_tools/tools/website_search/website_search_tool.py @@ -57,4 +57,4 @@ class WebsiteSearchTool(RagTool): search_query: str, **kwargs: Any, ) -> Any: - return super()._run(query=search_query) + return super()._run(query=search_query, **kwargs) diff --git a/src/crewai_tools/tools/xml_search_tool/xml_search_tool.py b/src/crewai_tools/tools/xml_search_tool/xml_search_tool.py index 53fd73248..0346d484e 100644 --- a/src/crewai_tools/tools/xml_search_tool/xml_search_tool.py +++ b/src/crewai_tools/tools/xml_search_tool/xml_search_tool.py @@ -57,4 +57,4 @@ class XMLSearchTool(RagTool): search_query: str, **kwargs: Any, ) -> Any: - return super()._run(query=search_query) + return super()._run(query=search_query, **kwargs) diff --git a/src/crewai_tools/tools/youtube_channel_search_tool/youtube_channel_search_tool.py b/src/crewai_tools/tools/youtube_channel_search_tool/youtube_channel_search_tool.py index 8e9591be8..2edc0026b 100644 --- a/src/crewai_tools/tools/youtube_channel_search_tool/youtube_channel_search_tool.py +++ b/src/crewai_tools/tools/youtube_channel_search_tool/youtube_channel_search_tool.py @@ -60,4 +60,4 @@ class YoutubeChannelSearchTool(RagTool): search_query: str, **kwargs: Any, ) -> Any: - return super()._run(query=search_query) + return super()._run(query=search_query, **kwargs) diff --git a/src/crewai_tools/tools/youtube_video_search_tool/youtube_video_search_tool.py b/src/crewai_tools/tools/youtube_video_search_tool/youtube_video_search_tool.py index f1caa1b9c..77d25752e 100644 --- a/src/crewai_tools/tools/youtube_video_search_tool/youtube_video_search_tool.py +++ b/src/crewai_tools/tools/youtube_video_search_tool/youtube_video_search_tool.py @@ -57,4 +57,4 @@ class YoutubeVideoSearchTool(RagTool): search_query: str, **kwargs: Any, ) -> Any: - return super()._run(query=search_query) + return super()._run(query=search_query, **kwargs) From c97678bb1106ba8e7c0a9837736f89a5df5f2cd1 Mon Sep 17 00:00:00 2001 From: Naman Garg Date: Fri, 21 Jun 2024 14:48:24 -0700 Subject: [PATCH 11/32] add multion tool --- src/crewai_tools/__init__.py | 53 +++++++++--------- src/crewai_tools/tools/__init__.py | 17 ++++-- src/crewai_tools/tools/multion_tool/README.md | 31 ++++++++++ .../tools/multion_tool/example.py | 29 ++++++++++ .../tools/multion_tool/multion_tool.py | 56 +++++++++++++++++++ 5 files changed, 154 insertions(+), 32 deletions(-) create mode 100644 src/crewai_tools/tools/multion_tool/README.md create mode 100644 src/crewai_tools/tools/multion_tool/example.py create mode 100644 src/crewai_tools/tools/multion_tool/multion_tool.py diff --git a/src/crewai_tools/__init__.py b/src/crewai_tools/__init__.py index a51d70449..d0c4746df 100644 --- a/src/crewai_tools/__init__.py +++ b/src/crewai_tools/__init__.py @@ -1,27 +1,28 @@ -from .tools.base_tool import BaseTool, Tool, tool from .tools import ( - BrowserbaseLoadTool, - CodeDocsSearchTool, - CSVSearchTool, - DirectorySearchTool, - DOCXSearchTool, - DirectoryReadTool, - EXASearchTool, - FileReadTool, - GithubSearchTool, - SerperDevTool, - TXTSearchTool, - JSONSearchTool, - MDXSearchTool, - PDFSearchTool, - PGSearchTool, - RagTool, - ScrapeElementFromWebsiteTool, - ScrapeWebsiteTool, - SeleniumScrapingTool, - WebsiteSearchTool, - XMLSearchTool, - YoutubeChannelSearchTool, - YoutubeVideoSearchTool, - LlamaIndexTool -) \ No newline at end of file + BrowserbaseLoadTool, + CodeDocsSearchTool, + CSVSearchTool, + DirectoryReadTool, + DirectorySearchTool, + DOCXSearchTool, + EXASearchTool, + FileReadTool, + GithubSearchTool, + JSONSearchTool, + LlamaIndexTool, + MDXSearchTool, + MultiOnTool, + PDFSearchTool, + PGSearchTool, + RagTool, + ScrapeElementFromWebsiteTool, + ScrapeWebsiteTool, + SeleniumScrapingTool, + SerperDevTool, + TXTSearchTool, + WebsiteSearchTool, + XMLSearchTool, + YoutubeChannelSearchTool, + YoutubeVideoSearchTool, +) +from .tools.base_tool import BaseTool, Tool, tool diff --git a/src/crewai_tools/tools/__init__.py b/src/crewai_tools/tools/__init__.py index 4da0c0337..11074bbe3 100644 --- a/src/crewai_tools/tools/__init__.py +++ b/src/crewai_tools/tools/__init__.py @@ -1,24 +1,29 @@ from .browserbase_load_tool.browserbase_load_tool import BrowserbaseLoadTool from .code_docs_search_tool.code_docs_search_tool import CodeDocsSearchTool from .csv_search_tool.csv_search_tool import CSVSearchTool -from .directory_search_tool.directory_search_tool import DirectorySearchTool from .directory_read_tool.directory_read_tool import DirectoryReadTool +from .directory_search_tool.directory_search_tool import DirectorySearchTool from .docx_search_tool.docx_search_tool import DOCXSearchTool from .exa_tools.exa_search_tool import EXASearchTool from .file_read_tool.file_read_tool import FileReadTool from .github_search_tool.github_search_tool import GithubSearchTool -from .serper_dev_tool.serper_dev_tool import SerperDevTool -from .txt_search_tool.txt_search_tool import TXTSearchTool from .json_search_tool.json_search_tool import JSONSearchTool +from .llamaindex_tool.llamaindex_tool import LlamaIndexTool from .mdx_seach_tool.mdx_search_tool import MDXSearchTool +from .multion_tool.multion_tool import MultiOnTool from .pdf_search_tool.pdf_search_tool import PDFSearchTool from .pg_seach_tool.pg_search_tool import PGSearchTool from .rag.rag_tool import RagTool -from .scrape_element_from_website.scrape_element_from_website import ScrapeElementFromWebsiteTool +from .scrape_element_from_website.scrape_element_from_website import ( + ScrapeElementFromWebsiteTool, +) from .scrape_website_tool.scrape_website_tool import ScrapeWebsiteTool from .selenium_scraping_tool.selenium_scraping_tool import SeleniumScrapingTool +from .serper_dev_tool.serper_dev_tool import SerperDevTool +from .txt_search_tool.txt_search_tool import TXTSearchTool from .website_search.website_search_tool import WebsiteSearchTool from .xml_search_tool.xml_search_tool import XMLSearchTool -from .youtube_channel_search_tool.youtube_channel_search_tool import YoutubeChannelSearchTool +from .youtube_channel_search_tool.youtube_channel_search_tool import ( + YoutubeChannelSearchTool, +) from .youtube_video_search_tool.youtube_video_search_tool import YoutubeVideoSearchTool -from .llamaindex_tool.llamaindex_tool import LlamaIndexTool diff --git a/src/crewai_tools/tools/multion_tool/README.md b/src/crewai_tools/tools/multion_tool/README.md new file mode 100644 index 000000000..0cbbbb2cd --- /dev/null +++ b/src/crewai_tools/tools/multion_tool/README.md @@ -0,0 +1,31 @@ +# MultiOnTool Documentation + +## Description +The MultiOnTool, integrated within the crewai_tools package, empowers CrewAI agents with the capability to navigate and interact with the web through natural language instructions. Leveraging the Multion API, this tool facilitates seamless web browsing, making it an essential asset for projects requiring dynamic web data interaction. + +## Installation +Ensure the `crewai[tools]` package is installed in your environment to use the MultiOnTool. If it's not already installed, you can add it using the command below: + +## Example +The following example demonstrates how to initialize the tool and execute a search with a given query: + +```python +from crewai_tools import MultiOnTool + +# Initialize the tool from a MultiOn Tool +multion_tool = MultiOnTool(api_key= "YOUR_MULTION_API_KEY", local=False) + +``` + +## Arguments + +- `api_key`: Specifies Browserbase API key. Defaults is the `BROWSERBASE_API_KEY` environment variable. +- `local`: Optional. Use the local flag to run the agent locally on your browser. + +## Steps to Get Started +To effectively use the `MultiOnTool`, follow these steps: + +1. **Install CrewAI**: Confirm that the `crewai[tools]` package is installed in your Python environment. +2. **Install and use MultiOn**: Follow MultiOn documentation (https://docs.multion.ai/). + + diff --git a/src/crewai_tools/tools/multion_tool/example.py b/src/crewai_tools/tools/multion_tool/example.py new file mode 100644 index 000000000..ec69e5cdf --- /dev/null +++ b/src/crewai_tools/tools/multion_tool/example.py @@ -0,0 +1,29 @@ +import os + +from crewai import Agent, Crew, Task +from multion_tool import MultiOnTool + +os.environ["OPENAI_API_KEY"] = "Your Key" + +multion_browse_tool = MultiOnTool(api_key="Your Key") + +# Create a new agent +Browser = Agent( + role="Browser Agent", + goal="control web browsers using natural language ", + backstory="An expert browsing agent.", + tools=[multion_browse_tool], + verbose=True, +) + +# Define tasks +browse = Task( + description="Summarize the top 3 trending AI News headlines", + expected_output="A summary of the top 3 trending AI News headlines", + agent=Browser, +) + + +crew = Crew(agents=[Browser], tasks=[browse]) + +crew.kickoff() diff --git a/src/crewai_tools/tools/multion_tool/multion_tool.py b/src/crewai_tools/tools/multion_tool/multion_tool.py new file mode 100644 index 000000000..1253627a2 --- /dev/null +++ b/src/crewai_tools/tools/multion_tool/multion_tool.py @@ -0,0 +1,56 @@ +"""Multion tool spec.""" + +from typing import Any, Optional + +from crewai_tools.tools.base_tool import BaseTool + + +class MultiOnTool(BaseTool): + """Tool to wrap MultiOn Browse Capabilities.""" + + name: str = "Multion Browse Tool" + description: str = """Multion gives the ability for LLMs to control web browsers using natural language instructions. + If the status is 'CONTINUE', reissue the same instruction to continue execution + """ + multion: Optional[Any] = None + session_id: Optional[str] = None + local: bool = False + + def __init__(self, api_key: Optional[str] = None, local: bool = False, **kwargs): + super().__init__(**kwargs) + try: + from multion.client import MultiOn # type: ignore + except ImportError: + raise ImportError( + "`multion` package not found, please run `pip install multion`" + ) + self.session_id = None + self.local = local + self.multion = MultiOn(api_key=api_key) + + def _run( + self, + cmd: str, + *args: Any, + **kwargs: Any, + ) -> str: + """ + Run the Multion client with the given command. + + Args: + cmd (str): The detailed and specific natural language instructrion for web browsing + + *args (Any): Additional arguments to pass to the Multion client + **kwargs (Any): Additional keyword arguments to pass to the Multion client + """ + + browse = self.multion.browse( + cmd=cmd, + session_id=self.session_id, + local=self.local, + *args, + **kwargs, + ) + self.session_id = browse.session_id + + return browse.message + "\n\n STATUS: " + browse.status From d84a61657274f925d43e7b0f9807d47589393904 Mon Sep 17 00:00:00 2001 From: Naman Garg Date: Fri, 21 Jun 2024 15:01:08 -0700 Subject: [PATCH 12/32] update local option description in readme --- src/crewai_tools/tools/multion_tool/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/crewai_tools/tools/multion_tool/README.md b/src/crewai_tools/tools/multion_tool/README.md index 0cbbbb2cd..608931166 100644 --- a/src/crewai_tools/tools/multion_tool/README.md +++ b/src/crewai_tools/tools/multion_tool/README.md @@ -20,7 +20,7 @@ multion_tool = MultiOnTool(api_key= "YOUR_MULTION_API_KEY", local=False) ## Arguments - `api_key`: Specifies Browserbase API key. Defaults is the `BROWSERBASE_API_KEY` environment variable. -- `local`: Optional. Use the local flag to run the agent locally on your browser. +- `local`: Optional. Use the local flag set as "true" to run the agent locally on your browser. Make sure the multion browser extension is installed and API Enabled is checked. ## Steps to Get Started To effectively use the `MultiOnTool`, follow these steps: From f9c803a8c1f13e75c526125994aa12127880b7fb Mon Sep 17 00:00:00 2001 From: Marcelo Busana Date: Sun, 23 Jun 2024 15:33:55 -0300 Subject: [PATCH 13/32] Fix: Selenium incorrect firefox options import --- .../tools/selenium_scraping_tool/selenium_scraping_tool.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 d0c420fc9..6bf8ff5f1 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 @@ -5,7 +5,7 @@ from pydantic.v1 import BaseModel, Field from bs4 import BeautifulSoup from selenium import webdriver from selenium.webdriver.common.by import By -from selenium.webdriver.firefox.options import Options +from selenium.webdriver.chrome.options import Options from ..base_tool import BaseTool From d4449ee5f0044e9324325bd77acc788c7d72ed1f Mon Sep 17 00:00:00 2001 From: angrybayblade Date: Mon, 24 Jun 2024 20:48:52 +0530 Subject: [PATCH 14/32] feat: add composio CrewAI tool wrapper --- .../tools/composio_tool/README.md | 30 +++++++++ .../tools/composio_tool/composio_tool.py | 62 +++++++++++++++++++ 2 files changed, 92 insertions(+) create mode 100644 src/crewai_tools/tools/composio_tool/README.md create mode 100644 src/crewai_tools/tools/composio_tool/composio_tool.py diff --git a/src/crewai_tools/tools/composio_tool/README.md b/src/crewai_tools/tools/composio_tool/README.md new file mode 100644 index 000000000..ef7cf1edb --- /dev/null +++ b/src/crewai_tools/tools/composio_tool/README.md @@ -0,0 +1,30 @@ +# ComposioTool Documentation + +## Description + +This tools is a wrapper around the composio toolset and gives your agent access to a wide variety of tools from the composio SDK. + +## Installation + +To incorporate this tool into your project, follow the installation instructions below: + +```shell +pip install composio-core +pip install 'crewai[tools]' +``` + +## Example + +The following example demonstrates how to initialize the tool and execute a mathematical operation: + +```python + +from composio import Action + +from crewai_tools.tools.composio_tool.composio_tool import ComposioTool + +tool = ComposioTool.from_tool( + tool=Action.MATHEMATICAL_CALCULATOR, +) +``` + diff --git a/src/crewai_tools/tools/composio_tool/composio_tool.py b/src/crewai_tools/tools/composio_tool/composio_tool.py new file mode 100644 index 000000000..e08fbde31 --- /dev/null +++ b/src/crewai_tools/tools/composio_tool/composio_tool.py @@ -0,0 +1,62 @@ +""" +Composio tools wrapper. +""" + +import typing as t + +import typing_extensions as te + +from crewai_tools.tools.base_tool import BaseTool + + +class ComposioTool(BaseTool): + """Wrapper for composio tools.""" + + composio_action: t.Callable + + def _run(self, *args: t.Any, **kwargs: t.Any) -> t.Any: + """Run the composio action with given arguments.""" + return self.composio_action(*args, **kwargs) + + @classmethod + def from_tool(cls, tool: t.Any, **kwargs: t.Any) -> te.Self: + """Wrap a composio tool as crewAI tool.""" + + from composio import Action, ComposioToolSet + from composio.constants import DEFAULT_ENTITY_ID + from composio.utils.shared import json_schema_to_model + + toolset = ComposioToolSet() + if not isinstance(tool, Action): + tool = Action.from_action(name=tool) + + tool = t.cast(Action, tool) + (action,) = toolset.get_action_schemas(actions=[tool]) + schema = action.model_dump(exclude_none=True) + entity_id = kwargs.pop("entity_id", DEFAULT_ENTITY_ID) + + def function(**kwargs: t.Any) -> t.Dict: + """Wrapper function for composio action.""" + return toolset.execute_action( + action=Action.from_app_and_action( + app=schema["appName"], + name=schema["name"], + ), + params=kwargs, + entity_id=entity_id, + ) + + function.__name__ = schema["name"] + function.__doc__ = schema["description"] + + return cls( + name=schema["name"], + description=schema["description"], + args_schema=json_schema_to_model( + action.parameters.model_dump( + exclude_none=True, + ) + ), + composio_action=function, + **kwargs + ) From f5d092f6a3a895e4c149ad15a2eb5a3756c47248 Mon Sep 17 00:00:00 2001 From: Seth Donaldson Date: Wed, 26 Jun 2024 15:46:14 -0400 Subject: [PATCH 15/32] clean copy of embedchain_adapter.py --- .../adapters/pdf_embedchain_adapter.py | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 src/crewai_tools/adapters/pdf_embedchain_adapter.py diff --git a/src/crewai_tools/adapters/pdf_embedchain_adapter.py b/src/crewai_tools/adapters/pdf_embedchain_adapter.py new file mode 100644 index 000000000..446aab96c --- /dev/null +++ b/src/crewai_tools/adapters/pdf_embedchain_adapter.py @@ -0,0 +1,25 @@ +from typing import Any + +from embedchain import App + +from crewai_tools.tools.rag.rag_tool import Adapter + + +class EmbedchainAdapter(Adapter): + embedchain_app: App + summarize: bool = False + + def query(self, question: str) -> str: + result, sources = self.embedchain_app.query( + question, citations=True, dry_run=(not self.summarize) + ) + if self.summarize: + return result + return "\n\n".join([source[0] for source in sources]) + + def add( + self, + *args: Any, + **kwargs: Any, + ) -> None: + self.embedchain_app.add(*args, **kwargs) From a95f5c27c68fa846139ae81ec9478a4b9f91c553 Mon Sep 17 00:00:00 2001 From: Seth Donaldson Date: Wed, 26 Jun 2024 15:52:54 -0400 Subject: [PATCH 16/32] Create PDFEmbedchainAdapter class and utilize it in PDFSearchTool --- .../adapters/pdf_embedchain_adapter.py | 13 ++++++++++--- .../tools/pdf_search_tool/pdf_search_tool.py | 17 +++++++++++++++++ 2 files changed, 27 insertions(+), 3 deletions(-) diff --git a/src/crewai_tools/adapters/pdf_embedchain_adapter.py b/src/crewai_tools/adapters/pdf_embedchain_adapter.py index 446aab96c..12557c971 100644 --- a/src/crewai_tools/adapters/pdf_embedchain_adapter.py +++ b/src/crewai_tools/adapters/pdf_embedchain_adapter.py @@ -1,17 +1,23 @@ -from typing import Any +from typing import Any, Optional from embedchain import App from crewai_tools.tools.rag.rag_tool import Adapter -class EmbedchainAdapter(Adapter): +class PDFEmbedchainAdapter(Adapter): embedchain_app: App summarize: bool = False + src: Optional[str] = None def query(self, question: str) -> str: + where = ( + {"app_id": self.embedchain_app.config.id, "source": self.src} + if self.src + else None + ) result, sources = self.embedchain_app.query( - question, citations=True, dry_run=(not self.summarize) + question, citations=True, dry_run=(not self.summarize), where=where ) if self.summarize: return result @@ -22,4 +28,5 @@ class EmbedchainAdapter(Adapter): *args: Any, **kwargs: Any, ) -> None: + self.src = args[0] if args else None self.embedchain_app.add(*args, **kwargs) diff --git a/src/crewai_tools/tools/pdf_search_tool/pdf_search_tool.py b/src/crewai_tools/tools/pdf_search_tool/pdf_search_tool.py index af95ae0bf..48df8e966 100644 --- a/src/crewai_tools/tools/pdf_search_tool/pdf_search_tool.py +++ b/src/crewai_tools/tools/pdf_search_tool/pdf_search_tool.py @@ -1,6 +1,7 @@ from typing import Any, Optional, Type from embedchain.models.data_type import DataType +from pydantic import model_validator from pydantic.v1 import BaseModel, Field from ..rag.rag_tool import RagTool @@ -35,6 +36,22 @@ class PDFSearchTool(RagTool): self.args_schema = FixedPDFSearchToolSchema self._generate_description() + @model_validator(mode="after") + def _set_default_adapter(self): + if isinstance(self.adapter, RagTool._AdapterPlaceholder): + from embedchain import App + + from crewai_tools.adapters.pdf_embedchain_adapter import ( + PDFEmbedchainAdapter, + ) + + app = App.from_config(config=self.config) if self.config else App() + self.adapter = PDFEmbedchainAdapter( + embedchain_app=app, summarize=self.summarize + ) + + return self + def add( self, *args: Any, From 41478abdf5665e9a912716e5b7d62a36db1e6d69 Mon Sep 17 00:00:00 2001 From: angrybayblade Date: Thu, 27 Jun 2024 11:36:52 +0530 Subject: [PATCH 17/32] feat: define `ComposioTool` in the top level imports --- src/crewai_tools/__init__.py | 53 +++++++++++++++--------------- src/crewai_tools/tools/__init__.py | 17 ++++++---- 2 files changed, 38 insertions(+), 32 deletions(-) diff --git a/src/crewai_tools/__init__.py b/src/crewai_tools/__init__.py index a51d70449..214dbbb31 100644 --- a/src/crewai_tools/__init__.py +++ b/src/crewai_tools/__init__.py @@ -1,27 +1,28 @@ -from .tools.base_tool import BaseTool, Tool, tool from .tools import ( - BrowserbaseLoadTool, - CodeDocsSearchTool, - CSVSearchTool, - DirectorySearchTool, - DOCXSearchTool, - DirectoryReadTool, - EXASearchTool, - FileReadTool, - GithubSearchTool, - SerperDevTool, - TXTSearchTool, - JSONSearchTool, - MDXSearchTool, - PDFSearchTool, - PGSearchTool, - RagTool, - ScrapeElementFromWebsiteTool, - ScrapeWebsiteTool, - SeleniumScrapingTool, - WebsiteSearchTool, - XMLSearchTool, - YoutubeChannelSearchTool, - YoutubeVideoSearchTool, - LlamaIndexTool -) \ No newline at end of file + BrowserbaseLoadTool, + CodeDocsSearchTool, + ComposioTool, + CSVSearchTool, + DirectoryReadTool, + DirectorySearchTool, + DOCXSearchTool, + EXASearchTool, + FileReadTool, + GithubSearchTool, + JSONSearchTool, + LlamaIndexTool, + MDXSearchTool, + PDFSearchTool, + PGSearchTool, + RagTool, + ScrapeElementFromWebsiteTool, + ScrapeWebsiteTool, + SeleniumScrapingTool, + SerperDevTool, + TXTSearchTool, + WebsiteSearchTool, + XMLSearchTool, + YoutubeChannelSearchTool, + YoutubeVideoSearchTool, +) +from .tools.base_tool import BaseTool, Tool, tool diff --git a/src/crewai_tools/tools/__init__.py b/src/crewai_tools/tools/__init__.py index 4da0c0337..df0ec7286 100644 --- a/src/crewai_tools/tools/__init__.py +++ b/src/crewai_tools/tools/__init__.py @@ -1,24 +1,29 @@ from .browserbase_load_tool.browserbase_load_tool import BrowserbaseLoadTool from .code_docs_search_tool.code_docs_search_tool import CodeDocsSearchTool +from .composio_tool.composio_tool import ComposioTool from .csv_search_tool.csv_search_tool import CSVSearchTool -from .directory_search_tool.directory_search_tool import DirectorySearchTool from .directory_read_tool.directory_read_tool import DirectoryReadTool +from .directory_search_tool.directory_search_tool import DirectorySearchTool from .docx_search_tool.docx_search_tool import DOCXSearchTool from .exa_tools.exa_search_tool import EXASearchTool from .file_read_tool.file_read_tool import FileReadTool from .github_search_tool.github_search_tool import GithubSearchTool -from .serper_dev_tool.serper_dev_tool import SerperDevTool -from .txt_search_tool.txt_search_tool import TXTSearchTool from .json_search_tool.json_search_tool import JSONSearchTool +from .llamaindex_tool.llamaindex_tool import LlamaIndexTool from .mdx_seach_tool.mdx_search_tool import MDXSearchTool from .pdf_search_tool.pdf_search_tool import PDFSearchTool from .pg_seach_tool.pg_search_tool import PGSearchTool from .rag.rag_tool import RagTool -from .scrape_element_from_website.scrape_element_from_website import ScrapeElementFromWebsiteTool +from .scrape_element_from_website.scrape_element_from_website import ( + ScrapeElementFromWebsiteTool, +) from .scrape_website_tool.scrape_website_tool import ScrapeWebsiteTool from .selenium_scraping_tool.selenium_scraping_tool import SeleniumScrapingTool +from .serper_dev_tool.serper_dev_tool import SerperDevTool +from .txt_search_tool.txt_search_tool import TXTSearchTool from .website_search.website_search_tool import WebsiteSearchTool from .xml_search_tool.xml_search_tool import XMLSearchTool -from .youtube_channel_search_tool.youtube_channel_search_tool import YoutubeChannelSearchTool +from .youtube_channel_search_tool.youtube_channel_search_tool import ( + YoutubeChannelSearchTool, +) from .youtube_video_search_tool.youtube_video_search_tool import YoutubeVideoSearchTool -from .llamaindex_tool.llamaindex_tool import LlamaIndexTool From be6e1a79dd136ed8b3e36947203e1e7b91f95eee Mon Sep 17 00:00:00 2001 From: angrybayblade Date: Thu, 27 Jun 2024 11:39:42 +0530 Subject: [PATCH 18/32] feat: add search utility methods --- .../tools/composio_tool/composio_tool.py | 52 ++++++++++++++++--- 1 file changed, 45 insertions(+), 7 deletions(-) diff --git a/src/crewai_tools/tools/composio_tool/composio_tool.py b/src/crewai_tools/tools/composio_tool/composio_tool.py index e08fbde31..664898ce3 100644 --- a/src/crewai_tools/tools/composio_tool/composio_tool.py +++ b/src/crewai_tools/tools/composio_tool/composio_tool.py @@ -19,7 +19,11 @@ class ComposioTool(BaseTool): return self.composio_action(*args, **kwargs) @classmethod - def from_tool(cls, tool: t.Any, **kwargs: t.Any) -> te.Self: + def from_tool( + cls, + tool: t.Any, + **kwargs: t.Any, + ) -> te.Self: """Wrap a composio tool as crewAI tool.""" from composio import Action, ComposioToolSet @@ -28,7 +32,7 @@ class ComposioTool(BaseTool): toolset = ComposioToolSet() if not isinstance(tool, Action): - tool = Action.from_action(name=tool) + tool = Action(tool) tool = t.cast(Action, tool) (action,) = toolset.get_action_schemas(actions=[tool]) @@ -38,10 +42,7 @@ class ComposioTool(BaseTool): def function(**kwargs: t.Any) -> t.Dict: """Wrapper function for composio action.""" return toolset.execute_action( - action=Action.from_app_and_action( - app=schema["appName"], - name=schema["name"], - ), + action=Action(schema["name"]), params=kwargs, entity_id=entity_id, ) @@ -58,5 +59,42 @@ class ComposioTool(BaseTool): ) ), composio_action=function, - **kwargs + **kwargs, ) + + @classmethod + def from_app( + cls, + app: t.Any, + tags: t.Optional[t.List[str]] = None, + **kwargs: t.Any, + ) -> t.List[te.Self]: + """Create toolset from an app.""" + from composio import App + + if not isinstance(app, App): + app = App(app) + + return [ + cls.from_tool(tool=action, **kwargs) + for action in app.get_actions(tags=tags) + ] + + @classmethod + def from_use_case( + cls, + *apps: t.Any, + use_case: str, + **kwargs: t.Any, + ) -> t.List[te.Self]: + """Create toolset from an app.""" + if len(apps) == 0: + raise ValueError( + "You need to provide at least one app name to search by use case" + ) + + from composio import ComposioToolSet + + toolset = ComposioToolSet() + actions = toolset.find_actions_by_use_case(*apps, use_case=use_case) + return [cls.from_tool(tool=action, **kwargs) for action in actions] From ab484172ef2d3237fb14416ca2894db3568e216b Mon Sep 17 00:00:00 2001 From: angrybayblade Date: Thu, 27 Jun 2024 11:40:02 +0530 Subject: [PATCH 19/32] chore: update readme --- .../tools/composio_tool/README.md | 49 +++++++++++++++++-- 1 file changed, 44 insertions(+), 5 deletions(-) diff --git a/src/crewai_tools/tools/composio_tool/README.md b/src/crewai_tools/tools/composio_tool/README.md index ef7cf1edb..4a4db85d5 100644 --- a/src/crewai_tools/tools/composio_tool/README.md +++ b/src/crewai_tools/tools/composio_tool/README.md @@ -15,16 +15,55 @@ pip install 'crewai[tools]' ## Example -The following example demonstrates how to initialize the tool and execute a mathematical operation: +The following example demonstrates how to initialize the tool and execute a github action: + +1. Initialize toolset ```python +from composio import App +from crewai_tools import ComposioTool +from crewai import Agent, Task -from composio import Action -from crewai_tools.tools.composio_tool.composio_tool import ComposioTool +tools = [ComposioTool.from_tool(tool=Action.GITHUB_ACTIVITY_STAR_REPO_FOR_AUTHENTICATED_USER)] +``` -tool = ComposioTool.from_tool( - tool=Action.MATHEMATICAL_CALCULATOR, +If you don't know what action you want to use, use `from_app` and `tags` filter to get relevant actions + +```python +tools = ComposioTool.from_app(app=App.GITHUB, tags=["important"]) +``` + +or use `from_use_case` to search relevant actions + +```python +tools = ComposioTool.from_use_case(App.GITHUB, use_case="Star a github repository") +``` + +2. Define agent + +```python +crewai_agent = Agent( + role="Github Agent", + goal="""You take action on Github using Github APIs""", + backstory=( + "You are AI agent that is responsible for taking actions on Github " + "on users behalf. You need to take action on Github using Github APIs" + ), + verbose=True, + tools=tools, ) ``` +3. Execute task + +```python +task = Task( + description="Star a repo ComposioHQ/composio on GitHub", + agent=crewai_agent, + expected_output="if the star happened", +) + +task.execute() +``` + From 369c03a257c6c11c5620d3bcf4e66310a649c193 Mon Sep 17 00:00:00 2001 From: angrybayblade Date: Thu, 27 Jun 2024 12:16:22 +0530 Subject: [PATCH 20/32] feat: add check for auth accounts --- .../tools/composio_tool/composio_tool.py | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/src/crewai_tools/tools/composio_tool/composio_tool.py b/src/crewai_tools/tools/composio_tool/composio_tool.py index 664898ce3..fd478eeb6 100644 --- a/src/crewai_tools/tools/composio_tool/composio_tool.py +++ b/src/crewai_tools/tools/composio_tool/composio_tool.py @@ -18,6 +18,26 @@ class ComposioTool(BaseTool): """Run the composio action with given arguments.""" return self.composio_action(*args, **kwargs) + @staticmethod + def _check_connected_account(tool: t.Any, toolset: t.Any) -> None: + """Check if connected account is required and if required it exists or not.""" + from composio import Action + from composio.client.collections import ConnectedAccountModel + + tool = t.cast(Action, tool) + if tool.no_auth: + return + + connections = t.cast( + t.List[ConnectedAccountModel], + toolset.client.connected_accounts.get(), + ) + if tool.app not in [connection.appUniqueId for connection in connections]: + raise RuntimeError( + f"No connected account found for app `{tool.app}`; " + f"Run `composio add {tool.app}` to fix this" + ) + @classmethod def from_tool( cls, @@ -35,6 +55,11 @@ class ComposioTool(BaseTool): tool = Action(tool) tool = t.cast(Action, tool) + cls._check_connected_account( + tool=tool, + toolset=toolset, + ) + (action,) = toolset.get_action_schemas(actions=[tool]) schema = action.model_dump(exclude_none=True) entity_id = kwargs.pop("entity_id", DEFAULT_ENTITY_ID) From 58354ec638326a4b406aa97d6038d2fc7d6df418 Mon Sep 17 00:00:00 2001 From: angrybayblade Date: Thu, 27 Jun 2024 12:23:05 +0530 Subject: [PATCH 21/32] chore: update README --- src/crewai_tools/tools/composio_tool/README.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/crewai_tools/tools/composio_tool/README.md b/src/crewai_tools/tools/composio_tool/README.md index 4a4db85d5..fe030cbd2 100644 --- a/src/crewai_tools/tools/composio_tool/README.md +++ b/src/crewai_tools/tools/composio_tool/README.md @@ -13,6 +13,8 @@ pip install composio-core pip install 'crewai[tools]' ``` +after the installation is complete, either run `composio login` or export your composio API key as `COMPOSIO_API_KEY`. + ## Example The following example demonstrates how to initialize the tool and execute a github action: @@ -45,7 +47,7 @@ tools = ComposioTool.from_use_case(App.GITHUB, use_case="Star a github repositor ```python crewai_agent = Agent( role="Github Agent", - goal="""You take action on Github using Github APIs""", + goal="You take action on Github using Github APIs", backstory=( "You are AI agent that is responsible for taking actions on Github " "on users behalf. You need to take action on Github using Github APIs" @@ -67,3 +69,4 @@ task = Task( task.execute() ``` +* More detailed list of tools can be found [here](https://app.composio.dev) From 9a8d88b8aa3890897d75814fd4b916d3dba706b7 Mon Sep 17 00:00:00 2001 From: angrybayblade Date: Thu, 27 Jun 2024 13:35:57 +0530 Subject: [PATCH 22/32] fix: merge `from_app` and `from_use_case` --- .../tools/composio_tool/README.md | 8 +-- .../tools/composio_tool/composio_tool.py | 59 +++++++++---------- 2 files changed, 32 insertions(+), 35 deletions(-) diff --git a/src/crewai_tools/tools/composio_tool/README.md b/src/crewai_tools/tools/composio_tool/README.md index fe030cbd2..18045e7f1 100644 --- a/src/crewai_tools/tools/composio_tool/README.md +++ b/src/crewai_tools/tools/composio_tool/README.md @@ -27,19 +27,19 @@ from crewai_tools import ComposioTool from crewai import Agent, Task -tools = [ComposioTool.from_tool(tool=Action.GITHUB_ACTIVITY_STAR_REPO_FOR_AUTHENTICATED_USER)] +tools = [ComposioTool.from_action(action=Action.GITHUB_ACTIVITY_STAR_REPO_FOR_AUTHENTICATED_USER)] ``` If you don't know what action you want to use, use `from_app` and `tags` filter to get relevant actions ```python -tools = ComposioTool.from_app(app=App.GITHUB, tags=["important"]) +tools = ComposioTool.from_app(App.GITHUB, tags=["important"]) ``` -or use `from_use_case` to search relevant actions +or use `use_case` to search relevant actions ```python -tools = ComposioTool.from_use_case(App.GITHUB, use_case="Star a github repository") +tools = ComposioTool.from_app(App.GITHUB, use_case="Star a github repository") ``` 2. Define agent diff --git a/src/crewai_tools/tools/composio_tool/composio_tool.py b/src/crewai_tools/tools/composio_tool/composio_tool.py index fd478eeb6..62068c0bd 100644 --- a/src/crewai_tools/tools/composio_tool/composio_tool.py +++ b/src/crewai_tools/tools/composio_tool/composio_tool.py @@ -39,9 +39,9 @@ class ComposioTool(BaseTool): ) @classmethod - def from_tool( + def from_action( cls, - tool: t.Any, + action: t.Any, **kwargs: t.Any, ) -> te.Self: """Wrap a composio tool as crewAI tool.""" @@ -51,17 +51,17 @@ class ComposioTool(BaseTool): from composio.utils.shared import json_schema_to_model toolset = ComposioToolSet() - if not isinstance(tool, Action): - tool = Action(tool) + if not isinstance(action, Action): + action = Action(action) - tool = t.cast(Action, tool) + action = t.cast(Action, action) cls._check_connected_account( - tool=tool, + tool=action, toolset=toolset, ) - (action,) = toolset.get_action_schemas(actions=[tool]) - schema = action.model_dump(exclude_none=True) + (action_schema,) = toolset.get_action_schemas(actions=[action]) + schema = action_schema.model_dump(exclude_none=True) entity_id = kwargs.pop("entity_id", DEFAULT_ENTITY_ID) def function(**kwargs: t.Any) -> t.Dict: @@ -79,7 +79,7 @@ class ComposioTool(BaseTool): name=schema["name"], description=schema["description"], args_schema=json_schema_to_model( - action.parameters.model_dump( + action_schema.parameters.model_dump( exclude_none=True, ) ), @@ -89,37 +89,34 @@ class ComposioTool(BaseTool): @classmethod def from_app( - cls, - app: t.Any, - tags: t.Optional[t.List[str]] = None, - **kwargs: t.Any, - ) -> t.List[te.Self]: - """Create toolset from an app.""" - from composio import App - - if not isinstance(app, App): - app = App(app) - - return [ - cls.from_tool(tool=action, **kwargs) - for action in app.get_actions(tags=tags) - ] - - @classmethod - def from_use_case( cls, *apps: t.Any, - use_case: str, + tags: t.Optional[t.List[str]] = None, + use_case: t.Optional[str] = None, **kwargs: t.Any, ) -> t.List[te.Self]: """Create toolset from an app.""" if len(apps) == 0: + raise ValueError("You need to provide at least one app name") + + if use_case is None and tags is None: + raise ValueError("Both `use_case` and `tags` cannot be `None`") + + if use_case is not None and tags is not None: raise ValueError( - "You need to provide at least one app name to search by use case" + "Cannot use both `use_case` and `tags` to filter the actions" ) from composio import ComposioToolSet toolset = ComposioToolSet() - actions = toolset.find_actions_by_use_case(*apps, use_case=use_case) - return [cls.from_tool(tool=action, **kwargs) for action in actions] + if use_case is not None: + return [ + cls.from_action(action=action, **kwargs) + for action in toolset.find_actions_by_use_case(*apps, use_case=use_case) + ] + + return [ + cls.from_action(action=action, **kwargs) + for action in toolset.find_actions_by_tags(*apps, tags=tags) + ] From a3d3a70b5a4f87b27089759938cee08a6a9d3f31 Mon Sep 17 00:00:00 2001 From: Mervin Praison Date: Mon, 1 Jul 2024 04:34:39 +0100 Subject: [PATCH 23/32] Update __init__.py to Add CodeInterpreterTool --- src/crewai_tools/__init__.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/crewai_tools/__init__.py b/src/crewai_tools/__init__.py index a51d70449..1398dcfaf 100644 --- a/src/crewai_tools/__init__.py +++ b/src/crewai_tools/__init__.py @@ -2,6 +2,7 @@ from .tools.base_tool import BaseTool, Tool, tool from .tools import ( BrowserbaseLoadTool, CodeDocsSearchTool, + CodeInterpreterTool, CSVSearchTool, DirectorySearchTool, DOCXSearchTool, @@ -24,4 +25,4 @@ from .tools import ( YoutubeChannelSearchTool, YoutubeVideoSearchTool, LlamaIndexTool -) \ No newline at end of file +) From f79c385bf76a1dcd0595cfc7bd111fa1928a87d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Moura?= Date: Mon, 1 Jul 2024 00:55:21 -0700 Subject: [PATCH 24/32] revamping code interpreter --- .../code_interpreter_tool.py | 60 ++++++++----------- 1 file changed, 26 insertions(+), 34 deletions(-) diff --git a/src/crewai_tools/tools/code_interpreter_tool/code_interpreter_tool.py b/src/crewai_tools/tools/code_interpreter_tool/code_interpreter_tool.py index 06cb081f0..ec756d8c0 100644 --- a/src/crewai_tools/tools/code_interpreter_tool/code_interpreter_tool.py +++ b/src/crewai_tools/tools/code_interpreter_tool/code_interpreter_tool.py @@ -1,69 +1,62 @@ import os -from typing import Optional, Type +import importlib.util +import textwrap +from typing import List, Optional, Type import docker from crewai_tools.tools.base_tool import BaseTool from pydantic.v1 import BaseModel, Field -class FixedCodeInterpreterSchemaSchema(BaseModel): +class CodeInterpreterSchema(BaseModel): """Input for CodeInterpreterTool.""" - - pass - - -class CodeInterpreterSchema(FixedCodeInterpreterSchemaSchema): - """Input for CodeInterpreterTool.""" - code: str = Field( ..., - description="Python3 code used to be interpreted in the Docker container. ALWAYS PRINT the final result and the output of the code", + description="Mandatory string of python3 code used to be interpreted with a final print statement.", ) - libraries_used: Optional[str] = Field( - None, - description="List of libraries used in the code with proper installing names separated by commas. Example: numpy,pandas,beautifulsoup4", + dependencies_used_in_code: List[str] = Field( + ..., + description="Mandatory list of libraries used in the code with proper installing names.", ) - class CodeInterpreterTool(BaseTool): name: str = "Code Interpreter" - description: str = "Interprets Python code in a Docker container. ALWAYS PRINT the final result and the output of the code" + description: str = "Interprets Python3 code strings with a final print statement." args_schema: Type[BaseModel] = CodeInterpreterSchema code: Optional[str] = None + @staticmethod + def _get_installed_package_path(): + spec = importlib.util.find_spec('crewai_tools') + return os.path.dirname(spec.origin) + def _verify_docker_image(self) -> None: """ Verify if the Docker image is available """ image_tag = "code-interpreter:latest" - client = docker.from_env() - images = client.images.list() - all_tags = [tag for image in images for tag in image.tags] + try: + client.images.get(image_tag) + except: + package_path = self._get_installed_package_path() + dockerfile_path = os.path.join(package_path, 'tools/code_interpreter_tool') + if not os.path.exists(dockerfile_path): + raise FileNotFoundError(f"Dockerfile not found in {dockerfile_path}") - if image_tag not in all_tags: client.images.build( - path=os.path.dirname(os.path.abspath(__file__)), + path=dockerfile_path, tag=image_tag, rm=True, ) - def __init__(self, code: Optional[str] = None, **kwargs) -> None: - super().__init__(**kwargs) - if code is not None: - self._verify_docker_image() - self.code = code - self.description = "Interprets Python code in a Docker container. ALWAYS PRINT the final result and the output of the code" - self.args_schema = FixedCodeInterpreterSchemaSchema - self._generate_description() - def _run(self, **kwargs) -> str: code = kwargs.get("code", self.code) - libraries_used = kwargs.get("libraries_used", None) + libraries_used = kwargs.get("dependencies_used_in_code", []) return self.run_code_in_docker(code, libraries_used) def _install_libraries( - self, container: docker.models.containers.Container, libraries: list[str] + self, container: docker.models.containers.Container, libraries: List[str] ) -> None: """ Install missing libraries in the Docker container @@ -78,10 +71,9 @@ class CodeInterpreterTool(BaseTool): ) def run_code_in_docker(self, code: str, libraries_used: str) -> str: + self._verify_docker_image() container = self._init_docker_container() - - if libraries_used: - self._install_libraries(container, libraries_used.split(",")) + self._install_libraries(container, libraries_used) cmd_to_run = f'python3 -c "{code}"' exec_result = container.exec_run(cmd_to_run) From d000bd2fc8eaed205b77064bc4f908ba6c8aed4e Mon Sep 17 00:00:00 2001 From: Eduardo Chiarotti Date: Tue, 2 Jul 2024 12:00:04 -0300 Subject: [PATCH 25/32] fix: add code interpreter tool --- src/crewai_tools/__init__.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/crewai_tools/__init__.py b/src/crewai_tools/__init__.py index 1cc222ec9..b85a16ffb 100644 --- a/src/crewai_tools/__init__.py +++ b/src/crewai_tools/__init__.py @@ -1,6 +1,7 @@ from .tools import ( BrowserbaseLoadTool, CodeDocsSearchTool, + CodeInterpreterTool, ComposioTool, CSVSearchTool, DirectoryReadTool, @@ -25,4 +26,4 @@ from .tools import ( YoutubeChannelSearchTool, YoutubeVideoSearchTool, ) -from .tools.base_tool import BaseTool, Tool, tool \ No newline at end of file +from .tools.base_tool import BaseTool, Tool, tool From b4d91d1ce01644893eb0c93be84d5067616f5f52 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Moura?= Date: Thu, 4 Jul 2024 00:09:18 -0400 Subject: [PATCH 26/32] adding new result_as_answer options --- src/crewai_tools/tools/base_tool.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/crewai_tools/tools/base_tool.py b/src/crewai_tools/tools/base_tool.py index e8e497859..4e0bd1fd5 100644 --- a/src/crewai_tools/tools/base_tool.py +++ b/src/crewai_tools/tools/base_tool.py @@ -22,6 +22,8 @@ class BaseTool(BaseModel, ABC): """Flag to check if the description has been updated.""" cache_function: Optional[Callable] = lambda _args, _result: True """Function that will be used to determine if the tool should be cached, should return a boolean. If None, the tool will be cached.""" + result_as_answer: bool = False + """Flag to check if the tool should be the final agent answer.""" @validator("args_schema", always=True, pre=True) def _default_args_schema(cls, v: Type[V1BaseModel]) -> Type[V1BaseModel]: From ba05d18ab16db4dd36f3b3ce414d3652ccfc9d20 Mon Sep 17 00:00:00 2001 From: Eduardo Chiarotti Date: Thu, 4 Jul 2024 16:42:29 -0300 Subject: [PATCH 27/32] fix: fix type hinting, add container name and handle exception and returned old description --- .../code_interpreter_tool.py | 30 ++++++++++++------- 1 file changed, 19 insertions(+), 11 deletions(-) diff --git a/src/crewai_tools/tools/code_interpreter_tool/code_interpreter_tool.py b/src/crewai_tools/tools/code_interpreter_tool/code_interpreter_tool.py index ec756d8c0..f341e52d0 100644 --- a/src/crewai_tools/tools/code_interpreter_tool/code_interpreter_tool.py +++ b/src/crewai_tools/tools/code_interpreter_tool/code_interpreter_tool.py @@ -1,6 +1,5 @@ -import os import importlib.util -import textwrap +import os from typing import List, Optional, Type import docker @@ -10,15 +9,18 @@ from pydantic.v1 import BaseModel, Field class CodeInterpreterSchema(BaseModel): """Input for CodeInterpreterTool.""" + code: str = Field( ..., - description="Mandatory string of python3 code used to be interpreted with a final print statement.", + description="Python3 code used to be interpreted in the Docker container. ALWAYS PRINT the final result and the output of the code", ) - dependencies_used_in_code: List[str] = Field( + + libraries_used: List[str] = Field( ..., - description="Mandatory list of libraries used in the code with proper installing names.", + description="List of libraries used in the code with proper installing names separated by commas. Example: numpy,pandas,beautifulsoup4", ) + class CodeInterpreterTool(BaseTool): name: str = "Code Interpreter" description: str = "Interprets Python3 code strings with a final print statement." @@ -27,7 +29,7 @@ class CodeInterpreterTool(BaseTool): @staticmethod def _get_installed_package_path(): - spec = importlib.util.find_spec('crewai_tools') + spec = importlib.util.find_spec("crewai_tools") return os.path.dirname(spec.origin) def _verify_docker_image(self) -> None: @@ -36,11 +38,13 @@ class CodeInterpreterTool(BaseTool): """ image_tag = "code-interpreter:latest" client = docker.from_env() + try: client.images.get(image_tag) - except: + + except docker.errors.ImageNotFound: package_path = self._get_installed_package_path() - dockerfile_path = os.path.join(package_path, 'tools/code_interpreter_tool') + dockerfile_path = os.path.join(package_path, "tools/code_interpreter_tool") if not os.path.exists(dockerfile_path): raise FileNotFoundError(f"Dockerfile not found in {dockerfile_path}") @@ -52,7 +56,7 @@ class CodeInterpreterTool(BaseTool): def _run(self, **kwargs) -> str: code = kwargs.get("code", self.code) - libraries_used = kwargs.get("dependencies_used_in_code", []) + libraries_used = kwargs.get("libraries_used", []) return self.run_code_in_docker(code, libraries_used) def _install_libraries( @@ -67,10 +71,14 @@ class CodeInterpreterTool(BaseTool): def _init_docker_container(self) -> docker.models.containers.Container: client = docker.from_env() return client.containers.run( - "code-interpreter", detach=True, tty=True, working_dir="/workspace" + "code-interpreter", + detach=True, + tty=True, + working_dir="/workspace", + name="code-interpreter", ) - def run_code_in_docker(self, code: str, libraries_used: str) -> str: + def run_code_in_docker(self, code: str, libraries_used: List[str]) -> str: self._verify_docker_image() container = self._init_docker_container() self._install_libraries(container, libraries_used) From a5d283943160e10185aa6bc6353d7c79d1c64aa9 Mon Sep 17 00:00:00 2001 From: Jakub Strnad Date: Fri, 5 Jul 2024 16:30:41 +0200 Subject: [PATCH 28/32] arguments descriptions added to tool description so now the agent knows how to use the tools params --- src/crewai_tools/tools/base_tool.py | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/src/crewai_tools/tools/base_tool.py b/src/crewai_tools/tools/base_tool.py index 4e0bd1fd5..dff1e37aa 100644 --- a/src/crewai_tools/tools/base_tool.py +++ b/src/crewai_tools/tools/base_tool.py @@ -86,13 +86,16 @@ class BaseTool(BaseModel, ABC): ) def _generate_description(self): - args = [] - for arg, attribute in self.args_schema.schema()["properties"].items(): - if "type" in attribute: - args.append(f"{arg}: '{attribute['type']}'") + args = [] + args_description = [] + for arg, attribute in self.args_schema.schema()["properties"].items(): + if "type" in attribute: + args.append(f"{arg}: '{attribute['type']}'") + if "description" in attribute: + args_description.append(f"{arg}: '{attribute['description']}'") - description = self.description.replace("\n", " ") - self.description = f"{self.name}({', '.join(args)}) - {description}" + description = self.description.replace("\n", " ") + self.description = f"{self.name}({', '.join(args)}) - {description} {', '.join(args_description)}" class Tool(BaseTool): From cb1dc13a9d214622f8ea8d0c3573b4207c9402bf Mon Sep 17 00:00:00 2001 From: Jakub Date: Fri, 5 Jul 2024 18:42:16 +0200 Subject: [PATCH 29/32] fixed intendation --- src/crewai_tools/tools/base_tool.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/crewai_tools/tools/base_tool.py b/src/crewai_tools/tools/base_tool.py index dff1e37aa..4b60d93d4 100644 --- a/src/crewai_tools/tools/base_tool.py +++ b/src/crewai_tools/tools/base_tool.py @@ -86,16 +86,16 @@ class BaseTool(BaseModel, ABC): ) def _generate_description(self): - args = [] - args_description = [] - for arg, attribute in self.args_schema.schema()["properties"].items(): - if "type" in attribute: - args.append(f"{arg}: '{attribute['type']}'") - if "description" in attribute: - args_description.append(f"{arg}: '{attribute['description']}'") + args = [] + args_description = [] + for arg, attribute in self.args_schema.schema()["properties"].items(): + if "type" in attribute: + args.append(f"{arg}: '{attribute['type']}'") + if "description" in attribute: + args_description.append(f"{arg}: '{attribute['description']}'") - description = self.description.replace("\n", " ") - self.description = f"{self.name}({', '.join(args)}) - {description} {', '.join(args_description)}" + description = self.description.replace("\n", " ") + self.description = f"{self.name}({', '.join(args)}) - {description} {', '.join(args_description)}" class Tool(BaseTool): From f056764132b5caa93846ad67fbc1fce841255796 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Moura?= Date: Mon, 8 Jul 2024 01:15:00 -0400 Subject: [PATCH 30/32] adding firecrawl imports --- src/crewai_tools/__init__.py | 3 +++ src/crewai_tools/tools/__init__.py | 3 +++ 2 files changed, 6 insertions(+) diff --git a/src/crewai_tools/__init__.py b/src/crewai_tools/__init__.py index b85a16ffb..a9c9a4168 100644 --- a/src/crewai_tools/__init__.py +++ b/src/crewai_tools/__init__.py @@ -9,6 +9,9 @@ from .tools import ( DOCXSearchTool, EXASearchTool, FileReadTool, + FirecrawlCrawlWebsiteTool, + FirecrawlScrapeWebsiteTool, + FirecrawlSearchTool, GithubSearchTool, JSONSearchTool, LlamaIndexTool, diff --git a/src/crewai_tools/tools/__init__.py b/src/crewai_tools/tools/__init__.py index f5ac94052..17d289832 100644 --- a/src/crewai_tools/tools/__init__.py +++ b/src/crewai_tools/tools/__init__.py @@ -8,6 +8,9 @@ from .directory_search_tool.directory_search_tool import DirectorySearchTool from .docx_search_tool.docx_search_tool import DOCXSearchTool from .exa_tools.exa_search_tool import EXASearchTool from .file_read_tool.file_read_tool import FileReadTool +from .firecrawl_crawl_website_tool.firecrawl_crawl_website_tool import FirecrawlCrawlWebsiteTool +from .firecrawl_scrape_website_tool.firecrawl_scrape_website_tool import FirecrawlScrapeWebsiteTool +from .firecrawl_search_tool.firecrawl_search_tool import FirecrawlSearchTool from .github_search_tool.github_search_tool import GithubSearchTool from .json_search_tool.json_search_tool import JSONSearchTool from .llamaindex_tool.llamaindex_tool import LlamaIndexTool From 65855cbe56e243bc61b8b48e3f731b5c5c1c3627 Mon Sep 17 00:00:00 2001 From: Jakub Strnad Date: Mon, 8 Jul 2024 15:24:26 +0200 Subject: [PATCH 31/32] bugfix: ScrapeWebsiteTool encoding fixed problem with garbage output of ScrapeWebsiteTool on some websites --- .../tools/scrape_website_tool/scrape_website_tool.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/crewai_tools/tools/scrape_website_tool/scrape_website_tool.py b/src/crewai_tools/tools/scrape_website_tool/scrape_website_tool.py index 148a0b320..e59064151 100644 --- a/src/crewai_tools/tools/scrape_website_tool/scrape_website_tool.py +++ b/src/crewai_tools/tools/scrape_website_tool/scrape_website_tool.py @@ -25,8 +25,7 @@ class ScrapeWebsiteTool(BaseTool): 'Accept-Language': 'en-US,en;q=0.9', 'Referer': 'https://www.google.com/', 'Connection': 'keep-alive', - 'Upgrade-Insecure-Requests': '1', - 'Accept-Encoding': 'gzip, deflate, br' + 'Upgrade-Insecure-Requests': '1' } def __init__(self, website_url: Optional[str] = None, cookies: Optional[dict] = None, **kwargs): From 6f45c6ed0949fece97b48ecf29df22336ec365e8 Mon Sep 17 00:00:00 2001 From: Naman Garg Date: Mon, 8 Jul 2024 15:11:04 -0700 Subject: [PATCH 32/32] Updated Readme --- src/crewai_tools/tools/multion_tool/README.md | 29 +++++++++++++++++-- .../tools/multion_tool/multion_tool.py | 11 ++++++- 2 files changed, 36 insertions(+), 4 deletions(-) diff --git a/src/crewai_tools/tools/multion_tool/README.md b/src/crewai_tools/tools/multion_tool/README.md index 608931166..ea530037f 100644 --- a/src/crewai_tools/tools/multion_tool/README.md +++ b/src/crewai_tools/tools/multion_tool/README.md @@ -5,27 +5,50 @@ The MultiOnTool, integrated within the crewai_tools package, empowers CrewAI age ## Installation Ensure the `crewai[tools]` package is installed in your environment to use the MultiOnTool. If it's not already installed, you can add it using the command below: +```shell +pip install 'crewai[tools]' +``` ## Example The following example demonstrates how to initialize the tool and execute a search with a given query: ```python +from crewai import Agent, Task, Crew from crewai_tools import MultiOnTool # Initialize the tool from a MultiOn Tool multion_tool = MultiOnTool(api_key= "YOUR_MULTION_API_KEY", local=False) +Browser = Agent( + role="Browser Agent", + goal="control web browsers using natural language ", + backstory="An expert browsing agent.", + tools=[multion_remote_tool], + verbose=True, +) + +# example task to search and summarize news +browse = Task( + description="Summarize the top 3 trending AI News headlines", + expected_output="A summary of the top 3 trending AI News headlines", + agent=Browser, +) + +crew = Crew(agents=[Browser], tasks=[browse]) + +crew.kickoff() ``` ## Arguments - `api_key`: Specifies Browserbase API key. Defaults is the `BROWSERBASE_API_KEY` environment variable. -- `local`: Optional. Use the local flag set as "true" to run the agent locally on your browser. Make sure the multion browser extension is installed and API Enabled is checked. +- `local`: Use the local flag set as "true" to run the agent locally on your browser. Make sure the multion browser extension is installed and API Enabled is checked. +- `max_steps`: Optional. Set the max_steps the multion agent can take for a command ## Steps to Get Started To effectively use the `MultiOnTool`, follow these steps: 1. **Install CrewAI**: Confirm that the `crewai[tools]` package is installed in your Python environment. -2. **Install and use MultiOn**: Follow MultiOn documentation (https://docs.multion.ai/). - +2. **Install and use MultiOn**: Follow MultiOn documentation for installing the MultiOn Browser Extension (https://docs.multion.ai/learn/browser-extension). +3. **Enable API Usage**: Click on the MultiOn extension in the extensions folder of your browser (not the hovering MultiOn icon on the web page) to open the extension configurations. Click the API Enabled toggle to enable the API diff --git a/src/crewai_tools/tools/multion_tool/multion_tool.py b/src/crewai_tools/tools/multion_tool/multion_tool.py index 1253627a2..2dc944f23 100644 --- a/src/crewai_tools/tools/multion_tool/multion_tool.py +++ b/src/crewai_tools/tools/multion_tool/multion_tool.py @@ -15,8 +15,15 @@ class MultiOnTool(BaseTool): multion: Optional[Any] = None session_id: Optional[str] = None local: bool = False + max_steps: int = 3 - def __init__(self, api_key: Optional[str] = None, local: bool = False, **kwargs): + def __init__( + self, + api_key: Optional[str] = None, + local: bool = False, + max_steps: int = 3, + **kwargs, + ): super().__init__(**kwargs) try: from multion.client import MultiOn # type: ignore @@ -27,6 +34,7 @@ class MultiOnTool(BaseTool): self.session_id = None self.local = local self.multion = MultiOn(api_key=api_key) + self.max_steps = max_steps def _run( self, @@ -48,6 +56,7 @@ class MultiOnTool(BaseTool): cmd=cmd, session_id=self.session_id, local=self.local, + max_steps=self.max_steps, *args, **kwargs, )