mirror of
https://github.com/crewAIInc/crewAI.git
synced 2026-01-09 16:18:30 +00:00
refactor: Move BaseTool to main package and centralize tool description generation (#1514)
* move base_tool to main package and consolidate tool desscription generation * update import path * update tests * update doc * add base_tool test * migrate agent delegation tools to use BaseTool * update tests * update import path for tool * fix lint * update param signature * add from_langchain to BaseTool for backwards support of langchain tools * fix the case where StructuredTool doesn't have func --------- Co-authored-by: c0dez <li@vitablehealth.com> Co-authored-by: Brandon Hancock (bhancock_ai) <109994880+bhancockio@users.noreply.github.com>
This commit is contained in:
@@ -5,6 +5,7 @@ icon: screwdriver-wrench
|
|||||||
---
|
---
|
||||||
|
|
||||||
## Introduction
|
## Introduction
|
||||||
|
|
||||||
CrewAI tools empower agents with capabilities ranging from web searching and data analysis to collaboration and delegating tasks among coworkers.
|
CrewAI tools empower agents with capabilities ranging from web searching and data analysis to collaboration and delegating tasks among coworkers.
|
||||||
This documentation outlines how to create, integrate, and leverage these tools within the CrewAI framework, including a new focus on collaboration tools.
|
This documentation outlines how to create, integrate, and leverage these tools within the CrewAI framework, including a new focus on collaboration tools.
|
||||||
|
|
||||||
@@ -104,7 +105,7 @@ crew.kickoff()
|
|||||||
Here is a list of the available tools and their descriptions:
|
Here is a list of the available tools and their descriptions:
|
||||||
|
|
||||||
| Tool | Description |
|
| Tool | Description |
|
||||||
| :-------------------------- | :-------------------------------------------------------------------------------------------- |
|
| :------------------------------- | :--------------------------------------------------------------------------------------------- |
|
||||||
| **BrowserbaseLoadTool** | A tool for interacting with and extracting data from web browsers. |
|
| **BrowserbaseLoadTool** | A tool for interacting with and extracting data from web browsers. |
|
||||||
| **CodeDocsSearchTool** | A RAG tool optimized for searching through code documentation and related technical documents. |
|
| **CodeDocsSearchTool** | A RAG tool optimized for searching through code documentation and related technical documents. |
|
||||||
| **CodeInterpreterTool** | A tool for interpreting python code. |
|
| **CodeInterpreterTool** | A tool for interpreting python code. |
|
||||||
@@ -119,7 +120,7 @@ Here is a list of the available tools and their descriptions:
|
|||||||
| **FirecrawlSearchTool** | A tool to search webpages using Firecrawl and return the results. |
|
| **FirecrawlSearchTool** | A tool to search webpages using Firecrawl and return the results. |
|
||||||
| **FirecrawlCrawlWebsiteTool** | A tool for crawling webpages using Firecrawl. |
|
| **FirecrawlCrawlWebsiteTool** | A tool for crawling webpages using Firecrawl. |
|
||||||
| **FirecrawlScrapeWebsiteTool** | A tool for scraping webpages URL using Firecrawl and returning its contents. |
|
| **FirecrawlScrapeWebsiteTool** | A tool for scraping webpages URL using Firecrawl and returning its contents. |
|
||||||
| **GithubSearchTool** | A RAG tool for searching within GitHub repositories, useful for code and documentation search.|
|
| **GithubSearchTool** | A RAG tool for searching within GitHub repositories, useful for code and documentation search. |
|
||||||
| **SerperDevTool** | A specialized tool for development purposes, with specific functionalities under development. |
|
| **SerperDevTool** | A specialized tool for development purposes, with specific functionalities under development. |
|
||||||
| **TXTSearchTool** | A RAG tool focused on searching within text (.txt) files, suitable for unstructured data. |
|
| **TXTSearchTool** | A RAG tool focused on searching within text (.txt) files, suitable for unstructured data. |
|
||||||
| **JSONSearchTool** | A RAG tool designed for searching within JSON files, catering to structured data handling. |
|
| **JSONSearchTool** | A RAG tool designed for searching within JSON files, catering to structured data handling. |
|
||||||
@@ -133,27 +134,23 @@ Here is a list of the available tools and their descriptions:
|
|||||||
| **ScrapeWebsiteTool** | Facilitates scraping entire websites, ideal for comprehensive data collection. |
|
| **ScrapeWebsiteTool** | Facilitates scraping entire websites, ideal for comprehensive data collection. |
|
||||||
| **WebsiteSearchTool** | A RAG tool for searching website content, optimized for web data extraction. |
|
| **WebsiteSearchTool** | A RAG tool for searching website content, optimized for web data extraction. |
|
||||||
| **XMLSearchTool** | A RAG tool designed for searching within XML files, suitable for structured data formats. |
|
| **XMLSearchTool** | A RAG tool designed for searching within XML files, suitable for structured data formats. |
|
||||||
| **YoutubeChannelSearchTool**| A RAG tool for searching within YouTube channels, useful for video content analysis. |
|
| **YoutubeChannelSearchTool** | A RAG tool for searching within YouTube channels, useful for video content analysis. |
|
||||||
| **YoutubeVideoSearchTool** | A RAG tool aimed at searching within YouTube videos, ideal for video data extraction. |
|
| **YoutubeVideoSearchTool** | A RAG tool aimed at searching within YouTube videos, ideal for video data extraction. |
|
||||||
|
|
||||||
## Creating your own Tools
|
## Creating your own Tools
|
||||||
|
|
||||||
<Tip>
|
<Tip>
|
||||||
Developers can craft `custom tools` tailored for their agent’s needs or utilize pre-built options.
|
Developers can craft `custom tools` tailored for their agent’s needs or
|
||||||
|
utilize pre-built options.
|
||||||
</Tip>
|
</Tip>
|
||||||
|
|
||||||
To create your own CrewAI tools you will need to install our extra tools package:
|
There are two main ways for one to create a CrewAI tool:
|
||||||
|
|
||||||
```bash
|
|
||||||
pip install 'crewai[tools]'
|
|
||||||
```
|
|
||||||
|
|
||||||
Once you do that there are two main ways for one to create a CrewAI tool:
|
|
||||||
|
|
||||||
### Subclassing `BaseTool`
|
### Subclassing `BaseTool`
|
||||||
|
|
||||||
```python Code
|
```python Code
|
||||||
from crewai_tools import BaseTool
|
from crewai.tools import BaseTool
|
||||||
|
|
||||||
|
|
||||||
class MyCustomTool(BaseTool):
|
class MyCustomTool(BaseTool):
|
||||||
name: str = "Name of my tool"
|
name: str = "Name of my tool"
|
||||||
@@ -167,7 +164,7 @@ class MyCustomTool(BaseTool):
|
|||||||
### Utilizing the `tool` Decorator
|
### Utilizing the `tool` Decorator
|
||||||
|
|
||||||
```python Code
|
```python Code
|
||||||
from crewai_tools import tool
|
from crewai.tools import tool
|
||||||
@tool("Name of my tool")
|
@tool("Name of my tool")
|
||||||
def my_tool(question: str) -> str:
|
def my_tool(question: str) -> str:
|
||||||
"""Clear description for what this tool is useful for, your agent will need this information to use it."""
|
"""Clear description for what this tool is useful for, your agent will need this information to use it."""
|
||||||
@@ -178,11 +175,13 @@ def my_tool(question: str) -> str:
|
|||||||
### Custom Caching Mechanism
|
### Custom Caching Mechanism
|
||||||
|
|
||||||
<Tip>
|
<Tip>
|
||||||
Tools can optionally implement a `cache_function` to fine-tune caching behavior. This function determines when to cache results based on specific conditions, offering granular control over caching logic.
|
Tools can optionally implement a `cache_function` to fine-tune caching
|
||||||
|
behavior. This function determines when to cache results based on specific
|
||||||
|
conditions, offering granular control over caching logic.
|
||||||
</Tip>
|
</Tip>
|
||||||
|
|
||||||
```python Code
|
```python Code
|
||||||
from crewai_tools import tool
|
from crewai.tools import tool
|
||||||
|
|
||||||
@tool
|
@tool
|
||||||
def multiplication_tool(first_number: int, second_number: int) -> str:
|
def multiplication_tool(first_number: int, second_number: int) -> str:
|
||||||
|
|||||||
@@ -10,21 +10,13 @@ This guide provides detailed instructions on creating custom tools for the CrewA
|
|||||||
incorporating the latest functionalities such as tool delegation, error handling, and dynamic tool calling. It also highlights the importance of collaboration tools,
|
incorporating the latest functionalities such as tool delegation, error handling, and dynamic tool calling. It also highlights the importance of collaboration tools,
|
||||||
enabling agents to perform a wide range of actions.
|
enabling agents to perform a wide range of actions.
|
||||||
|
|
||||||
### Prerequisites
|
|
||||||
|
|
||||||
Before creating your own tools, ensure you have the crewAI extra tools package installed:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
pip install 'crewai[tools]'
|
|
||||||
```
|
|
||||||
|
|
||||||
### Subclassing `BaseTool`
|
### Subclassing `BaseTool`
|
||||||
|
|
||||||
To create a personalized tool, inherit from `BaseTool` and define the necessary attributes, including the `args_schema` for input validation, and the `_run` method.
|
To create a personalized tool, inherit from `BaseTool` and define the necessary attributes, including the `args_schema` for input validation, and the `_run` method.
|
||||||
|
|
||||||
```python Code
|
```python Code
|
||||||
from typing import Type
|
from typing import Type
|
||||||
from crewai_tools import BaseTool
|
from crewai.tools import BaseTool
|
||||||
from pydantic import BaseModel, Field
|
from pydantic import BaseModel, Field
|
||||||
|
|
||||||
class MyToolInput(BaseModel):
|
class MyToolInput(BaseModel):
|
||||||
@@ -47,7 +39,7 @@ Alternatively, you can use the tool decorator `@tool`. This approach allows you
|
|||||||
offering a concise and efficient way to create specialized tools tailored to your needs.
|
offering a concise and efficient way to create specialized tools tailored to your needs.
|
||||||
|
|
||||||
```python Code
|
```python Code
|
||||||
from crewai_tools import tool
|
from crewai.tools import tool
|
||||||
|
|
||||||
@tool("Tool Name")
|
@tool("Tool Name")
|
||||||
def my_simple_tool(question: str) -> str:
|
def my_simple_tool(question: str) -> str:
|
||||||
|
|||||||
@@ -10,7 +10,8 @@ from crewai.agents.agent_builder.base_agent import BaseAgent
|
|||||||
from crewai.agents.crew_agent_executor import CrewAgentExecutor
|
from crewai.agents.crew_agent_executor import CrewAgentExecutor
|
||||||
from crewai.llm import LLM
|
from crewai.llm import LLM
|
||||||
from crewai.memory.contextual.contextual_memory import ContextualMemory
|
from crewai.memory.contextual.contextual_memory import ContextualMemory
|
||||||
from crewai.tools.agent_tools import AgentTools
|
from crewai.tools.agent_tools.agent_tools import AgentTools
|
||||||
|
from crewai.tools import BaseTool
|
||||||
from crewai.utilities import Converter, Prompts
|
from crewai.utilities import Converter, Prompts
|
||||||
from crewai.utilities.constants import TRAINED_AGENTS_DATA_FILE, TRAINING_DATA_FILE
|
from crewai.utilities.constants import TRAINED_AGENTS_DATA_FILE, TRAINING_DATA_FILE
|
||||||
from crewai.utilities.token_counter_callback import TokenCalcHandler
|
from crewai.utilities.token_counter_callback import TokenCalcHandler
|
||||||
@@ -192,7 +193,7 @@ class Agent(BaseAgent):
|
|||||||
self,
|
self,
|
||||||
task: Any,
|
task: Any,
|
||||||
context: Optional[str] = None,
|
context: Optional[str] = None,
|
||||||
tools: Optional[List[Any]] = None,
|
tools: Optional[List[BaseTool]] = None,
|
||||||
) -> str:
|
) -> str:
|
||||||
"""Execute a task with the agent.
|
"""Execute a task with the agent.
|
||||||
|
|
||||||
@@ -259,7 +260,9 @@ class Agent(BaseAgent):
|
|||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
def create_agent_executor(self, tools=None, task=None) -> None:
|
def create_agent_executor(
|
||||||
|
self, tools: Optional[List[BaseTool]] = None, task=None
|
||||||
|
) -> None:
|
||||||
"""Create an agent executor for the agent.
|
"""Create an agent executor for the agent.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
@@ -332,7 +335,7 @@ class Agent(BaseAgent):
|
|||||||
tools_list = []
|
tools_list = []
|
||||||
try:
|
try:
|
||||||
# tentatively try to import from crewai_tools import BaseTool as CrewAITool
|
# tentatively try to import from crewai_tools import BaseTool as CrewAITool
|
||||||
from crewai_tools import BaseTool as CrewAITool
|
from crewai.tools import BaseTool as CrewAITool
|
||||||
|
|
||||||
for tool in tools:
|
for tool in tools:
|
||||||
if isinstance(tool, CrewAITool):
|
if isinstance(tool, CrewAITool):
|
||||||
@@ -391,7 +394,7 @@ class Agent(BaseAgent):
|
|||||||
|
|
||||||
return description
|
return description
|
||||||
|
|
||||||
def _render_text_description_and_args(self, tools: List[Any]) -> str:
|
def _render_text_description_and_args(self, tools: List[BaseTool]) -> str:
|
||||||
"""Render the tool name, description, and args in plain text.
|
"""Render the tool name, description, and args in plain text.
|
||||||
|
|
||||||
Output will be in the format of:
|
Output will be in the format of:
|
||||||
@@ -404,17 +407,7 @@ class Agent(BaseAgent):
|
|||||||
"""
|
"""
|
||||||
tool_strings = []
|
tool_strings = []
|
||||||
for tool in tools:
|
for tool in tools:
|
||||||
args_schema = {
|
tool_strings.append(tool.description)
|
||||||
name: {
|
|
||||||
"description": field.description,
|
|
||||||
"type": field.annotation.__name__,
|
|
||||||
}
|
|
||||||
for name, field in tool.args_schema.model_fields.items()
|
|
||||||
}
|
|
||||||
description = (
|
|
||||||
f"Tool Name: {tool.name}\nTool Description: {tool.description}"
|
|
||||||
)
|
|
||||||
tool_strings.append(f"{description}\nTool Arguments: {args_schema}")
|
|
||||||
|
|
||||||
return "\n".join(tool_strings)
|
return "\n".join(tool_strings)
|
||||||
|
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ from pydantic_core import PydanticCustomError
|
|||||||
from crewai.agents.agent_builder.utilities.base_token_process import TokenProcess
|
from crewai.agents.agent_builder.utilities.base_token_process import TokenProcess
|
||||||
from crewai.agents.cache.cache_handler import CacheHandler
|
from crewai.agents.cache.cache_handler import CacheHandler
|
||||||
from crewai.agents.tools_handler import ToolsHandler
|
from crewai.agents.tools_handler import ToolsHandler
|
||||||
|
from crewai.tools import BaseTool
|
||||||
from crewai.utilities import I18N, Logger, RPMController
|
from crewai.utilities import I18N, Logger, RPMController
|
||||||
from crewai.utilities.config import process_config
|
from crewai.utilities.config import process_config
|
||||||
|
|
||||||
@@ -49,11 +50,11 @@ class BaseAgent(ABC, BaseModel):
|
|||||||
|
|
||||||
|
|
||||||
Methods:
|
Methods:
|
||||||
execute_task(task: Any, context: Optional[str] = None, tools: Optional[List[Any]] = None) -> str:
|
execute_task(task: Any, context: Optional[str] = None, tools: Optional[List[BaseTool]] = None) -> str:
|
||||||
Abstract method to execute a task.
|
Abstract method to execute a task.
|
||||||
create_agent_executor(tools=None) -> None:
|
create_agent_executor(tools=None) -> None:
|
||||||
Abstract method to create an agent executor.
|
Abstract method to create an agent executor.
|
||||||
_parse_tools(tools: List[Any]) -> List[Any]:
|
_parse_tools(tools: List[BaseTool]) -> List[Any]:
|
||||||
Abstract method to parse tools.
|
Abstract method to parse tools.
|
||||||
get_delegation_tools(agents: List["BaseAgent"]):
|
get_delegation_tools(agents: List["BaseAgent"]):
|
||||||
Abstract method to set the agents task tools for handling delegation and question asking to other agents in crew.
|
Abstract method to set the agents task tools for handling delegation and question asking to other agents in crew.
|
||||||
@@ -105,7 +106,7 @@ class BaseAgent(ABC, BaseModel):
|
|||||||
default=False,
|
default=False,
|
||||||
description="Enable agent to delegate and ask questions among each other.",
|
description="Enable agent to delegate and ask questions among each other.",
|
||||||
)
|
)
|
||||||
tools: Optional[List[Any]] = Field(
|
tools: Optional[List[BaseTool]] = Field(
|
||||||
default_factory=list, description="Tools at agents' disposal"
|
default_factory=list, description="Tools at agents' disposal"
|
||||||
)
|
)
|
||||||
max_iter: Optional[int] = Field(
|
max_iter: Optional[int] = Field(
|
||||||
@@ -188,7 +189,7 @@ class BaseAgent(ABC, BaseModel):
|
|||||||
self,
|
self,
|
||||||
task: Any,
|
task: Any,
|
||||||
context: Optional[str] = None,
|
context: Optional[str] = None,
|
||||||
tools: Optional[List[Any]] = None,
|
tools: Optional[List[BaseTool]] = None,
|
||||||
) -> str:
|
) -> str:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@@ -197,11 +198,11 @@ class BaseAgent(ABC, BaseModel):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def _parse_tools(self, tools: List[Any]) -> List[Any]:
|
def _parse_tools(self, tools: List[BaseTool]) -> List[BaseTool]:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def get_delegation_tools(self, agents: List["BaseAgent"]) -> List[Any]:
|
def get_delegation_tools(self, agents: List["BaseAgent"]) -> List[BaseTool]:
|
||||||
"""Set the task tools that init BaseAgenTools class."""
|
"""Set the task tools that init BaseAgenTools class."""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
from typing import Any, Optional, Union
|
from typing import Any, Optional, Union
|
||||||
|
|
||||||
from ..tools.cache_tools import CacheTools
|
from ..tools.cache_tools.cache_tools import CacheTools
|
||||||
from ..tools.tool_calling import InstructorToolCalling, ToolCalling
|
from ..tools.tool_calling import InstructorToolCalling, ToolCalling
|
||||||
from .cache.cache_handler import CacheHandler
|
from .cache.cache_handler import CacheHandler
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
|
from crewai.tools import BaseTool
|
||||||
from typing import Type
|
from typing import Type
|
||||||
from crewai_tools import BaseTool
|
|
||||||
from pydantic import BaseModel, Field
|
from pydantic import BaseModel, Field
|
||||||
|
|
||||||
|
|
||||||
class MyCustomToolInput(BaseModel):
|
class MyCustomToolInput(BaseModel):
|
||||||
"""Input schema for MyCustomTool."""
|
"""Input schema for MyCustomTool."""
|
||||||
argument: str = Field(..., description="Description of the argument.")
|
argument: str = Field(..., description="Description of the argument.")
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
from typing import Type
|
from typing import Type
|
||||||
|
|
||||||
from crewai_tools import BaseTool
|
from crewai.tools import BaseTool
|
||||||
from pydantic import BaseModel, Field
|
from pydantic import BaseModel, Field
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
from typing import Type
|
from typing import Type
|
||||||
from crewai_tools import BaseTool
|
from crewai.tools import BaseTool
|
||||||
from pydantic import BaseModel, Field
|
from pydantic import BaseModel, Field
|
||||||
|
|
||||||
|
|
||||||
class MyCustomToolInput(BaseModel):
|
class MyCustomToolInput(BaseModel):
|
||||||
"""Input schema for MyCustomTool."""
|
"""Input schema for MyCustomTool."""
|
||||||
argument: str = Field(..., description="Description of the argument.")
|
argument: str = Field(..., description="Description of the argument.")
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
from typing import Type
|
from typing import Type
|
||||||
from crewai_tools import BaseTool
|
from crewai.tools import BaseTool
|
||||||
from pydantic import BaseModel, Field
|
from pydantic import BaseModel, Field
|
||||||
|
|
||||||
|
|
||||||
class MyCustomToolInput(BaseModel):
|
class MyCustomToolInput(BaseModel):
|
||||||
"""Input schema for MyCustomTool."""
|
"""Input schema for MyCustomTool."""
|
||||||
argument: str = Field(..., description="Description of the argument.")
|
argument: str = Field(..., description="Description of the argument.")
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
from crewai_tools import BaseTool
|
from crewai.tools import BaseTool
|
||||||
|
|
||||||
|
|
||||||
class {{class_name}}(BaseTool):
|
class {{class_name}}(BaseTool):
|
||||||
name: str = "Name of my tool"
|
name: str = "Name of my tool"
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ from crewai.task import Task
|
|||||||
from crewai.tasks.conditional_task import ConditionalTask
|
from crewai.tasks.conditional_task import ConditionalTask
|
||||||
from crewai.tasks.task_output import TaskOutput
|
from crewai.tasks.task_output import TaskOutput
|
||||||
from crewai.telemetry import Telemetry
|
from crewai.telemetry import Telemetry
|
||||||
from crewai.tools.agent_tools import AgentTools
|
from crewai.tools.agent_tools.agent_tools import AgentTools
|
||||||
from crewai.types.usage_metrics import UsageMetrics
|
from crewai.types.usage_metrics import UsageMetrics
|
||||||
from crewai.utilities import I18N, FileHandler, Logger, RPMController
|
from crewai.utilities import I18N, FileHandler, Logger, RPMController
|
||||||
from crewai.utilities.constants import (
|
from crewai.utilities.constants import (
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ from pydantic import (
|
|||||||
from pydantic_core import PydanticCustomError
|
from pydantic_core import PydanticCustomError
|
||||||
|
|
||||||
from crewai.agents.agent_builder.base_agent import BaseAgent
|
from crewai.agents.agent_builder.base_agent import BaseAgent
|
||||||
|
from crewai.tools.base_tool import BaseTool
|
||||||
from crewai.tasks.output_format import OutputFormat
|
from crewai.tasks.output_format import OutputFormat
|
||||||
from crewai.tasks.task_output import TaskOutput
|
from crewai.tasks.task_output import TaskOutput
|
||||||
from crewai.telemetry.telemetry import Telemetry
|
from crewai.telemetry.telemetry import Telemetry
|
||||||
@@ -91,7 +92,7 @@ class Task(BaseModel):
|
|||||||
output: Optional[TaskOutput] = Field(
|
output: Optional[TaskOutput] = Field(
|
||||||
description="Task output, it's final result after being executed", default=None
|
description="Task output, it's final result after being executed", default=None
|
||||||
)
|
)
|
||||||
tools: Optional[List[Any]] = Field(
|
tools: Optional[List[BaseTool]] = Field(
|
||||||
default_factory=list,
|
default_factory=list,
|
||||||
description="Tools the agent is limited to use for this task.",
|
description="Tools the agent is limited to use for this task.",
|
||||||
)
|
)
|
||||||
@@ -185,7 +186,7 @@ class Task(BaseModel):
|
|||||||
self,
|
self,
|
||||||
agent: Optional[BaseAgent] = None,
|
agent: Optional[BaseAgent] = None,
|
||||||
context: Optional[str] = None,
|
context: Optional[str] = None,
|
||||||
tools: Optional[List[Any]] = None,
|
tools: Optional[List[BaseTool]] = None,
|
||||||
) -> TaskOutput:
|
) -> TaskOutput:
|
||||||
"""Execute the task synchronously."""
|
"""Execute the task synchronously."""
|
||||||
return self._execute_core(agent, context, tools)
|
return self._execute_core(agent, context, tools)
|
||||||
@@ -202,7 +203,7 @@ class Task(BaseModel):
|
|||||||
self,
|
self,
|
||||||
agent: BaseAgent | None = None,
|
agent: BaseAgent | None = None,
|
||||||
context: Optional[str] = None,
|
context: Optional[str] = None,
|
||||||
tools: Optional[List[Any]] = None,
|
tools: Optional[List[BaseTool]] = None,
|
||||||
) -> Future[TaskOutput]:
|
) -> Future[TaskOutput]:
|
||||||
"""Execute the task asynchronously."""
|
"""Execute the task asynchronously."""
|
||||||
future: Future[TaskOutput] = Future()
|
future: Future[TaskOutput] = Future()
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
from .base_tool import BaseTool, tool
|
||||||
|
|||||||
@@ -1,25 +0,0 @@
|
|||||||
from crewai.agents.agent_builder.utilities.base_agent_tool import BaseAgentTools
|
|
||||||
|
|
||||||
|
|
||||||
class AgentTools(BaseAgentTools):
|
|
||||||
"""Default tools around agent delegation"""
|
|
||||||
|
|
||||||
def tools(self):
|
|
||||||
from langchain.tools import StructuredTool
|
|
||||||
|
|
||||||
coworkers = ", ".join([f"{agent.role}" for agent in self.agents])
|
|
||||||
tools = [
|
|
||||||
StructuredTool.from_function(
|
|
||||||
func=self.delegate_work,
|
|
||||||
name="Delegate work to coworker",
|
|
||||||
description=self.i18n.tools("delegate_work").format(
|
|
||||||
coworkers=coworkers
|
|
||||||
),
|
|
||||||
),
|
|
||||||
StructuredTool.from_function(
|
|
||||||
func=self.ask_question,
|
|
||||||
name="Ask question to coworker",
|
|
||||||
description=self.i18n.tools("ask_question").format(coworkers=coworkers),
|
|
||||||
),
|
|
||||||
]
|
|
||||||
return tools
|
|
||||||
32
src/crewai/tools/agent_tools/agent_tools.py
Normal file
32
src/crewai/tools/agent_tools/agent_tools.py
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
from crewai.tools.base_tool import BaseTool
|
||||||
|
from crewai.agents.agent_builder.base_agent import BaseAgent
|
||||||
|
from crewai.utilities import I18N
|
||||||
|
|
||||||
|
from .delegate_work_tool import DelegateWorkTool
|
||||||
|
from .ask_question_tool import AskQuestionTool
|
||||||
|
|
||||||
|
|
||||||
|
class AgentTools:
|
||||||
|
"""Manager class for agent-related tools"""
|
||||||
|
|
||||||
|
def __init__(self, agents: list[BaseAgent], i18n: I18N = I18N()):
|
||||||
|
self.agents = agents
|
||||||
|
self.i18n = i18n
|
||||||
|
|
||||||
|
def tools(self) -> list[BaseTool]:
|
||||||
|
"""Get all available agent tools"""
|
||||||
|
coworkers = ", ".join([f"{agent.role}" for agent in self.agents])
|
||||||
|
|
||||||
|
delegate_tool = DelegateWorkTool(
|
||||||
|
agents=self.agents,
|
||||||
|
i18n=self.i18n,
|
||||||
|
description=self.i18n.tools("delegate_work").format(coworkers=coworkers),
|
||||||
|
)
|
||||||
|
|
||||||
|
ask_tool = AskQuestionTool(
|
||||||
|
agents=self.agents,
|
||||||
|
i18n=self.i18n,
|
||||||
|
description=self.i18n.tools("ask_question").format(coworkers=coworkers),
|
||||||
|
)
|
||||||
|
|
||||||
|
return [delegate_tool, ask_tool]
|
||||||
26
src/crewai/tools/agent_tools/ask_question_tool.py
Normal file
26
src/crewai/tools/agent_tools/ask_question_tool.py
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
from crewai.tools.agent_tools.base_agent_tools import BaseAgentTool
|
||||||
|
from typing import Optional
|
||||||
|
from pydantic import BaseModel, Field
|
||||||
|
|
||||||
|
|
||||||
|
class AskQuestionToolSchema(BaseModel):
|
||||||
|
question: str = Field(..., description="The question to ask")
|
||||||
|
context: str = Field(..., description="The context for the question")
|
||||||
|
coworker: str = Field(..., description="The role/name of the coworker to ask")
|
||||||
|
|
||||||
|
|
||||||
|
class AskQuestionTool(BaseAgentTool):
|
||||||
|
"""Tool for asking questions to coworkers"""
|
||||||
|
|
||||||
|
name: str = "Ask question to coworker"
|
||||||
|
args_schema: type[BaseModel] = AskQuestionToolSchema
|
||||||
|
|
||||||
|
def _run(
|
||||||
|
self,
|
||||||
|
question: str,
|
||||||
|
context: str,
|
||||||
|
coworker: Optional[str] = None,
|
||||||
|
**kwargs,
|
||||||
|
) -> str:
|
||||||
|
coworker = self._get_coworker(coworker, **kwargs)
|
||||||
|
return self._execute(coworker, question, context)
|
||||||
@@ -1,22 +1,19 @@
|
|||||||
from abc import ABC, abstractmethod
|
from typing import Optional, Union
|
||||||
from typing import List, Optional, Union
|
from pydantic import Field
|
||||||
|
|
||||||
from pydantic import BaseModel, Field
|
|
||||||
|
|
||||||
|
from crewai.tools.base_tool import BaseTool
|
||||||
from crewai.agents.agent_builder.base_agent import BaseAgent
|
from crewai.agents.agent_builder.base_agent import BaseAgent
|
||||||
from crewai.task import Task
|
from crewai.task import Task
|
||||||
from crewai.utilities import I18N
|
from crewai.utilities import I18N
|
||||||
|
|
||||||
|
|
||||||
class BaseAgentTools(BaseModel, ABC):
|
class BaseAgentTool(BaseTool):
|
||||||
"""Default tools around agent delegation"""
|
"""Base class for agent-related tools"""
|
||||||
|
|
||||||
agents: List[BaseAgent] = Field(description="List of agents in this crew.")
|
agents: list[BaseAgent] = Field(description="List of available agents")
|
||||||
i18n: I18N = Field(default=I18N(), description="Internationalization settings.")
|
i18n: I18N = Field(
|
||||||
|
default_factory=I18N, description="Internationalization settings"
|
||||||
@abstractmethod
|
)
|
||||||
def tools(self):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def _get_coworker(self, coworker: Optional[str], **kwargs) -> Optional[str]:
|
def _get_coworker(self, coworker: Optional[str], **kwargs) -> Optional[str]:
|
||||||
coworker = coworker or kwargs.get("co_worker") or kwargs.get("coworker")
|
coworker = coworker or kwargs.get("co_worker") or kwargs.get("coworker")
|
||||||
@@ -24,27 +21,11 @@ class BaseAgentTools(BaseModel, ABC):
|
|||||||
is_list = coworker.startswith("[") and coworker.endswith("]")
|
is_list = coworker.startswith("[") and coworker.endswith("]")
|
||||||
if is_list:
|
if is_list:
|
||||||
coworker = coworker[1:-1].split(",")[0]
|
coworker = coworker[1:-1].split(",")[0]
|
||||||
|
|
||||||
return coworker
|
return coworker
|
||||||
|
|
||||||
def delegate_work(
|
|
||||||
self, task: str, context: str, coworker: Optional[str] = None, **kwargs
|
|
||||||
):
|
|
||||||
"""Useful to delegate a specific task to a coworker passing all necessary context and names."""
|
|
||||||
coworker = self._get_coworker(coworker, **kwargs)
|
|
||||||
return self._execute(coworker, task, context)
|
|
||||||
|
|
||||||
def ask_question(
|
|
||||||
self, question: str, context: str, coworker: Optional[str] = None, **kwargs
|
|
||||||
):
|
|
||||||
"""Useful to ask a question, opinion or take from a coworker passing all necessary context and names."""
|
|
||||||
coworker = self._get_coworker(coworker, **kwargs)
|
|
||||||
return self._execute(coworker, question, context)
|
|
||||||
|
|
||||||
def _execute(
|
def _execute(
|
||||||
self, agent_name: Union[str, None], task: str, context: Union[str, None]
|
self, agent_name: Union[str, None], task: str, context: Union[str, None]
|
||||||
):
|
) -> str:
|
||||||
"""Execute the command."""
|
|
||||||
try:
|
try:
|
||||||
if agent_name is None:
|
if agent_name is None:
|
||||||
agent_name = ""
|
agent_name = ""
|
||||||
@@ -57,7 +38,6 @@ class BaseAgentTools(BaseModel, ABC):
|
|||||||
# when it should look like this:
|
# when it should look like this:
|
||||||
# {"task": "....", "coworker": "...."}
|
# {"task": "....", "coworker": "...."}
|
||||||
agent_name = agent_name.casefold().replace('"', "").replace("\n", "")
|
agent_name = agent_name.casefold().replace('"', "").replace("\n", "")
|
||||||
|
|
||||||
agent = [ # type: ignore # Incompatible types in assignment (expression has type "list[BaseAgent]", variable has type "str | None")
|
agent = [ # type: ignore # Incompatible types in assignment (expression has type "list[BaseAgent]", variable has type "str | None")
|
||||||
available_agent
|
available_agent
|
||||||
for available_agent in self.agents
|
for available_agent in self.agents
|
||||||
29
src/crewai/tools/agent_tools/delegate_work_tool.py
Normal file
29
src/crewai/tools/agent_tools/delegate_work_tool.py
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
from crewai.tools.agent_tools.base_agent_tools import BaseAgentTool
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
from pydantic import BaseModel, Field
|
||||||
|
|
||||||
|
|
||||||
|
class DelegateWorkToolSchema(BaseModel):
|
||||||
|
task: str = Field(..., description="The task to delegate")
|
||||||
|
context: str = Field(..., description="The context for the task")
|
||||||
|
coworker: str = Field(
|
||||||
|
..., description="The role/name of the coworker to delegate to"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class DelegateWorkTool(BaseAgentTool):
|
||||||
|
"""Tool for delegating work to coworkers"""
|
||||||
|
|
||||||
|
name: str = "Delegate work to coworker"
|
||||||
|
args_schema: type[BaseModel] = DelegateWorkToolSchema
|
||||||
|
|
||||||
|
def _run(
|
||||||
|
self,
|
||||||
|
task: str,
|
||||||
|
context: str,
|
||||||
|
coworker: Optional[str] = None,
|
||||||
|
**kwargs,
|
||||||
|
) -> str:
|
||||||
|
coworker = self._get_coworker(coworker, **kwargs)
|
||||||
|
return self._execute(coworker, task, context)
|
||||||
186
src/crewai/tools/base_tool.py
Normal file
186
src/crewai/tools/base_tool.py
Normal file
@@ -0,0 +1,186 @@
|
|||||||
|
from abc import ABC, abstractmethod
|
||||||
|
from typing import Any, Callable, Type, get_args, get_origin
|
||||||
|
|
||||||
|
from langchain_core.tools import StructuredTool
|
||||||
|
from pydantic import BaseModel, ConfigDict, Field, validator
|
||||||
|
from pydantic import BaseModel as PydanticBaseModel
|
||||||
|
|
||||||
|
|
||||||
|
class BaseTool(BaseModel, ABC):
|
||||||
|
class _ArgsSchemaPlaceholder(PydanticBaseModel):
|
||||||
|
pass
|
||||||
|
|
||||||
|
model_config = ConfigDict()
|
||||||
|
|
||||||
|
name: str
|
||||||
|
"""The unique name of the tool that clearly communicates its purpose."""
|
||||||
|
description: str
|
||||||
|
"""Used to tell the model how/when/why to use the tool."""
|
||||||
|
args_schema: Type[PydanticBaseModel] = Field(default_factory=_ArgsSchemaPlaceholder)
|
||||||
|
"""The schema for the arguments that the tool accepts."""
|
||||||
|
description_updated: bool = False
|
||||||
|
"""Flag to check if the description has been updated."""
|
||||||
|
cache_function: Callable = lambda _args=None, _result=None: 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[PydanticBaseModel]
|
||||||
|
) -> Type[PydanticBaseModel]:
|
||||||
|
if not isinstance(v, cls._ArgsSchemaPlaceholder):
|
||||||
|
return v
|
||||||
|
|
||||||
|
return type(
|
||||||
|
f"{cls.__name__}Schema",
|
||||||
|
(PydanticBaseModel,),
|
||||||
|
{
|
||||||
|
"__annotations__": {
|
||||||
|
k: v for k, v in cls._run.__annotations__.items() if k != "return"
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
def model_post_init(self, __context: Any) -> None:
|
||||||
|
self._generate_description()
|
||||||
|
|
||||||
|
super().model_post_init(__context)
|
||||||
|
|
||||||
|
def run(
|
||||||
|
self,
|
||||||
|
*args: Any,
|
||||||
|
**kwargs: Any,
|
||||||
|
) -> Any:
|
||||||
|
print(f"Using Tool: {self.name}")
|
||||||
|
return self._run(*args, **kwargs)
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def _run(
|
||||||
|
self,
|
||||||
|
*args: Any,
|
||||||
|
**kwargs: Any,
|
||||||
|
) -> Any:
|
||||||
|
"""Here goes the actual implementation of the tool."""
|
||||||
|
|
||||||
|
def to_langchain(self) -> StructuredTool:
|
||||||
|
self._set_args_schema()
|
||||||
|
return StructuredTool(
|
||||||
|
name=self.name,
|
||||||
|
description=self.description,
|
||||||
|
args_schema=self.args_schema,
|
||||||
|
func=self._run,
|
||||||
|
)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_langchain(cls, tool: StructuredTool) -> "BaseTool":
|
||||||
|
if cls == Tool:
|
||||||
|
if tool.func is None:
|
||||||
|
raise ValueError("StructuredTool must have a callable 'func'")
|
||||||
|
return Tool(
|
||||||
|
name=tool.name,
|
||||||
|
description=tool.description,
|
||||||
|
args_schema=tool.args_schema,
|
||||||
|
func=tool.func,
|
||||||
|
)
|
||||||
|
raise NotImplementedError(f"from_langchain not implemented for {cls.__name__}")
|
||||||
|
|
||||||
|
def _set_args_schema(self):
|
||||||
|
if self.args_schema is None:
|
||||||
|
class_name = f"{self.__class__.__name__}Schema"
|
||||||
|
self.args_schema = type(
|
||||||
|
class_name,
|
||||||
|
(PydanticBaseModel,),
|
||||||
|
{
|
||||||
|
"__annotations__": {
|
||||||
|
k: v
|
||||||
|
for k, v in self._run.__annotations__.items()
|
||||||
|
if k != "return"
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
def _generate_description(self):
|
||||||
|
args_schema = {
|
||||||
|
name: {
|
||||||
|
"description": field.description,
|
||||||
|
"type": BaseTool._get_arg_annotations(field.annotation),
|
||||||
|
}
|
||||||
|
for name, field in self.args_schema.model_fields.items()
|
||||||
|
}
|
||||||
|
|
||||||
|
self.description = f"Tool Name: {self.name}\nTool Arguments: {args_schema}\nTool Description: {self.description}"
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _get_arg_annotations(annotation: type[Any] | None) -> str:
|
||||||
|
if annotation is None:
|
||||||
|
return "None"
|
||||||
|
|
||||||
|
origin = get_origin(annotation)
|
||||||
|
args = get_args(annotation)
|
||||||
|
|
||||||
|
if origin is None:
|
||||||
|
return (
|
||||||
|
annotation.__name__
|
||||||
|
if hasattr(annotation, "__name__")
|
||||||
|
else str(annotation)
|
||||||
|
)
|
||||||
|
|
||||||
|
if args:
|
||||||
|
args_str = ", ".join(BaseTool._get_arg_annotations(arg) for arg in args)
|
||||||
|
return f"{origin.__name__}[{args_str}]"
|
||||||
|
|
||||||
|
return origin.__name__
|
||||||
|
|
||||||
|
|
||||||
|
class Tool(BaseTool):
|
||||||
|
func: Callable
|
||||||
|
"""The function that will be executed when the tool is called."""
|
||||||
|
|
||||||
|
def _run(self, *args: Any, **kwargs: Any) -> Any:
|
||||||
|
return self.func(*args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
def to_langchain(
|
||||||
|
tools: list[BaseTool | StructuredTool],
|
||||||
|
) -> list[StructuredTool]:
|
||||||
|
return [t.to_langchain() if isinstance(t, BaseTool) else t for t in tools]
|
||||||
|
|
||||||
|
|
||||||
|
def tool(*args):
|
||||||
|
"""
|
||||||
|
Decorator to create a tool from a function.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def _make_with_name(tool_name: str) -> Callable:
|
||||||
|
def _make_tool(f: Callable) -> BaseTool:
|
||||||
|
if f.__doc__ is None:
|
||||||
|
raise ValueError("Function must have a docstring")
|
||||||
|
if f.__annotations__ is None:
|
||||||
|
raise ValueError("Function must have type annotations")
|
||||||
|
|
||||||
|
class_name = "".join(tool_name.split()).title()
|
||||||
|
args_schema = type(
|
||||||
|
class_name,
|
||||||
|
(PydanticBaseModel,),
|
||||||
|
{
|
||||||
|
"__annotations__": {
|
||||||
|
k: v for k, v in f.__annotations__.items() if k != "return"
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
return Tool(
|
||||||
|
name=tool_name,
|
||||||
|
description=f.__doc__,
|
||||||
|
func=f,
|
||||||
|
args_schema=args_schema,
|
||||||
|
)
|
||||||
|
|
||||||
|
return _make_tool
|
||||||
|
|
||||||
|
if len(args) == 1 and callable(args[0]):
|
||||||
|
return _make_with_name(args[0].__name__)(args[0])
|
||||||
|
if len(args) == 1 and isinstance(args[0], str):
|
||||||
|
return _make_with_name(args[0])
|
||||||
|
raise ValueError("Invalid arguments")
|
||||||
@@ -10,6 +10,7 @@ import crewai.utilities.events as events
|
|||||||
from crewai.agents.tools_handler import ToolsHandler
|
from crewai.agents.tools_handler import ToolsHandler
|
||||||
from crewai.task import Task
|
from crewai.task import Task
|
||||||
from crewai.telemetry import Telemetry
|
from crewai.telemetry import Telemetry
|
||||||
|
from crewai.tools import BaseTool
|
||||||
from crewai.tools.tool_calling import InstructorToolCalling, ToolCalling
|
from crewai.tools.tool_calling import InstructorToolCalling, ToolCalling
|
||||||
from crewai.tools.tool_usage_events import ToolUsageError, ToolUsageFinished
|
from crewai.tools.tool_usage_events import ToolUsageError, ToolUsageFinished
|
||||||
from crewai.utilities import I18N, Converter, ConverterError, Printer
|
from crewai.utilities import I18N, Converter, ConverterError, Printer
|
||||||
@@ -49,7 +50,7 @@ class ToolUsage:
|
|||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
tools_handler: ToolsHandler,
|
tools_handler: ToolsHandler,
|
||||||
tools: List[Any],
|
tools: List[BaseTool],
|
||||||
original_tools: List[Any],
|
original_tools: List[Any],
|
||||||
tools_description: str,
|
tools_description: str,
|
||||||
tools_names: str,
|
tools_names: str,
|
||||||
@@ -298,22 +299,7 @@ class ToolUsage:
|
|||||||
"""Render the tool name and description in plain text."""
|
"""Render the tool name and description in plain text."""
|
||||||
descriptions = []
|
descriptions = []
|
||||||
for tool in self.tools:
|
for tool in self.tools:
|
||||||
args = {
|
descriptions.append(tool.description)
|
||||||
name: {
|
|
||||||
"description": field.description,
|
|
||||||
"type": field.annotation.__name__,
|
|
||||||
}
|
|
||||||
for name, field in tool.args_schema.model_fields.items()
|
|
||||||
}
|
|
||||||
descriptions.append(
|
|
||||||
"\n".join(
|
|
||||||
[
|
|
||||||
f"Tool Name: {tool.name.lower()}",
|
|
||||||
f"Tool Description: {tool.description}",
|
|
||||||
f"Tool Arguments: {args}",
|
|
||||||
]
|
|
||||||
)
|
|
||||||
)
|
|
||||||
return "\n--\n".join(descriptions)
|
return "\n--\n".join(descriptions)
|
||||||
|
|
||||||
def _function_calling(self, tool_string: str):
|
def _function_calling(self, tool_string: str):
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ from unittest import mock
|
|||||||
from unittest.mock import patch
|
from unittest.mock import patch
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
from crewai_tools import tool
|
|
||||||
|
|
||||||
from crewai import Agent, Crew, Task
|
from crewai import Agent, Crew, Task
|
||||||
from crewai.agents.cache import CacheHandler
|
from crewai.agents.cache import CacheHandler
|
||||||
@@ -14,6 +13,7 @@ from crewai.agents.parser import AgentAction, CrewAgentParser, OutputParserExcep
|
|||||||
from crewai.llm import LLM
|
from crewai.llm import LLM
|
||||||
from crewai.tools.tool_calling import InstructorToolCalling
|
from crewai.tools.tool_calling import InstructorToolCalling
|
||||||
from crewai.tools.tool_usage import ToolUsage
|
from crewai.tools.tool_usage import ToolUsage
|
||||||
|
from crewai.tools import tool
|
||||||
from crewai.tools.tool_usage_events import ToolUsageFinished
|
from crewai.tools.tool_usage_events import ToolUsageFinished
|
||||||
from crewai.utilities import RPMController
|
from crewai.utilities import RPMController
|
||||||
from crewai.utilities.events import Emitter
|
from crewai.utilities.events import Emitter
|
||||||
@@ -277,9 +277,10 @@ def test_cache_hitting():
|
|||||||
"multiplier-{'first_number': 12, 'second_number': 3}": 36,
|
"multiplier-{'first_number': 12, 'second_number': 3}": 36,
|
||||||
}
|
}
|
||||||
|
|
||||||
with patch.object(CacheHandler, "read") as read, patch.object(
|
with (
|
||||||
Emitter, "emit"
|
patch.object(CacheHandler, "read") as read,
|
||||||
) as emit:
|
patch.object(Emitter, "emit") as emit,
|
||||||
|
):
|
||||||
read.return_value = "0"
|
read.return_value = "0"
|
||||||
task = Task(
|
task = Task(
|
||||||
description="What is 2 times 6? Ignore correctness and just return the result of the multiplication tool, you must use the tool.",
|
description="What is 2 times 6? Ignore correctness and just return the result of the multiplication tool, you must use the tool.",
|
||||||
@@ -604,7 +605,7 @@ def test_agent_respect_the_max_rpm_set(capsys):
|
|||||||
def test_agent_respect_the_max_rpm_set_over_crew_rpm(capsys):
|
def test_agent_respect_the_max_rpm_set_over_crew_rpm(capsys):
|
||||||
from unittest.mock import patch
|
from unittest.mock import patch
|
||||||
|
|
||||||
from crewai_tools import tool
|
from crewai.tools import tool
|
||||||
|
|
||||||
@tool
|
@tool
|
||||||
def get_final_answer() -> float:
|
def get_final_answer() -> float:
|
||||||
@@ -642,7 +643,7 @@ def test_agent_respect_the_max_rpm_set_over_crew_rpm(capsys):
|
|||||||
def test_agent_without_max_rpm_respet_crew_rpm(capsys):
|
def test_agent_without_max_rpm_respet_crew_rpm(capsys):
|
||||||
from unittest.mock import patch
|
from unittest.mock import patch
|
||||||
|
|
||||||
from crewai_tools import tool
|
from crewai.tools import tool
|
||||||
|
|
||||||
@tool
|
@tool
|
||||||
def get_final_answer() -> float:
|
def get_final_answer() -> float:
|
||||||
@@ -696,7 +697,7 @@ def test_agent_without_max_rpm_respet_crew_rpm(capsys):
|
|||||||
def test_agent_error_on_parsing_tool(capsys):
|
def test_agent_error_on_parsing_tool(capsys):
|
||||||
from unittest.mock import patch
|
from unittest.mock import patch
|
||||||
|
|
||||||
from crewai_tools import tool
|
from crewai.tools import tool
|
||||||
|
|
||||||
@tool
|
@tool
|
||||||
def get_final_answer() -> float:
|
def get_final_answer() -> float:
|
||||||
@@ -739,7 +740,7 @@ def test_agent_error_on_parsing_tool(capsys):
|
|||||||
def test_agent_remembers_output_format_after_using_tools_too_many_times():
|
def test_agent_remembers_output_format_after_using_tools_too_many_times():
|
||||||
from unittest.mock import patch
|
from unittest.mock import patch
|
||||||
|
|
||||||
from crewai_tools import tool
|
from crewai.tools import tool
|
||||||
|
|
||||||
@tool
|
@tool
|
||||||
def get_final_answer() -> float:
|
def get_final_answer() -> float:
|
||||||
@@ -863,11 +864,16 @@ def test_agent_function_calling_llm():
|
|||||||
|
|
||||||
from crewai.tools.tool_usage import ToolUsage
|
from crewai.tools.tool_usage import ToolUsage
|
||||||
|
|
||||||
with patch.object(
|
with (
|
||||||
|
patch.object(
|
||||||
instructor, "from_litellm", wraps=instructor.from_litellm
|
instructor, "from_litellm", wraps=instructor.from_litellm
|
||||||
) as mock_from_litellm, patch.object(
|
) as mock_from_litellm,
|
||||||
ToolUsage, "_original_tool_calling", side_effect=Exception("Forced exception")
|
patch.object(
|
||||||
) as mock_original_tool_calling:
|
ToolUsage,
|
||||||
|
"_original_tool_calling",
|
||||||
|
side_effect=Exception("Forced exception"),
|
||||||
|
) as mock_original_tool_calling,
|
||||||
|
):
|
||||||
crew.kickoff()
|
crew.kickoff()
|
||||||
mock_from_litellm.assert_called()
|
mock_from_litellm.assert_called()
|
||||||
mock_original_tool_calling.assert_called()
|
mock_original_tool_calling.assert_called()
|
||||||
@@ -894,7 +900,7 @@ def test_agent_count_formatting_error():
|
|||||||
|
|
||||||
@pytest.mark.vcr(filter_headers=["authorization"])
|
@pytest.mark.vcr(filter_headers=["authorization"])
|
||||||
def test_tool_result_as_answer_is_the_final_answer_for_the_agent():
|
def test_tool_result_as_answer_is_the_final_answer_for_the_agent():
|
||||||
from crewai_tools import BaseTool
|
from crewai.tools import BaseTool
|
||||||
|
|
||||||
class MyCustomTool(BaseTool):
|
class MyCustomTool(BaseTool):
|
||||||
name: str = "Get Greetings"
|
name: str = "Get Greetings"
|
||||||
@@ -924,7 +930,7 @@ def test_tool_result_as_answer_is_the_final_answer_for_the_agent():
|
|||||||
|
|
||||||
@pytest.mark.vcr(filter_headers=["authorization"])
|
@pytest.mark.vcr(filter_headers=["authorization"])
|
||||||
def test_tool_usage_information_is_appended_to_agent():
|
def test_tool_usage_information_is_appended_to_agent():
|
||||||
from crewai_tools import BaseTool
|
from crewai.tools import BaseTool
|
||||||
|
|
||||||
class MyCustomTool(BaseTool):
|
class MyCustomTool(BaseTool):
|
||||||
name: str = "Decide Greetings"
|
name: str = "Decide Greetings"
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import hashlib
|
|||||||
from typing import Any, List, Optional
|
from typing import Any, List, Optional
|
||||||
|
|
||||||
from crewai.agents.agent_builder.base_agent import BaseAgent
|
from crewai.agents.agent_builder.base_agent import BaseAgent
|
||||||
|
from crewai.tools.base_tool import BaseTool
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
|
|
||||||
|
|
||||||
@@ -10,13 +11,13 @@ class TestAgent(BaseAgent):
|
|||||||
self,
|
self,
|
||||||
task: Any,
|
task: Any,
|
||||||
context: Optional[str] = None,
|
context: Optional[str] = None,
|
||||||
tools: Optional[List[Any]] = None,
|
tools: Optional[List[BaseTool]] = None,
|
||||||
) -> str:
|
) -> str:
|
||||||
return ""
|
return ""
|
||||||
|
|
||||||
def create_agent_executor(self, tools=None) -> None: ...
|
def create_agent_executor(self, tools=None) -> None: ...
|
||||||
|
|
||||||
def _parse_tools(self, tools: List[Any]) -> List[Any]:
|
def _parse_tools(self, tools: List[BaseTool]) -> List[BaseTool]:
|
||||||
return []
|
return []
|
||||||
|
|
||||||
def get_delegation_tools(self, agents: List["BaseAgent"]): ...
|
def get_delegation_tools(self, agents: List["BaseAgent"]): ...
|
||||||
|
|||||||
@@ -456,7 +456,7 @@ def test_crew_verbose_output(capsys):
|
|||||||
def test_cache_hitting_between_agents():
|
def test_cache_hitting_between_agents():
|
||||||
from unittest.mock import call, patch
|
from unittest.mock import call, patch
|
||||||
|
|
||||||
from crewai_tools import tool
|
from crewai.tools import tool
|
||||||
|
|
||||||
@tool
|
@tool
|
||||||
def multiplier(first_number: int, second_number: int) -> float:
|
def multiplier(first_number: int, second_number: int) -> float:
|
||||||
@@ -499,7 +499,7 @@ def test_cache_hitting_between_agents():
|
|||||||
def test_api_calls_throttling(capsys):
|
def test_api_calls_throttling(capsys):
|
||||||
from unittest.mock import patch
|
from unittest.mock import patch
|
||||||
|
|
||||||
from crewai_tools import tool
|
from crewai.tools import tool
|
||||||
|
|
||||||
@tool
|
@tool
|
||||||
def get_final_answer() -> float:
|
def get_final_answer() -> float:
|
||||||
@@ -1111,7 +1111,7 @@ def test_dont_set_agents_step_callback_if_already_set():
|
|||||||
def test_crew_function_calling_llm():
|
def test_crew_function_calling_llm():
|
||||||
from unittest.mock import patch
|
from unittest.mock import patch
|
||||||
|
|
||||||
from crewai_tools import tool
|
from crewai.tools import tool
|
||||||
|
|
||||||
llm = "gpt-4o"
|
llm = "gpt-4o"
|
||||||
|
|
||||||
@@ -1146,7 +1146,7 @@ def test_crew_function_calling_llm():
|
|||||||
|
|
||||||
@pytest.mark.vcr(filter_headers=["authorization"])
|
@pytest.mark.vcr(filter_headers=["authorization"])
|
||||||
def test_task_with_no_arguments():
|
def test_task_with_no_arguments():
|
||||||
from crewai_tools import tool
|
from crewai.tools import tool
|
||||||
|
|
||||||
@tool
|
@tool
|
||||||
def return_data() -> str:
|
def return_data() -> str:
|
||||||
@@ -1309,8 +1309,9 @@ def test_hierarchical_crew_creation_tasks_with_agents():
|
|||||||
|
|
||||||
assert crew.manager_agent is not None
|
assert crew.manager_agent is not None
|
||||||
assert crew.manager_agent.tools is not None
|
assert crew.manager_agent.tools is not None
|
||||||
assert crew.manager_agent.tools[0].description.startswith(
|
assert (
|
||||||
"Delegate a specific task to one of the following coworkers: Senior Writer"
|
"Delegate a specific task to one of the following coworkers: Senior Writer\n"
|
||||||
|
in crew.manager_agent.tools[0].description
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -1337,8 +1338,9 @@ def test_hierarchical_crew_creation_tasks_with_async_execution():
|
|||||||
crew.kickoff()
|
crew.kickoff()
|
||||||
assert crew.manager_agent is not None
|
assert crew.manager_agent is not None
|
||||||
assert crew.manager_agent.tools is not None
|
assert crew.manager_agent.tools is not None
|
||||||
assert crew.manager_agent.tools[0].description.startswith(
|
assert (
|
||||||
"Delegate a specific task to one of the following coworkers: Senior Writer\n"
|
"Delegate a specific task to one of the following coworkers: Senior Writer\n"
|
||||||
|
in crew.manager_agent.tools[0].description
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -1370,8 +1372,9 @@ def test_hierarchical_crew_creation_tasks_with_sync_last():
|
|||||||
crew.kickoff()
|
crew.kickoff()
|
||||||
assert crew.manager_agent is not None
|
assert crew.manager_agent is not None
|
||||||
assert crew.manager_agent.tools is not None
|
assert crew.manager_agent.tools is not None
|
||||||
assert crew.manager_agent.tools[0].description.startswith(
|
assert (
|
||||||
"Delegate a specific task to one of the following coworkers: Senior Writer, Researcher, CEO\n"
|
"Delegate a specific task to one of the following coworkers: Senior Writer, Researcher, CEO\n"
|
||||||
|
in crew.manager_agent.tools[0].description
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -1494,7 +1497,7 @@ def test_task_callback_on_crew():
|
|||||||
def test_tools_with_custom_caching():
|
def test_tools_with_custom_caching():
|
||||||
from unittest.mock import patch
|
from unittest.mock import patch
|
||||||
|
|
||||||
from crewai_tools import tool
|
from crewai.tools import tool
|
||||||
|
|
||||||
@tool
|
@tool
|
||||||
def multiplcation_tool(first_number: int, second_number: int) -> int:
|
def multiplcation_tool(first_number: int, second_number: int) -> int:
|
||||||
@@ -1696,7 +1699,7 @@ def test_manager_agent_in_agents_raises_exception():
|
|||||||
|
|
||||||
|
|
||||||
def test_manager_agent_with_tools_raises_exception():
|
def test_manager_agent_with_tools_raises_exception():
|
||||||
from crewai_tools import tool
|
from crewai.tools import tool
|
||||||
|
|
||||||
@tool
|
@tool
|
||||||
def testing_tool(first_number: int, second_number: int) -> int:
|
def testing_tool(first_number: int, second_number: int) -> int:
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ from pydantic_core import ValidationError
|
|||||||
|
|
||||||
|
|
||||||
def test_task_tool_reflect_agent_tools():
|
def test_task_tool_reflect_agent_tools():
|
||||||
from crewai_tools import tool
|
from crewai.tools import tool
|
||||||
|
|
||||||
@tool
|
@tool
|
||||||
def fake_tool() -> None:
|
def fake_tool() -> None:
|
||||||
@@ -39,7 +39,7 @@ def test_task_tool_reflect_agent_tools():
|
|||||||
|
|
||||||
|
|
||||||
def test_task_tool_takes_precedence_over_agent_tools():
|
def test_task_tool_takes_precedence_over_agent_tools():
|
||||||
from crewai_tools import tool
|
from crewai.tools import tool
|
||||||
|
|
||||||
@tool
|
@tool
|
||||||
def fake_tool() -> None:
|
def fake_tool() -> None:
|
||||||
@@ -656,7 +656,7 @@ def test_increment_delegations_for_sequential_process():
|
|||||||
|
|
||||||
@pytest.mark.vcr(filter_headers=["authorization"])
|
@pytest.mark.vcr(filter_headers=["authorization"])
|
||||||
def test_increment_tool_errors():
|
def test_increment_tool_errors():
|
||||||
from crewai_tools import tool
|
from crewai.tools import tool
|
||||||
|
|
||||||
@tool
|
@tool
|
||||||
def scoring_examples() -> None:
|
def scoring_examples() -> None:
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from crewai.agent import Agent
|
from crewai.agent import Agent
|
||||||
from crewai.tools.agent_tools import AgentTools
|
from crewai.tools.agent_tools.agent_tools import AgentTools
|
||||||
|
|
||||||
researcher = Agent(
|
researcher = Agent(
|
||||||
role="researcher",
|
role="researcher",
|
||||||
@@ -11,12 +11,14 @@ researcher = Agent(
|
|||||||
backstory="You're an expert researcher, specialized in technology",
|
backstory="You're an expert researcher, specialized in technology",
|
||||||
allow_delegation=False,
|
allow_delegation=False,
|
||||||
)
|
)
|
||||||
tools = AgentTools(agents=[researcher])
|
tools = AgentTools(agents=[researcher]).tools()
|
||||||
|
delegate_tool = tools[0]
|
||||||
|
ask_tool = tools[1]
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.vcr(filter_headers=["authorization"])
|
@pytest.mark.vcr(filter_headers=["authorization"])
|
||||||
def test_delegate_work():
|
def test_delegate_work():
|
||||||
result = tools.delegate_work(
|
result = delegate_tool.run(
|
||||||
coworker="researcher",
|
coworker="researcher",
|
||||||
task="share your take on AI Agents",
|
task="share your take on AI Agents",
|
||||||
context="I heard you hate them",
|
context="I heard you hate them",
|
||||||
@@ -30,8 +32,8 @@ def test_delegate_work():
|
|||||||
|
|
||||||
@pytest.mark.vcr(filter_headers=["authorization"])
|
@pytest.mark.vcr(filter_headers=["authorization"])
|
||||||
def test_delegate_work_with_wrong_co_worker_variable():
|
def test_delegate_work_with_wrong_co_worker_variable():
|
||||||
result = tools.delegate_work(
|
result = delegate_tool.run(
|
||||||
co_worker="researcher",
|
coworker="researcher",
|
||||||
task="share your take on AI Agents",
|
task="share your take on AI Agents",
|
||||||
context="I heard you hate them",
|
context="I heard you hate them",
|
||||||
)
|
)
|
||||||
@@ -44,7 +46,7 @@ def test_delegate_work_with_wrong_co_worker_variable():
|
|||||||
|
|
||||||
@pytest.mark.vcr(filter_headers=["authorization"])
|
@pytest.mark.vcr(filter_headers=["authorization"])
|
||||||
def test_ask_question():
|
def test_ask_question():
|
||||||
result = tools.ask_question(
|
result = ask_tool.run(
|
||||||
coworker="researcher",
|
coworker="researcher",
|
||||||
question="do you hate AI Agents?",
|
question="do you hate AI Agents?",
|
||||||
context="I heard you LOVE them",
|
context="I heard you LOVE them",
|
||||||
@@ -58,8 +60,8 @@ def test_ask_question():
|
|||||||
|
|
||||||
@pytest.mark.vcr(filter_headers=["authorization"])
|
@pytest.mark.vcr(filter_headers=["authorization"])
|
||||||
def test_ask_question_with_wrong_co_worker_variable():
|
def test_ask_question_with_wrong_co_worker_variable():
|
||||||
result = tools.ask_question(
|
result = ask_tool.run(
|
||||||
co_worker="researcher",
|
coworker="researcher",
|
||||||
question="do you hate AI Agents?",
|
question="do you hate AI Agents?",
|
||||||
context="I heard you LOVE them",
|
context="I heard you LOVE them",
|
||||||
)
|
)
|
||||||
@@ -72,8 +74,8 @@ def test_ask_question_with_wrong_co_worker_variable():
|
|||||||
|
|
||||||
@pytest.mark.vcr(filter_headers=["authorization"])
|
@pytest.mark.vcr(filter_headers=["authorization"])
|
||||||
def test_delegate_work_withwith_coworker_as_array():
|
def test_delegate_work_withwith_coworker_as_array():
|
||||||
result = tools.delegate_work(
|
result = delegate_tool.run(
|
||||||
co_worker="[researcher]",
|
coworker="[researcher]",
|
||||||
task="share your take on AI Agents",
|
task="share your take on AI Agents",
|
||||||
context="I heard you hate them",
|
context="I heard you hate them",
|
||||||
)
|
)
|
||||||
@@ -86,8 +88,8 @@ def test_delegate_work_withwith_coworker_as_array():
|
|||||||
|
|
||||||
@pytest.mark.vcr(filter_headers=["authorization"])
|
@pytest.mark.vcr(filter_headers=["authorization"])
|
||||||
def test_ask_question_with_coworker_as_array():
|
def test_ask_question_with_coworker_as_array():
|
||||||
result = tools.ask_question(
|
result = ask_tool.run(
|
||||||
co_worker="[researcher]",
|
coworker="[researcher]",
|
||||||
question="do you hate AI Agents?",
|
question="do you hate AI Agents?",
|
||||||
context="I heard you LOVE them",
|
context="I heard you LOVE them",
|
||||||
)
|
)
|
||||||
@@ -99,7 +101,7 @@ def test_ask_question_with_coworker_as_array():
|
|||||||
|
|
||||||
|
|
||||||
def test_delegate_work_to_wrong_agent():
|
def test_delegate_work_to_wrong_agent():
|
||||||
result = tools.ask_question(
|
result = ask_tool.run(
|
||||||
coworker="writer",
|
coworker="writer",
|
||||||
question="share your take on AI Agents",
|
question="share your take on AI Agents",
|
||||||
context="I heard you hate them",
|
context="I heard you hate them",
|
||||||
@@ -112,7 +114,7 @@ def test_delegate_work_to_wrong_agent():
|
|||||||
|
|
||||||
|
|
||||||
def test_ask_question_to_wrong_agent():
|
def test_ask_question_to_wrong_agent():
|
||||||
result = tools.ask_question(
|
result = ask_tool.run(
|
||||||
coworker="writer",
|
coworker="writer",
|
||||||
question="do you hate AI Agents?",
|
question="do you hate AI Agents?",
|
||||||
context="I heard you LOVE them",
|
context="I heard you LOVE them",
|
||||||
109
tests/tools/test_base_tool.py
Normal file
109
tests/tools/test_base_tool.py
Normal file
@@ -0,0 +1,109 @@
|
|||||||
|
from typing import Callable
|
||||||
|
from crewai.tools import BaseTool, tool
|
||||||
|
|
||||||
|
|
||||||
|
def test_creating_a_tool_using_annotation():
|
||||||
|
@tool("Name of my tool")
|
||||||
|
def my_tool(question: str) -> str:
|
||||||
|
"""Clear description for what this tool is useful for, you agent will need this information to use it."""
|
||||||
|
return question
|
||||||
|
|
||||||
|
# Assert all the right attributes were defined
|
||||||
|
assert my_tool.name == "Name of my tool"
|
||||||
|
assert (
|
||||||
|
my_tool.description
|
||||||
|
== "Tool Name: Name of my tool\nTool Arguments: {'question': {'description': None, 'type': 'str'}}\nTool Description: Clear description for what this tool is useful for, you agent will need this information to use it."
|
||||||
|
)
|
||||||
|
assert my_tool.args_schema.schema()["properties"] == {
|
||||||
|
"question": {"title": "Question", "type": "string"}
|
||||||
|
}
|
||||||
|
assert (
|
||||||
|
my_tool.func("What is the meaning of life?") == "What is the meaning of life?"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Assert the langchain tool conversion worked as expected
|
||||||
|
converted_tool = my_tool.to_langchain()
|
||||||
|
assert converted_tool.name == "Name of my tool"
|
||||||
|
|
||||||
|
assert (
|
||||||
|
converted_tool.description
|
||||||
|
== "Tool Name: Name of my tool\nTool Arguments: {'question': {'description': None, 'type': 'str'}}\nTool Description: Clear description for what this tool is useful for, you agent will need this information to use it."
|
||||||
|
)
|
||||||
|
assert converted_tool.args_schema.schema()["properties"] == {
|
||||||
|
"question": {"title": "Question", "type": "string"}
|
||||||
|
}
|
||||||
|
assert (
|
||||||
|
converted_tool.func("What is the meaning of life?")
|
||||||
|
== "What is the meaning of life?"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_creating_a_tool_using_baseclass():
|
||||||
|
class MyCustomTool(BaseTool):
|
||||||
|
name: str = "Name of my tool"
|
||||||
|
description: str = (
|
||||||
|
"Clear description for what this tool is useful for, you agent will need this information to use it."
|
||||||
|
)
|
||||||
|
|
||||||
|
def _run(self, question: str) -> str:
|
||||||
|
return question
|
||||||
|
|
||||||
|
my_tool = MyCustomTool()
|
||||||
|
# Assert all the right attributes were defined
|
||||||
|
assert my_tool.name == "Name of my tool"
|
||||||
|
|
||||||
|
assert (
|
||||||
|
my_tool.description
|
||||||
|
== "Tool Name: Name of my tool\nTool Arguments: {'question': {'description': None, 'type': 'str'}}\nTool Description: Clear description for what this tool is useful for, you agent will need this information to use it."
|
||||||
|
)
|
||||||
|
assert my_tool.args_schema.schema()["properties"] == {
|
||||||
|
"question": {"title": "Question", "type": "string"}
|
||||||
|
}
|
||||||
|
assert my_tool.run("What is the meaning of life?") == "What is the meaning of life?"
|
||||||
|
|
||||||
|
# Assert the langchain tool conversion worked as expected
|
||||||
|
converted_tool = my_tool.to_langchain()
|
||||||
|
assert converted_tool.name == "Name of my tool"
|
||||||
|
|
||||||
|
assert (
|
||||||
|
converted_tool.description
|
||||||
|
== "Tool Name: Name of my tool\nTool Arguments: {'question': {'description': None, 'type': 'str'}}\nTool Description: Clear description for what this tool is useful for, you agent will need this information to use it."
|
||||||
|
)
|
||||||
|
assert converted_tool.args_schema.schema()["properties"] == {
|
||||||
|
"question": {"title": "Question", "type": "string"}
|
||||||
|
}
|
||||||
|
assert (
|
||||||
|
converted_tool.run("What is the meaning of life?")
|
||||||
|
== "What is the meaning of life?"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_setting_cache_function():
|
||||||
|
class MyCustomTool(BaseTool):
|
||||||
|
name: str = "Name of my tool"
|
||||||
|
description: str = (
|
||||||
|
"Clear description for what this tool is useful for, you agent will need this information to use it."
|
||||||
|
)
|
||||||
|
cache_function: Callable = lambda: False
|
||||||
|
|
||||||
|
def _run(self, question: str) -> str:
|
||||||
|
return question
|
||||||
|
|
||||||
|
my_tool = MyCustomTool()
|
||||||
|
# Assert all the right attributes were defined
|
||||||
|
assert not my_tool.cache_function()
|
||||||
|
|
||||||
|
|
||||||
|
def test_default_cache_function_is_true():
|
||||||
|
class MyCustomTool(BaseTool):
|
||||||
|
name: str = "Name of my tool"
|
||||||
|
description: str = (
|
||||||
|
"Clear description for what this tool is useful for, you agent will need this information to use it."
|
||||||
|
)
|
||||||
|
|
||||||
|
def _run(self, question: str) -> str:
|
||||||
|
return question
|
||||||
|
|
||||||
|
my_tool = MyCustomTool()
|
||||||
|
# Assert all the right attributes were defined
|
||||||
|
assert my_tool.cache_function()
|
||||||
@@ -3,11 +3,11 @@ import random
|
|||||||
from unittest.mock import MagicMock
|
from unittest.mock import MagicMock
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
from crewai_tools import BaseTool
|
|
||||||
from pydantic import BaseModel, Field
|
from pydantic import BaseModel, Field
|
||||||
|
|
||||||
from crewai import Agent, Task
|
from crewai import Agent, Task
|
||||||
from crewai.tools.tool_usage import ToolUsage
|
from crewai.tools.tool_usage import ToolUsage
|
||||||
|
from crewai.tools import BaseTool
|
||||||
|
|
||||||
|
|
||||||
class RandomNumberToolInput(BaseModel):
|
class RandomNumberToolInput(BaseModel):
|
||||||
@@ -103,11 +103,7 @@ def test_tool_usage_render():
|
|||||||
rendered = tool_usage._render()
|
rendered = tool_usage._render()
|
||||||
|
|
||||||
# Updated checks to match the actual output
|
# Updated checks to match the actual output
|
||||||
assert "Tool Name: random number generator" in rendered
|
assert "Tool Name: Random Number Generator" in rendered
|
||||||
assert (
|
|
||||||
"Random Number Generator(min_value: 'integer', max_value: 'integer') - Generates a random number within a specified range min_value: 'The minimum value of the range (inclusive)', max_value: 'The maximum value of the range (inclusive)'"
|
|
||||||
in rendered
|
|
||||||
)
|
|
||||||
assert "Tool Arguments:" in rendered
|
assert "Tool Arguments:" in rendered
|
||||||
assert (
|
assert (
|
||||||
"'min_value': {'description': 'The minimum value of the range (inclusive)', 'type': 'int'}"
|
"'min_value': {'description': 'The minimum value of the range (inclusive)', 'type': 'int'}"
|
||||||
@@ -117,3 +113,11 @@ def test_tool_usage_render():
|
|||||||
"'max_value': {'description': 'The maximum value of the range (inclusive)', 'type': 'int'}"
|
"'max_value': {'description': 'The maximum value of the range (inclusive)', 'type': 'int'}"
|
||||||
in rendered
|
in rendered
|
||||||
)
|
)
|
||||||
|
assert (
|
||||||
|
"Tool Description: Generates a random number within a specified range"
|
||||||
|
in rendered
|
||||||
|
)
|
||||||
|
assert (
|
||||||
|
"Tool Name: Random Number Generator\nTool Arguments: {'min_value': {'description': 'The minimum value of the range (inclusive)', 'type': 'int'}, 'max_value': {'description': 'The maximum value of the range (inclusive)', 'type': 'int'}}\nTool Description: Generates a random number within a specified range"
|
||||||
|
in rendered
|
||||||
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user