Merge pull request #59 from joaomdmoura/feat/code-interpreter

Feat: Code Interpreter tool
This commit is contained in:
João Moura
2024-06-27 02:25:25 -03:00
committed by GitHub
5 changed files with 186 additions and 6 deletions

View File

@@ -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

View File

@@ -0,0 +1,14 @@
FROM python:3.11-slim
# Install common utilities
RUN apt-get update && apt-get install -y \
build-essential \
curl \
wget \
software-properties-common
# Clean up
RUN apt-get clean && rm -rf /var/lib/apt/lists/*
# Set the working directory
WORKDIR /workspace

View File

@@ -0,0 +1,29 @@
# 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.
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
## 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()],
)
```

View File

@@ -0,0 +1,94 @@
import os
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 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",
)
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. ALWAYS PRINT the final result and the output of the code"
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
self._generate_description()
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 _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()
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")

View File

@@ -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)