diff --git a/docs/tools/ai-ml/codeinterpretertool.mdx b/docs/tools/ai-ml/codeinterpretertool.mdx index 119e32a87..af486f963 100644 --- a/docs/tools/ai-ml/codeinterpretertool.mdx +++ b/docs/tools/ai-ml/codeinterpretertool.mdx @@ -108,6 +108,30 @@ programmer_agent = Agent( ) ``` +### Best Practices for Custom Docker Images + +When using custom Docker images for code execution, consider the following best practices: + +- **Use specific version tags**: Instead of `latest`, use specific version tags like `python:3.11-slim` for reproducible builds +- **Security scanning**: Ensure your custom images are regularly scanned for security vulnerabilities +- **Image size optimization**: Consider using slim or alpine variants to reduce image size and improve performance +- **Pre-installed dependencies**: Include commonly used libraries in your custom image to avoid repeated installations +- **Registry accessibility**: Ensure the Docker registry hosting your custom image is accessible from your execution environment + +Example with a custom image containing data science libraries: + +```python Code +# Custom image with pre-installed data science packages +data_scientist_agent = Agent( + role="Data Scientist", + goal="Analyze datasets and create visualizations", + backstory="Expert in data analysis with access to specialized tools", + allow_code_execution=True, + execution_image="my-registry.com/datascience:python3.11-pandas-numpy", + verbose=True, +) +``` + ### Enabling `unsafe_mode` ```python Code diff --git a/src/crewai/agent.py b/src/crewai/agent.py index 905b252fe..c65f1a7ef 100644 --- a/src/crewai/agent.py +++ b/src/crewai/agent.py @@ -130,6 +130,7 @@ class Agent(BaseAgent): execution_image: Optional[str] = Field( default=None, description="Custom Docker image to use for code execution. If not specified, uses the default image.", + pattern=r"^[a-zA-Z0-9._/-]+(?::[a-zA-Z0-9._-]+)?$", ) reasoning: bool = Field( default=False, @@ -569,10 +570,10 @@ class Agent(BaseAgent): # Set the unsafe_mode based on the code_execution_mode attribute unsafe_mode = self.code_execution_mode == "unsafe" + tool_kwargs = {"unsafe_mode": unsafe_mode} if self.execution_image: - return [CodeInterpreterTool(unsafe_mode=unsafe_mode, default_image_tag=self.execution_image)] - else: - return [CodeInterpreterTool(unsafe_mode=unsafe_mode)] + tool_kwargs["default_image_tag"] = self.execution_image + return [CodeInterpreterTool(**tool_kwargs)] except ModuleNotFoundError: self._logger.log( "info", "Coding tools not available. Install crewai_tools. " diff --git a/tests/test_agent_custom_execution_image.py b/tests/test_agent_custom_execution_image.py index 05a6d819e..8433a7f10 100644 --- a/tests/test_agent_custom_execution_image.py +++ b/tests/test_agent_custom_execution_image.py @@ -1,6 +1,7 @@ import pytest -from unittest.mock import patch, MagicMock +from unittest.mock import patch from crewai import Agent +from pydantic import ValidationError def test_agent_with_custom_execution_image(): @@ -39,7 +40,7 @@ def test_get_code_execution_tools_with_custom_image(mock_code_interpreter): execution_image="my-custom-image:latest" ) - tools = agent.get_code_execution_tools() + agent.get_code_execution_tools() mock_code_interpreter.assert_called_once_with( unsafe_mode=False, @@ -57,7 +58,7 @@ def test_get_code_execution_tools_without_custom_image(mock_code_interpreter): allow_code_execution=True ) - tools = agent.get_code_execution_tools() + agent.get_code_execution_tools() mock_code_interpreter.assert_called_once_with(unsafe_mode=False) @@ -74,9 +75,44 @@ def test_get_code_execution_tools_with_unsafe_mode_and_custom_image(mock_code_in execution_image="my-custom-image:latest" ) - tools = agent.get_code_execution_tools() + agent.get_code_execution_tools() mock_code_interpreter.assert_called_once_with( unsafe_mode=True, default_image_tag="my-custom-image:latest" ) + + +def test_agent_with_invalid_execution_image(): + """Test that Agent validates execution image format.""" + with pytest.raises(ValidationError, match="String should match pattern"): + Agent( + role="Test Agent", + goal="Test goal", + backstory="Test backstory", + allow_code_execution=True, + execution_image="invalid@@image" + ) + + +def test_agent_with_valid_execution_image_formats(): + """Test that Agent accepts various valid Docker image formats.""" + valid_images = [ + "python:3.11", + "python:3.11-slim", + "registry.example.com/python:3.11", + "my-registry.com:5000/python:latest", + "python", + "ubuntu:20.04", + "gcr.io/project/image:tag" + ] + + for image in valid_images: + agent = Agent( + role="Test Agent", + goal="Test goal", + backstory="Test backstory", + allow_code_execution=True, + execution_image=image + ) + assert agent.execution_image == image diff --git a/tests/test_custom_execution_image_integration.py b/tests/test_custom_execution_image_integration.py index 08b64bddd..1d60058f5 100644 --- a/tests/test_custom_execution_image_integration.py +++ b/tests/test_custom_execution_image_integration.py @@ -1,4 +1,3 @@ -import pytest from unittest.mock import patch, MagicMock from crewai import Agent, Task, Crew @@ -28,7 +27,7 @@ def test_crew_with_custom_execution_image_integration(mock_code_interpreter_clas tasks=[task] ) - tools = crew._prepare_tools(task, agent) + crew._prepare_tools(task, agent) mock_code_interpreter_class.assert_called_with( unsafe_mode=False,