Address PR feedback: Add validation, refactor code duplication, enhance tests

- Add regex pattern validation for execution_image parameter
- Refactor get_code_execution_tools() to eliminate code duplication using tool_kwargs
- Add comprehensive tests for invalid Docker image format validation
- Add tests for various valid Docker image formats
- Update documentation with best practices section for custom Docker images
- Fix lint issues: remove unused imports and variables

Addresses feedback from joaomdmoura in PR #2934

Co-Authored-By: João <joao@crewai.com>
This commit is contained in:
Devin AI
2025-06-02 18:23:11 +00:00
parent 229bbd9bbe
commit 45404537fd
4 changed files with 69 additions and 9 deletions

View File

@@ -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` ### Enabling `unsafe_mode`
```python Code ```python Code

View File

@@ -130,6 +130,7 @@ class Agent(BaseAgent):
execution_image: Optional[str] = Field( execution_image: Optional[str] = Field(
default=None, default=None,
description="Custom Docker image to use for code execution. If not specified, uses the default image.", 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( reasoning: bool = Field(
default=False, default=False,
@@ -569,10 +570,10 @@ class Agent(BaseAgent):
# Set the unsafe_mode based on the code_execution_mode attribute # Set the unsafe_mode based on the code_execution_mode attribute
unsafe_mode = self.code_execution_mode == "unsafe" unsafe_mode = self.code_execution_mode == "unsafe"
tool_kwargs = {"unsafe_mode": unsafe_mode}
if self.execution_image: if self.execution_image:
return [CodeInterpreterTool(unsafe_mode=unsafe_mode, default_image_tag=self.execution_image)] tool_kwargs["default_image_tag"] = self.execution_image
else: return [CodeInterpreterTool(**tool_kwargs)]
return [CodeInterpreterTool(unsafe_mode=unsafe_mode)]
except ModuleNotFoundError: except ModuleNotFoundError:
self._logger.log( self._logger.log(
"info", "Coding tools not available. Install crewai_tools. " "info", "Coding tools not available. Install crewai_tools. "

View File

@@ -1,6 +1,7 @@
import pytest import pytest
from unittest.mock import patch, MagicMock from unittest.mock import patch
from crewai import Agent from crewai import Agent
from pydantic import ValidationError
def test_agent_with_custom_execution_image(): 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" execution_image="my-custom-image:latest"
) )
tools = agent.get_code_execution_tools() agent.get_code_execution_tools()
mock_code_interpreter.assert_called_once_with( mock_code_interpreter.assert_called_once_with(
unsafe_mode=False, unsafe_mode=False,
@@ -57,7 +58,7 @@ def test_get_code_execution_tools_without_custom_image(mock_code_interpreter):
allow_code_execution=True 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) 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" execution_image="my-custom-image:latest"
) )
tools = agent.get_code_execution_tools() agent.get_code_execution_tools()
mock_code_interpreter.assert_called_once_with( mock_code_interpreter.assert_called_once_with(
unsafe_mode=True, unsafe_mode=True,
default_image_tag="my-custom-image:latest" 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

View File

@@ -1,4 +1,3 @@
import pytest
from unittest.mock import patch, MagicMock from unittest.mock import patch, MagicMock
from crewai import Agent, Task, Crew from crewai import Agent, Task, Crew
@@ -28,7 +27,7 @@ def test_crew_with_custom_execution_image_integration(mock_code_interpreter_clas
tasks=[task] tasks=[task]
) )
tools = crew._prepare_tools(task, agent) crew._prepare_tools(task, agent)
mock_code_interpreter_class.assert_called_with( mock_code_interpreter_class.assert_called_with(
unsafe_mode=False, unsafe_mode=False,