mirror of
https://github.com/crewAIInc/crewAI.git
synced 2026-01-21 05:48:14 +00:00
Compare commits
6 Commits
devin/1768
...
devin/1755
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ce13217f0c | ||
|
|
e5e6b11909 | ||
|
|
5495cccf2f | ||
|
|
d8eaadca47 | ||
|
|
be09b519c4 | ||
|
|
95d3b5dbc3 |
@@ -1,7 +1,7 @@
|
|||||||
import shutil
|
import shutil
|
||||||
import subprocess
|
import subprocess
|
||||||
import time
|
import time
|
||||||
from typing import Any, Callable, Dict, List, Literal, Optional, Sequence, Tuple, Type, Union
|
from typing import Any, Callable, Dict, List, Literal, Optional, Sequence, Tuple, Type, Union, cast
|
||||||
|
|
||||||
from pydantic import Field, InstanceOf, PrivateAttr, model_validator
|
from pydantic import Field, InstanceOf, PrivateAttr, model_validator
|
||||||
|
|
||||||
@@ -399,8 +399,13 @@ class Agent(BaseAgent):
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
tools = tools or self.tools or []
|
if tools is not None:
|
||||||
self.create_agent_executor(tools=tools, task=task)
|
agent_tools: List[Union[BaseTool, dict]] = cast(List[Union[BaseTool, dict]], tools)
|
||||||
|
elif self.tools is not None:
|
||||||
|
agent_tools = cast(List[Union[BaseTool, dict]], self.tools)
|
||||||
|
else:
|
||||||
|
agent_tools = []
|
||||||
|
self.create_agent_executor(tools=agent_tools, task=task)
|
||||||
|
|
||||||
if self.crew and self.crew._train:
|
if self.crew and self.crew._train:
|
||||||
task_prompt = self._training_handler(task_prompt=task_prompt)
|
task_prompt = self._training_handler(task_prompt=task_prompt)
|
||||||
@@ -537,14 +542,14 @@ class Agent(BaseAgent):
|
|||||||
)["output"]
|
)["output"]
|
||||||
|
|
||||||
def create_agent_executor(
|
def create_agent_executor(
|
||||||
self, tools: Optional[List[BaseTool]] = None, task=None
|
self, tools: Optional[List[Union[BaseTool, dict]]] = None, task=None
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Create an agent executor for the agent.
|
"""Create an agent executor for the agent.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
An instance of the CrewAgentExecutor class.
|
An instance of the CrewAgentExecutor class.
|
||||||
"""
|
"""
|
||||||
raw_tools: List[BaseTool] = tools or self.tools or []
|
raw_tools: List[Union[BaseTool, dict]] = tools or self.tools or []
|
||||||
parsed_tools = parse_tools(raw_tools)
|
parsed_tools = parse_tools(raw_tools)
|
||||||
|
|
||||||
prompt = Prompts(
|
prompt = Prompts(
|
||||||
@@ -803,7 +808,7 @@ class Agent(BaseAgent):
|
|||||||
goal=self.goal,
|
goal=self.goal,
|
||||||
backstory=self.backstory,
|
backstory=self.backstory,
|
||||||
llm=self.llm,
|
llm=self.llm,
|
||||||
tools=self.tools or [],
|
tools=[tool for tool in (self.tools or []) if isinstance(tool, BaseTool)],
|
||||||
max_iterations=self.max_iter,
|
max_iterations=self.max_iter,
|
||||||
max_execution_time=self.max_execution_time,
|
max_execution_time=self.max_execution_time,
|
||||||
respect_context_window=self.respect_context_window,
|
respect_context_window=self.respect_context_window,
|
||||||
@@ -841,7 +846,7 @@ class Agent(BaseAgent):
|
|||||||
goal=self.goal,
|
goal=self.goal,
|
||||||
backstory=self.backstory,
|
backstory=self.backstory,
|
||||||
llm=self.llm,
|
llm=self.llm,
|
||||||
tools=self.tools or [],
|
tools=[tool for tool in (self.tools or []) if isinstance(tool, BaseTool)],
|
||||||
max_iterations=self.max_iter,
|
max_iterations=self.max_iter,
|
||||||
max_execution_time=self.max_execution_time,
|
max_execution_time=self.max_execution_time,
|
||||||
respect_context_window=self.respect_context_window,
|
respect_context_window=self.respect_context_window,
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
from abc import ABC, abstractmethod
|
from abc import ABC, abstractmethod
|
||||||
from typing import Any, Dict, List, Optional
|
from typing import Any, Dict, List, Optional, Union
|
||||||
|
|
||||||
from pydantic import PrivateAttr
|
from pydantic import PrivateAttr
|
||||||
|
|
||||||
@@ -25,11 +25,11 @@ class BaseAgentAdapter(BaseAgent, ABC):
|
|||||||
self._agent_config = agent_config
|
self._agent_config = agent_config
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def configure_tools(self, tools: Optional[List[BaseTool]] = None) -> None:
|
def configure_tools(self, tools: Optional[List[Union[BaseTool, dict]]] = None) -> None:
|
||||||
"""Configure and adapt tools for the specific agent implementation.
|
"""Configure and adapt tools for the specific agent implementation.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
tools: Optional list of BaseTool instances to be configured
|
tools: Optional list of BaseTool instances and raw tool definitions to be configured
|
||||||
"""
|
"""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
from typing import Any, AsyncIterable, Dict, List, Optional
|
from typing import Any, Dict, List, Optional, Union
|
||||||
|
|
||||||
from pydantic import Field, PrivateAttr
|
from pydantic import Field, PrivateAttr
|
||||||
|
|
||||||
@@ -22,7 +22,6 @@ from crewai.utilities.events.agent_events import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from langchain_core.messages import ToolMessage
|
|
||||||
from langgraph.checkpoint.memory import MemorySaver
|
from langgraph.checkpoint.memory import MemorySaver
|
||||||
from langgraph.prebuilt import create_react_agent
|
from langgraph.prebuilt import create_react_agent
|
||||||
|
|
||||||
@@ -128,7 +127,8 @@ class LangGraphAgentAdapter(BaseAgentAdapter):
|
|||||||
tools: Optional[List[BaseTool]] = None,
|
tools: Optional[List[BaseTool]] = None,
|
||||||
) -> str:
|
) -> str:
|
||||||
"""Execute a task using the LangGraph workflow."""
|
"""Execute a task using the LangGraph workflow."""
|
||||||
self.create_agent_executor(tools)
|
mixed_tools: Optional[List[Union[BaseTool, dict]]] = tools # type: ignore[assignment]
|
||||||
|
self.create_agent_executor(mixed_tools)
|
||||||
|
|
||||||
self.configure_structured_output(task)
|
self.configure_structured_output(task)
|
||||||
|
|
||||||
@@ -198,17 +198,20 @@ class LangGraphAgentAdapter(BaseAgentAdapter):
|
|||||||
)
|
)
|
||||||
raise
|
raise
|
||||||
|
|
||||||
def create_agent_executor(self, tools: Optional[List[BaseTool]] = None) -> None:
|
def create_agent_executor(self, tools: Optional[List[Union[BaseTool, dict]]] = None) -> None:
|
||||||
"""Configure the LangGraph agent for execution."""
|
"""Configure the LangGraph agent for execution."""
|
||||||
self.configure_tools(tools)
|
self.configure_tools(tools)
|
||||||
|
|
||||||
def configure_tools(self, tools: Optional[List[BaseTool]] = None) -> None:
|
def configure_tools(self, tools: Optional[List[Union[BaseTool, dict]]] = None) -> None:
|
||||||
"""Configure tools for the LangGraph agent."""
|
"""Configure tools for the LangGraph agent."""
|
||||||
if tools:
|
if tools:
|
||||||
all_tools = list(self.tools or []) + list(tools or [])
|
base_tools = [tool for tool in tools if isinstance(tool, BaseTool)]
|
||||||
self._tool_adapter.configure_tools(all_tools)
|
existing_base_tools = [tool for tool in (self.tools or []) if isinstance(tool, BaseTool)]
|
||||||
available_tools = self._tool_adapter.tools()
|
all_tools = existing_base_tools + base_tools
|
||||||
self._graph.tools = available_tools
|
if all_tools:
|
||||||
|
self._tool_adapter.configure_tools(all_tools)
|
||||||
|
available_tools = self._tool_adapter.tools()
|
||||||
|
self._graph.tools = available_tools
|
||||||
|
|
||||||
def get_delegation_tools(self, agents: List[BaseAgent]) -> List[BaseTool]:
|
def get_delegation_tools(self, agents: List[BaseAgent]) -> List[BaseTool]:
|
||||||
"""Implement delegation tools support for LangGraph."""
|
"""Implement delegation tools support for LangGraph."""
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
from typing import Any, List, Optional
|
from typing import Any, List, Optional, Union
|
||||||
|
|
||||||
from pydantic import Field, PrivateAttr
|
from pydantic import Field, PrivateAttr
|
||||||
|
|
||||||
@@ -152,10 +152,12 @@ class OpenAIAgentAdapter(BaseAgentAdapter):
|
|||||||
|
|
||||||
self.agent_executor = Runner
|
self.agent_executor = Runner
|
||||||
|
|
||||||
def configure_tools(self, tools: Optional[List[BaseTool]] = None) -> None:
|
def configure_tools(self, tools: Optional[List[Union[BaseTool, dict]]] = None) -> None:
|
||||||
"""Configure tools for the OpenAI Assistant"""
|
"""Configure tools for the OpenAI Assistant"""
|
||||||
if tools:
|
if tools:
|
||||||
self._tool_adapter.configure_tools(tools)
|
base_tools = [tool for tool in tools if isinstance(tool, BaseTool)]
|
||||||
|
if base_tools:
|
||||||
|
self._tool_adapter.configure_tools(base_tools)
|
||||||
if self._tool_adapter.converted_tools:
|
if self._tool_adapter.converted_tools:
|
||||||
self._openai_agent.tools = self._tool_adapter.converted_tools
|
self._openai_agent.tools = self._tool_adapter.converted_tools
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import uuid
|
|||||||
from abc import ABC, abstractmethod
|
from abc import ABC, abstractmethod
|
||||||
from copy import copy as shallow_copy
|
from copy import copy as shallow_copy
|
||||||
from hashlib import md5
|
from hashlib import md5
|
||||||
from typing import Any, Callable, Dict, List, Optional, TypeVar
|
from typing import Any, Callable, Dict, List, Optional, TypeVar, Union
|
||||||
|
|
||||||
from pydantic import (
|
from pydantic import (
|
||||||
UUID4,
|
UUID4,
|
||||||
@@ -25,7 +25,6 @@ from crewai.security.security_config import SecurityConfig
|
|||||||
from crewai.tools.base_tool import BaseTool, Tool
|
from crewai.tools.base_tool import BaseTool, Tool
|
||||||
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
|
||||||
from crewai.utilities.converter import Converter
|
|
||||||
from crewai.utilities.string_utils import interpolate_only
|
from crewai.utilities.string_utils import interpolate_only
|
||||||
|
|
||||||
T = TypeVar("T", bound="BaseAgent")
|
T = TypeVar("T", bound="BaseAgent")
|
||||||
@@ -108,7 +107,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[BaseTool]] = Field(
|
tools: Optional[List[Union[BaseTool, dict]]] = Field(
|
||||||
default_factory=list, description="Tools at agents' disposal"
|
default_factory=list, description="Tools at agents' disposal"
|
||||||
)
|
)
|
||||||
max_iter: int = Field(
|
max_iter: int = Field(
|
||||||
@@ -168,29 +167,34 @@ class BaseAgent(ABC, BaseModel):
|
|||||||
|
|
||||||
@field_validator("tools")
|
@field_validator("tools")
|
||||||
@classmethod
|
@classmethod
|
||||||
def validate_tools(cls, tools: List[Any]) -> List[BaseTool]:
|
def validate_tools(cls, tools: List[Any]) -> List[Union[BaseTool, dict]]:
|
||||||
"""Validate and process the tools provided to the agent.
|
"""Validate and process the tools provided to the agent.
|
||||||
|
|
||||||
This method ensures that each tool is either an instance of BaseTool
|
This method ensures that each tool is either an instance of BaseTool,
|
||||||
or an object with 'name', 'func', and 'description' attributes. If the
|
an object with 'name', 'func', and 'description' attributes, or a
|
||||||
tool meets these criteria, it is processed and added to the list of
|
raw tool definition (dict) for hosted/server-side tools.
|
||||||
tools. Otherwise, a ValueError is raised.
|
|
||||||
"""
|
"""
|
||||||
if not tools:
|
if not tools:
|
||||||
return []
|
return []
|
||||||
|
|
||||||
processed_tools = []
|
processed_tools: List[Union[BaseTool, dict]] = []
|
||||||
required_attrs = ["name", "func", "description"]
|
required_attrs = ["name", "func", "description"]
|
||||||
for tool in tools:
|
for tool in tools:
|
||||||
if isinstance(tool, BaseTool):
|
if isinstance(tool, BaseTool):
|
||||||
processed_tools.append(tool)
|
processed_tools.append(tool)
|
||||||
|
elif isinstance(tool, dict):
|
||||||
|
if "name" not in tool:
|
||||||
|
raise ValueError(
|
||||||
|
f"Raw tool definition must have a 'name' field: {tool}"
|
||||||
|
)
|
||||||
|
processed_tools.append(tool)
|
||||||
elif all(hasattr(tool, attr) for attr in required_attrs):
|
elif all(hasattr(tool, attr) for attr in required_attrs):
|
||||||
# Tool has the required attributes, create a Tool instance
|
# Tool has the required attributes, create a Tool instance
|
||||||
processed_tools.append(Tool.from_langchain(tool))
|
processed_tools.append(Tool.from_langchain(tool))
|
||||||
else:
|
else:
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
f"Invalid tool type: {type(tool)}. "
|
f"Invalid tool type: {type(tool)}. "
|
||||||
"Tool must be an instance of BaseTool or "
|
"Tool must be an instance of BaseTool, a dict for hosted tools, or "
|
||||||
"an object with 'name', 'func', and 'description' attributes."
|
"an object with 'name', 'func', and 'description' attributes."
|
||||||
)
|
)
|
||||||
return processed_tools
|
return processed_tools
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ class CrewAgentExecutor(CrewAgentExecutorMixin):
|
|||||||
agent: BaseAgent,
|
agent: BaseAgent,
|
||||||
prompt: dict[str, str],
|
prompt: dict[str, str],
|
||||||
max_iter: int,
|
max_iter: int,
|
||||||
tools: List[CrewStructuredTool],
|
tools: List[Union[CrewStructuredTool, dict]],
|
||||||
tools_names: str,
|
tools_names: str,
|
||||||
stop_words: List[str],
|
stop_words: List[str],
|
||||||
tools_description: str,
|
tools_description: str,
|
||||||
@@ -84,8 +84,8 @@ class CrewAgentExecutor(CrewAgentExecutorMixin):
|
|||||||
self.messages: List[Dict[str, str]] = []
|
self.messages: List[Dict[str, str]] = []
|
||||||
self.iterations = 0
|
self.iterations = 0
|
||||||
self.log_error_after = 3
|
self.log_error_after = 3
|
||||||
self.tool_name_to_tool_map: Dict[str, Union[CrewStructuredTool, BaseTool]] = {
|
self.tool_name_to_tool_map: Dict[str, Union[CrewStructuredTool, BaseTool, dict]] = {
|
||||||
tool.name: tool for tool in self.tools
|
tool.name if hasattr(tool, 'name') else tool.get('name', 'unknown'): tool for tool in self.tools
|
||||||
}
|
}
|
||||||
existing_stop = self.llm.stop or []
|
existing_stop = self.llm.stop or []
|
||||||
self.llm.stop = list(
|
self.llm.stop = list(
|
||||||
|
|||||||
@@ -43,7 +43,9 @@ class ToolSelectionEvaluator(BaseEvaluator):
|
|||||||
available_tools_info = ""
|
available_tools_info = ""
|
||||||
if agent.tools:
|
if agent.tools:
|
||||||
for tool in agent.tools:
|
for tool in agent.tools:
|
||||||
available_tools_info += f"- {tool.name}: {tool.description}\n"
|
tool_name = tool.name if hasattr(tool, 'name') else tool.get('name', 'unknown')
|
||||||
|
tool_desc = tool.description if hasattr(tool, 'description') else tool.get('description', 'No description')
|
||||||
|
available_tools_info += f"- {tool_name}: {tool_desc}\n"
|
||||||
else:
|
else:
|
||||||
available_tools_info = "No tools available"
|
available_tools_info = "No tools available"
|
||||||
|
|
||||||
|
|||||||
@@ -564,9 +564,10 @@ class LiteAgent(FlowTrackable, BaseModel):
|
|||||||
|
|
||||||
if isinstance(formatted_answer, AgentAction):
|
if isinstance(formatted_answer, AgentAction):
|
||||||
try:
|
try:
|
||||||
|
from typing import cast
|
||||||
tool_result = execute_tool_and_check_finality(
|
tool_result = execute_tool_and_check_finality(
|
||||||
agent_action=formatted_answer,
|
agent_action=formatted_answer,
|
||||||
tools=self._parsed_tools,
|
tools=cast(List[Union[CrewStructuredTool, dict]], self._parsed_tools),
|
||||||
i18n=self.i18n,
|
i18n=self.i18n,
|
||||||
agent_key=self.key,
|
agent_key=self.key,
|
||||||
agent_role=self.role,
|
agent_role=self.role,
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ from crewai.agents.parser import (
|
|||||||
)
|
)
|
||||||
from crewai.llm import LLM
|
from crewai.llm import LLM
|
||||||
from crewai.llms.base_llm import BaseLLM
|
from crewai.llms.base_llm import BaseLLM
|
||||||
from crewai.tools import BaseTool as CrewAITool
|
|
||||||
from crewai.tools.base_tool import BaseTool
|
from crewai.tools.base_tool import BaseTool
|
||||||
from crewai.tools.structured_tool import CrewStructuredTool
|
from crewai.tools.structured_tool import CrewStructuredTool
|
||||||
from crewai.tools.tool_types import ToolResult
|
from crewai.tools.tool_types import ToolResult
|
||||||
@@ -25,26 +24,28 @@ from crewai.cli.config import Settings
|
|||||||
|
|
||||||
console = Console()
|
console = Console()
|
||||||
|
|
||||||
def parse_tools(tools: List[BaseTool]) -> List[CrewStructuredTool]:
|
def parse_tools(tools: List[Union[BaseTool, dict]]) -> List[Union[CrewStructuredTool, dict]]:
|
||||||
"""Parse tools to be used for the task."""
|
"""Parse tools to be used for the task."""
|
||||||
tools_list = []
|
tools_list: List[Union[CrewStructuredTool, dict]] = []
|
||||||
|
|
||||||
for tool in tools:
|
for tool in tools:
|
||||||
if isinstance(tool, CrewAITool):
|
if isinstance(tool, dict):
|
||||||
|
tools_list.append(tool)
|
||||||
|
elif isinstance(tool, BaseTool):
|
||||||
tools_list.append(tool.to_structured_tool())
|
tools_list.append(tool.to_structured_tool())
|
||||||
else:
|
else:
|
||||||
raise ValueError("Tool is not a CrewStructuredTool or BaseTool")
|
raise ValueError(f"Tool is not a CrewStructuredTool, BaseTool, or raw tool definition (dict): {type(tool)}")
|
||||||
|
|
||||||
return tools_list
|
return tools_list
|
||||||
|
|
||||||
|
|
||||||
def get_tool_names(tools: Sequence[Union[CrewStructuredTool, BaseTool]]) -> str:
|
def get_tool_names(tools: Sequence[Union[CrewStructuredTool, BaseTool, dict]]) -> str:
|
||||||
"""Get the names of the tools."""
|
"""Get the names of the tools."""
|
||||||
return ", ".join([t.name for t in tools])
|
return ", ".join([t.name if hasattr(t, 'name') else t.get('name', 'unknown') for t in tools])
|
||||||
|
|
||||||
|
|
||||||
def render_text_description_and_args(
|
def render_text_description_and_args(
|
||||||
tools: Sequence[Union[CrewStructuredTool, BaseTool]],
|
tools: Sequence[Union[CrewStructuredTool, BaseTool, dict]],
|
||||||
) -> str:
|
) -> str:
|
||||||
"""Render the tool name, description, and args in plain text.
|
"""Render the tool name, description, and args in plain text.
|
||||||
|
|
||||||
@@ -54,7 +55,12 @@ def render_text_description_and_args(
|
|||||||
"""
|
"""
|
||||||
tool_strings = []
|
tool_strings = []
|
||||||
for tool in tools:
|
for tool in tools:
|
||||||
tool_strings.append(tool.description)
|
if hasattr(tool, 'description'):
|
||||||
|
tool_strings.append(tool.description)
|
||||||
|
elif isinstance(tool, dict) and 'description' in tool:
|
||||||
|
tool_strings.append(f"Tool name: {tool.get('name', 'unknown')}\nTool description:\n{tool['description']}")
|
||||||
|
else:
|
||||||
|
tool_strings.append(f"Tool name: {tool.get('name', 'unknown') if isinstance(tool, dict) else 'unknown'}")
|
||||||
|
|
||||||
return "\n".join(tool_strings)
|
return "\n".join(tool_strings)
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
from typing import Any, Dict, List, Optional
|
from typing import Any, Dict, List, Optional, Union
|
||||||
|
|
||||||
from crewai.agents.parser import AgentAction
|
from crewai.agents.parser import AgentAction
|
||||||
from crewai.security import Fingerprint
|
from crewai.security import Fingerprint
|
||||||
@@ -10,7 +10,7 @@ from crewai.utilities.i18n import I18N
|
|||||||
|
|
||||||
def execute_tool_and_check_finality(
|
def execute_tool_and_check_finality(
|
||||||
agent_action: AgentAction,
|
agent_action: AgentAction,
|
||||||
tools: List[CrewStructuredTool],
|
tools: List[Union[CrewStructuredTool, dict]],
|
||||||
i18n: I18N,
|
i18n: I18N,
|
||||||
agent_key: Optional[str] = None,
|
agent_key: Optional[str] = None,
|
||||||
agent_role: Optional[str] = None,
|
agent_role: Optional[str] = None,
|
||||||
@@ -37,7 +37,8 @@ def execute_tool_and_check_finality(
|
|||||||
ToolResult containing the execution result and whether it should be treated as a final answer
|
ToolResult containing the execution result and whether it should be treated as a final answer
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
tool_name_to_tool_map = {tool.name: tool for tool in tools}
|
executable_tools = [tool for tool in tools if hasattr(tool, 'name') and hasattr(tool, 'result_as_answer')]
|
||||||
|
tool_name_to_tool_map = {tool.name: tool for tool in executable_tools}
|
||||||
|
|
||||||
if agent_key and agent_role and agent:
|
if agent_key and agent_role and agent:
|
||||||
fingerprint_context = fingerprint_context or {}
|
fingerprint_context = fingerprint_context or {}
|
||||||
@@ -52,10 +53,12 @@ def execute_tool_and_check_finality(
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise ValueError(f"Failed to set fingerprint: {e}")
|
raise ValueError(f"Failed to set fingerprint: {e}")
|
||||||
|
|
||||||
# Create tool usage instance
|
# Create tool usage instance - filter to only CrewStructuredTool instances for ToolUsage
|
||||||
|
from typing import cast
|
||||||
|
crew_structured_tools = [tool for tool in tools if hasattr(tool, 'name') and hasattr(tool, 'result_as_answer') and not isinstance(tool, dict)]
|
||||||
tool_usage = ToolUsage(
|
tool_usage = ToolUsage(
|
||||||
tools_handler=tools_handler,
|
tools_handler=tools_handler,
|
||||||
tools=tools,
|
tools=cast(List[CrewStructuredTool], crew_structured_tools),
|
||||||
function_calling_llm=function_calling_llm,
|
function_calling_llm=function_calling_llm,
|
||||||
task=task,
|
task=task,
|
||||||
agent=agent,
|
agent=agent,
|
||||||
@@ -82,7 +85,7 @@ def execute_tool_and_check_finality(
|
|||||||
# Handle invalid tool name
|
# Handle invalid tool name
|
||||||
tool_result = i18n.errors("wrong_tool_name").format(
|
tool_result = i18n.errors("wrong_tool_name").format(
|
||||||
tool=tool_calling.tool_name,
|
tool=tool_calling.tool_name,
|
||||||
tools=", ".join([tool.name.casefold() for tool in tools]),
|
tools=", ".join([tool.name.casefold() for tool in executable_tools]),
|
||||||
)
|
)
|
||||||
return ToolResult(tool_result, False)
|
return ToolResult(tool_result, False)
|
||||||
|
|
||||||
|
|||||||
237
tests/test_hosted_tools.py
Normal file
237
tests/test_hosted_tools.py
Normal file
@@ -0,0 +1,237 @@
|
|||||||
|
import pytest
|
||||||
|
from crewai import Agent
|
||||||
|
from crewai.tools import BaseTool
|
||||||
|
|
||||||
|
|
||||||
|
class MockTool(BaseTool):
|
||||||
|
name: str = "mock_tool"
|
||||||
|
description: str = "A mock tool for testing"
|
||||||
|
|
||||||
|
def _run(self, query: str) -> str:
|
||||||
|
return f"Mock result for: {query}"
|
||||||
|
|
||||||
|
|
||||||
|
def test_agent_with_crewai_tools_only():
|
||||||
|
"""Test backward compatibility with CrewAI tools only."""
|
||||||
|
mock_tool = MockTool()
|
||||||
|
agent = Agent(
|
||||||
|
role="Test Agent",
|
||||||
|
goal="Test goal",
|
||||||
|
backstory="Test backstory",
|
||||||
|
tools=[mock_tool]
|
||||||
|
)
|
||||||
|
|
||||||
|
assert len(agent.tools) == 1
|
||||||
|
assert isinstance(agent.tools[0], BaseTool)
|
||||||
|
|
||||||
|
|
||||||
|
def test_agent_with_raw_tools_only():
|
||||||
|
"""Test agent with raw tool definitions only."""
|
||||||
|
raw_tool = {
|
||||||
|
"name": "hosted_search",
|
||||||
|
"description": "Search the web using hosted search",
|
||||||
|
"parameters": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"query": {"type": "string", "description": "Search query"}
|
||||||
|
},
|
||||||
|
"required": ["query"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
agent = Agent(
|
||||||
|
role="Test Agent",
|
||||||
|
goal="Test goal",
|
||||||
|
backstory="Test backstory",
|
||||||
|
tools=[raw_tool]
|
||||||
|
)
|
||||||
|
|
||||||
|
assert len(agent.tools) == 1
|
||||||
|
assert isinstance(agent.tools[0], dict)
|
||||||
|
assert agent.tools[0]["name"] == "hosted_search"
|
||||||
|
|
||||||
|
|
||||||
|
def test_agent_with_mixed_tools():
|
||||||
|
"""Test agent with both CrewAI tools and raw tool definitions."""
|
||||||
|
mock_tool = MockTool()
|
||||||
|
raw_tool = {
|
||||||
|
"name": "hosted_calculator",
|
||||||
|
"description": "Hosted calculator tool",
|
||||||
|
"parameters": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"expression": {"type": "string"}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
agent = Agent(
|
||||||
|
role="Test Agent",
|
||||||
|
goal="Test goal",
|
||||||
|
backstory="Test backstory",
|
||||||
|
tools=[mock_tool, raw_tool]
|
||||||
|
)
|
||||||
|
|
||||||
|
assert len(agent.tools) == 2
|
||||||
|
assert isinstance(agent.tools[0], BaseTool)
|
||||||
|
assert isinstance(agent.tools[1], dict)
|
||||||
|
|
||||||
|
|
||||||
|
def test_invalid_raw_tool_definition():
|
||||||
|
"""Test error handling for invalid raw tool definitions."""
|
||||||
|
invalid_tool = {"description": "Missing name field"}
|
||||||
|
|
||||||
|
with pytest.raises(ValueError, match="Raw tool definition must have a 'name' field"):
|
||||||
|
Agent(
|
||||||
|
role="Test Agent",
|
||||||
|
goal="Test goal",
|
||||||
|
backstory="Test backstory",
|
||||||
|
tools=[invalid_tool]
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_parse_tools_with_mixed_types():
|
||||||
|
"""Test parse_tools function with mixed tool types."""
|
||||||
|
from crewai.utilities.agent_utils import parse_tools
|
||||||
|
|
||||||
|
mock_tool = MockTool()
|
||||||
|
raw_tool = {
|
||||||
|
"name": "hosted_tool",
|
||||||
|
"description": "A hosted tool",
|
||||||
|
"parameters": {"type": "object"}
|
||||||
|
}
|
||||||
|
|
||||||
|
parsed = parse_tools([mock_tool, raw_tool])
|
||||||
|
|
||||||
|
assert len(parsed) == 2
|
||||||
|
assert hasattr(parsed[0], 'name')
|
||||||
|
assert parsed[0].name == "mock_tool"
|
||||||
|
assert isinstance(parsed[1], dict)
|
||||||
|
assert parsed[1]["name"] == "hosted_tool"
|
||||||
|
|
||||||
|
|
||||||
|
def test_get_tool_names_with_mixed_types():
|
||||||
|
"""Test get_tool_names function with mixed tool types."""
|
||||||
|
from crewai.utilities.agent_utils import get_tool_names
|
||||||
|
|
||||||
|
mock_tool = MockTool()
|
||||||
|
raw_tool = {"name": "hosted_tool", "description": "A hosted tool"}
|
||||||
|
|
||||||
|
names = get_tool_names([mock_tool, raw_tool])
|
||||||
|
assert "mock_tool" in names
|
||||||
|
assert "hosted_tool" in names
|
||||||
|
|
||||||
|
|
||||||
|
def test_render_text_description_with_mixed_types():
|
||||||
|
"""Test render_text_description_and_args function with mixed tool types."""
|
||||||
|
from crewai.utilities.agent_utils import render_text_description_and_args
|
||||||
|
|
||||||
|
mock_tool = MockTool()
|
||||||
|
raw_tool = {"name": "hosted_tool", "description": "A hosted tool"}
|
||||||
|
|
||||||
|
description = render_text_description_and_args([mock_tool, raw_tool])
|
||||||
|
assert "A mock tool for testing" in description
|
||||||
|
assert "A hosted tool" in description
|
||||||
|
|
||||||
|
|
||||||
|
def test_agent_executor_with_mixed_tools():
|
||||||
|
"""Test CrewAgentExecutor initialization with mixed tool types."""
|
||||||
|
mock_tool = MockTool()
|
||||||
|
raw_tool = {"name": "hosted_tool", "description": "A hosted tool"}
|
||||||
|
|
||||||
|
agent = Agent(
|
||||||
|
role="Test Agent",
|
||||||
|
goal="Test goal",
|
||||||
|
backstory="Test backstory",
|
||||||
|
tools=[mock_tool, raw_tool]
|
||||||
|
)
|
||||||
|
|
||||||
|
agent.create_agent_executor()
|
||||||
|
|
||||||
|
assert len(agent.agent_executor.tool_name_to_tool_map) == 2
|
||||||
|
assert "mock_tool" in agent.agent_executor.tool_name_to_tool_map
|
||||||
|
assert "hosted_tool" in agent.agent_executor.tool_name_to_tool_map
|
||||||
|
|
||||||
|
|
||||||
|
def test_empty_tools_list():
|
||||||
|
"""Test agent with empty tools list."""
|
||||||
|
agent = Agent(
|
||||||
|
role="Test Agent",
|
||||||
|
goal="Test goal",
|
||||||
|
backstory="Test backstory",
|
||||||
|
tools=[]
|
||||||
|
)
|
||||||
|
|
||||||
|
assert len(agent.tools) == 0
|
||||||
|
|
||||||
|
|
||||||
|
def test_none_tools():
|
||||||
|
"""Test agent with None tools."""
|
||||||
|
agent = Agent(
|
||||||
|
role="Test Agent",
|
||||||
|
goal="Test goal",
|
||||||
|
backstory="Test backstory",
|
||||||
|
tools=None
|
||||||
|
)
|
||||||
|
|
||||||
|
assert agent.tools == []
|
||||||
|
|
||||||
|
|
||||||
|
def test_raw_tool_without_description():
|
||||||
|
"""Test raw tool definition without description field."""
|
||||||
|
raw_tool = {"name": "minimal_tool"}
|
||||||
|
|
||||||
|
agent = Agent(
|
||||||
|
role="Test Agent",
|
||||||
|
goal="Test goal",
|
||||||
|
backstory="Test backstory",
|
||||||
|
tools=[raw_tool]
|
||||||
|
)
|
||||||
|
|
||||||
|
assert len(agent.tools) == 1
|
||||||
|
assert isinstance(agent.tools[0], dict)
|
||||||
|
assert agent.tools[0]["name"] == "minimal_tool"
|
||||||
|
|
||||||
|
|
||||||
|
def test_complex_raw_tool_definition():
|
||||||
|
"""Test complex raw tool definition with nested parameters."""
|
||||||
|
raw_tool = {
|
||||||
|
"name": "complex_search",
|
||||||
|
"description": "Advanced search with multiple parameters",
|
||||||
|
"parameters": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"query": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Search query"
|
||||||
|
},
|
||||||
|
"filters": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"date_range": {"type": "string"},
|
||||||
|
"category": {"type": "string"}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"limit": {
|
||||||
|
"type": "integer",
|
||||||
|
"minimum": 1,
|
||||||
|
"maximum": 100,
|
||||||
|
"default": 10
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": ["query"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
agent = Agent(
|
||||||
|
role="Test Agent",
|
||||||
|
goal="Test goal",
|
||||||
|
backstory="Test backstory",
|
||||||
|
tools=[raw_tool]
|
||||||
|
)
|
||||||
|
|
||||||
|
assert len(agent.tools) == 1
|
||||||
|
assert isinstance(agent.tools[0], dict)
|
||||||
|
assert agent.tools[0]["name"] == "complex_search"
|
||||||
|
assert "parameters" in agent.tools[0]
|
||||||
|
assert agent.tools[0]["parameters"]["type"] == "object"
|
||||||
Reference in New Issue
Block a user