feat: add ToolWithInstruction wrapper for tool-specific usage instructions (issue #2515)

Co-Authored-By: Joe Moura <joao@crewai.com>
This commit is contained in:
Devin AI
2025-04-03 11:04:12 +00:00
parent 409892d65f
commit d8571dc196
5 changed files with 158 additions and 0 deletions

View File

@@ -267,6 +267,7 @@ In addition to the sequential process, you can use the hierarchical process, whi
- **Role-Based Agent Design**: Customize agents with specific roles, goals, and tools.
- **Autonomous Inter-Agent Delegation**: Agents can autonomously delegate tasks and inquire amongst themselves, enhancing problem-solving efficiency.
- **Flexible Task Management**: Define tasks with customizable tools and assign them to agents dynamically.
- **Tool Instructions**: Attach specific usage instructions to tools for better control over when and how agents use them.
- **Processes Driven**: Currently only supports `sequential` task execution and `hierarchical` processes, but more complex processes like consensual and autonomous are being worked on.
- **Save output as file**: Save the output of individual tasks as a file, so you can use it later.
- **Parse output as Pydantic or Json**: Parse the output of individual tasks as a Pydantic model or as a Json if you want to.

View File

@@ -0,0 +1,44 @@
# Tool Instructions
CrewAI allows you to provide specific instructions for when and how to use tools. This is useful when you want to guide agents on proper tool usage without cluttering their backstory.
## Basic Usage
```python
from crewai import Agent
from crewai_tools import ScrapeWebsiteTool
from crewai.tools import ToolWithInstruction
# Create a tool with instructions
scrape_tool = ScrapeWebsiteTool()
scrape_with_instructions = ToolWithInstruction(
tool=scrape_tool,
instructions="""
ALWAYS use this tool when making a joke.
NEVER use this tool when making joke about someone's mom.
"""
)
# Use the tool with an agent
agent = Agent(
role="Comedian",
goal="Create hilarious and engaging jokes",
backstory="""
You are a professional stand-up comedian with years of experience in crafting jokes.
You have a great sense of humor and can create jokes about any topic
while keeping them appropriate and entertaining.
""",
tools=[scrape_with_instructions],
)
```
## When to Use Tool Instructions
Tool instructions are useful when:
1. You want to specify precise conditions for tool usage
2. You have multiple similar tools that should be used in different situations
3. You want to keep the agent's backstory focused on its role and personality,
not technical details about tools
Tool instructions are semantically more correct than putting tool usage guidelines in the agent's backstory.

View File

@@ -1 +1,2 @@
from .base_tool import BaseTool, tool
from .tool_with_instruction import ToolWithInstruction

View File

@@ -0,0 +1,45 @@
from typing import Any, List, Optional, Dict, Callable, Union
from pydantic import Field, model_validator
from crewai.tools.base_tool import BaseTool
from crewai.tools.structured_tool import CrewStructuredTool
class ToolWithInstruction(BaseTool):
"""A wrapper for tools that adds specific usage instructions.
This allows users to provide specific instructions on when and how to use a tool,
without having to include these instructions in the agent's backstory.
Attributes:
tool: The tool to wrap
instructions: Specific instructions about when and how to use this tool
name: Name of the tool (inherited from the wrapped tool)
description: Description of the tool (inherited from the wrapped tool with instructions)
"""
name: str = Field(default="", description="Name of the tool")
description: str = Field(default="", description="Description of the tool")
tool: BaseTool = Field(description="The tool to wrap")
instructions: str = Field(description="Instructions about when and how to use this tool")
@model_validator(mode="after")
def set_tool_attributes(self) -> "ToolWithInstruction":
"""Set attributes from the wrapped tool."""
self.name = self.tool.name
self.description = f"{self.tool.description}\nInstructions: {self.instructions}"
self.args_schema = self.tool.args_schema
return self
def _run(self, *args: Any, **kwargs: Any) -> Any:
"""Run the wrapped tool."""
return self.tool._run(*args, **kwargs)
def to_structured_tool(self) -> CrewStructuredTool:
"""Convert this tool to a CrewStructuredTool instance."""
structured_tool = self.tool.to_structured_tool()
structured_tool.description = f"{structured_tool.description}\nInstructions: {self.instructions}"
return structured_tool

View File

@@ -0,0 +1,67 @@
import pytest
from unittest.mock import MagicMock, patch
from typing import Any, Dict, Optional
from crewai.tools.base_tool import BaseTool, Tool
from crewai.tools.tool_with_instruction import ToolWithInstruction
class MockTool(BaseTool):
"""Mock tool for testing."""
name: str = "mock_tool"
description: str = "A mock tool for testing"
def _run(self, *args: Any, **kwargs: Any) -> str:
return "mock result"
class TestToolWithInstruction:
"""Test suite for ToolWithInstruction."""
def test_initialization(self):
"""Test tool initialization with instructions."""
tool = MockTool()
instructions = "Only use this tool for XYZ"
wrapped_tool = ToolWithInstruction(tool=tool, instructions=instructions)
assert wrapped_tool.name == tool.name
assert "Instructions: Only use this tool for XYZ" in wrapped_tool.description
assert wrapped_tool.args_schema == tool.args_schema
def test_run_method(self):
"""Test that the run method delegates to the original tool."""
tool = MockTool()
instructions = "Only use this tool for XYZ"
wrapped_tool = ToolWithInstruction(tool=tool, instructions=instructions)
result = wrapped_tool.run()
assert result == "mock result"
def test_to_structured_tool(self):
"""Test that to_structured_tool includes instructions."""
tool = MockTool()
instructions = "Only use this tool for XYZ"
wrapped_tool = ToolWithInstruction(tool=tool, instructions=instructions)
structured_tool = wrapped_tool.to_structured_tool()
assert "Instructions: Only use this tool for XYZ" in structured_tool.description
def test_with_function_tool(self):
"""Test tool wrapping with a function tool."""
def sample_func():
return "sample result"
tool = Tool(
name="sample_tool",
description="A sample tool",
func=sample_func
)
instructions = "Only use this tool for XYZ"
wrapped_tool = ToolWithInstruction(tool=tool, instructions=instructions)
assert wrapped_tool.name == tool.name
assert "Instructions: Only use this tool for XYZ" in wrapped_tool.description