mirror of
https://github.com/crewAIInc/crewAI.git
synced 2026-01-10 00:28:31 +00:00
Implement comprehensive A2A protocol improvements
- Add tool deduplication logic in agent_tools.py - Re-implement recursion depth limits and validation in base_agent_tools.py - Add proper type hinting for _agent_tools attributes - Add consistent tool retrieval method across delegation tools - Enhance logging for debugging recursive agent invocations Addresses all remaining PR review comments for full A2A protocol support. Co-Authored-By: Joe Moura <joao@crewai.com>
This commit is contained in:
@@ -1,3 +1,5 @@
|
|||||||
|
from typing import List, Set
|
||||||
|
|
||||||
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.tools.base_tool import BaseTool
|
||||||
from crewai.utilities import I18N
|
from crewai.utilities import I18N
|
||||||
@@ -22,21 +24,33 @@ class AgentTools:
|
|||||||
i18n=self.i18n,
|
i18n=self.i18n,
|
||||||
description=self.i18n.tools("delegate_work").format(coworkers=coworkers), # type: ignore
|
description=self.i18n.tools("delegate_work").format(coworkers=coworkers), # type: ignore
|
||||||
)
|
)
|
||||||
delegate_tool._agent_tools = self._get_all_agent_tools()
|
delegate_tool._agent_tools: List[BaseTool] = self._get_all_agent_tools()
|
||||||
|
|
||||||
ask_tool = AskQuestionTool(
|
ask_tool = AskQuestionTool(
|
||||||
agents=self.agents,
|
agents=self.agents,
|
||||||
i18n=self.i18n,
|
i18n=self.i18n,
|
||||||
description=self.i18n.tools("ask_question").format(coworkers=coworkers), # type: ignore
|
description=self.i18n.tools("ask_question").format(coworkers=coworkers), # type: ignore
|
||||||
)
|
)
|
||||||
ask_tool._agent_tools = self._get_all_agent_tools()
|
ask_tool._agent_tools: List[BaseTool] = self._get_all_agent_tools()
|
||||||
|
|
||||||
return [delegate_tool, ask_tool]
|
return [delegate_tool, ask_tool]
|
||||||
|
|
||||||
def _get_all_agent_tools(self) -> list[BaseTool]:
|
def _get_all_agent_tools(self) -> list[BaseTool]:
|
||||||
"""Get all tools from all agents for recursive invocation"""
|
"""
|
||||||
all_tools = []
|
Get all tools from all agents for recursive invocation.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
list[BaseTool]: A deduplicated list of all tools from all agents.
|
||||||
|
"""
|
||||||
|
seen_tools: Set[int] = set()
|
||||||
|
unique_tools: List[BaseTool] = []
|
||||||
|
|
||||||
for agent in self.agents:
|
for agent in self.agents:
|
||||||
if agent.tools:
|
if agent.tools:
|
||||||
all_tools.extend(agent.tools)
|
for tool in agent.tools:
|
||||||
return all_tools
|
tool_id = id(tool)
|
||||||
|
if tool_id not in seen_tools:
|
||||||
|
seen_tools.add(tool_id)
|
||||||
|
unique_tools.append(tool)
|
||||||
|
|
||||||
|
return unique_tools
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
from typing import Optional
|
from typing import List, Optional
|
||||||
|
|
||||||
from pydantic import BaseModel, Field
|
from pydantic import BaseModel, Field
|
||||||
|
|
||||||
from crewai.tools.agent_tools.base_agent_tools import BaseAgentTool
|
from crewai.tools.agent_tools.base_agent_tools import BaseAgentTool
|
||||||
|
from crewai.tools.base_tool import BaseTool
|
||||||
|
|
||||||
|
|
||||||
class AskQuestionToolSchema(BaseModel):
|
class AskQuestionToolSchema(BaseModel):
|
||||||
@@ -16,6 +17,7 @@ class AskQuestionTool(BaseAgentTool):
|
|||||||
|
|
||||||
name: str = "Ask question to coworker"
|
name: str = "Ask question to coworker"
|
||||||
args_schema: type[BaseModel] = AskQuestionToolSchema
|
args_schema: type[BaseModel] = AskQuestionToolSchema
|
||||||
|
_agent_tools: Optional[List[BaseTool]] = None
|
||||||
|
|
||||||
def _run(
|
def _run(
|
||||||
self,
|
self,
|
||||||
@@ -25,5 +27,5 @@ class AskQuestionTool(BaseAgentTool):
|
|||||||
**kwargs,
|
**kwargs,
|
||||||
) -> str:
|
) -> str:
|
||||||
coworker = self._get_coworker(coworker, **kwargs)
|
coworker = self._get_coworker(coworker, **kwargs)
|
||||||
tools = getattr(self, '_agent_tools', None) or kwargs.get('tools')
|
tools = self._get_tools(**kwargs)
|
||||||
return self._execute(coworker, question, context, tools)
|
return self._execute(coworker, question, context, tools)
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ class BaseAgentTool(BaseTool):
|
|||||||
i18n: I18N = Field(
|
i18n: I18N = Field(
|
||||||
default_factory=I18N, description="Internationalization settings"
|
default_factory=I18N, description="Internationalization settings"
|
||||||
)
|
)
|
||||||
|
MAX_RECURSION_DEPTH: int = 5
|
||||||
|
|
||||||
def sanitize_agent_name(self, name: str) -> str:
|
def sanitize_agent_name(self, name: str) -> str:
|
||||||
"""
|
"""
|
||||||
@@ -46,12 +47,22 @@ class BaseAgentTool(BaseTool):
|
|||||||
coworker = coworker[1:-1].split(",")[0]
|
coworker = coworker[1:-1].split(",")[0]
|
||||||
return coworker
|
return coworker
|
||||||
|
|
||||||
|
def _get_tools(self, **kwargs) -> Optional[List[BaseTool]]:
|
||||||
|
"""
|
||||||
|
Get tools from instance or kwargs.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Optional[List[BaseTool]]: The tools to use for recursive invocation.
|
||||||
|
"""
|
||||||
|
return getattr(self, "_agent_tools", None) or kwargs.get("tools")
|
||||||
|
|
||||||
def _execute(
|
def _execute(
|
||||||
self,
|
self,
|
||||||
agent_name: Optional[str],
|
agent_name: Optional[str],
|
||||||
task: str,
|
task: str,
|
||||||
context: Optional[str] = None,
|
context: Optional[str] = None,
|
||||||
tools: Optional[List[Any]] = None
|
tools: Optional[List[BaseTool]] = None,
|
||||||
|
recursion_depth: int = 0,
|
||||||
) -> str:
|
) -> str:
|
||||||
"""
|
"""
|
||||||
Execute delegation to an agent with case-insensitive and whitespace-tolerant matching.
|
Execute delegation to an agent with case-insensitive and whitespace-tolerant matching.
|
||||||
@@ -61,11 +72,24 @@ class BaseAgentTool(BaseTool):
|
|||||||
task: The specific question or task to delegate
|
task: The specific question or task to delegate
|
||||||
context: Optional additional context for the task execution
|
context: Optional additional context for the task execution
|
||||||
tools: Optional tools to pass to the delegated agent for recursive invocation
|
tools: Optional tools to pass to the delegated agent for recursive invocation
|
||||||
|
recursion_depth: Current recursion depth to prevent infinite loops
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
str: The execution result from the delegated agent or an error message
|
str: The execution result from the delegated agent or an error message
|
||||||
if the agent cannot be found
|
if the agent cannot be found
|
||||||
"""
|
"""
|
||||||
|
if tools is not None and not all(isinstance(tool, BaseTool) for tool in tools):
|
||||||
|
return self.i18n.errors("agent_tool_execution_error").format(
|
||||||
|
agent_role="unknown",
|
||||||
|
error="Invalid tools provided: all tools must inherit from BaseTool",
|
||||||
|
)
|
||||||
|
|
||||||
|
if recursion_depth >= self.MAX_RECURSION_DEPTH:
|
||||||
|
return self.i18n.errors("agent_tool_execution_error").format(
|
||||||
|
agent_role="unknown",
|
||||||
|
error=f"Maximum recursion depth ({self.MAX_RECURSION_DEPTH}) exceeded",
|
||||||
|
)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if agent_name is None:
|
if agent_name is None:
|
||||||
agent_name = ""
|
agent_name = ""
|
||||||
@@ -110,6 +134,7 @@ class BaseAgentTool(BaseTool):
|
|||||||
|
|
||||||
agent = agent[0]
|
agent = agent[0]
|
||||||
try:
|
try:
|
||||||
|
logger.debug(f"Executing task with {len(tools) if tools else 0} tools at recursion depth {recursion_depth}")
|
||||||
task_with_assigned_agent = Task(
|
task_with_assigned_agent = Task(
|
||||||
description=task,
|
description=task,
|
||||||
agent=agent,
|
agent=agent,
|
||||||
@@ -118,7 +143,9 @@ class BaseAgentTool(BaseTool):
|
|||||||
tools=tools,
|
tools=tools,
|
||||||
)
|
)
|
||||||
logger.debug(f"Created task for agent '{self.sanitize_agent_name(agent.role)}': {task}")
|
logger.debug(f"Created task for agent '{self.sanitize_agent_name(agent.role)}': {task}")
|
||||||
return agent.execute_task(task_with_assigned_agent, context, tools)
|
return agent.execute_task(
|
||||||
|
task_with_assigned_agent, context, tools, recursion_depth + 1
|
||||||
|
)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
# Handle task creation or execution errors
|
# Handle task creation or execution errors
|
||||||
return self.i18n.errors("agent_tool_execution_error").format(
|
return self.i18n.errors("agent_tool_execution_error").format(
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
from typing import Optional
|
from typing import List, Optional
|
||||||
|
|
||||||
from pydantic import BaseModel, Field
|
from pydantic import BaseModel, Field
|
||||||
|
|
||||||
from crewai.tools.agent_tools.base_agent_tools import BaseAgentTool
|
from crewai.tools.agent_tools.base_agent_tools import BaseAgentTool
|
||||||
|
from crewai.tools.base_tool import BaseTool
|
||||||
|
|
||||||
|
|
||||||
class DelegateWorkToolSchema(BaseModel):
|
class DelegateWorkToolSchema(BaseModel):
|
||||||
@@ -18,6 +19,7 @@ class DelegateWorkTool(BaseAgentTool):
|
|||||||
|
|
||||||
name: str = "Delegate work to coworker"
|
name: str = "Delegate work to coworker"
|
||||||
args_schema: type[BaseModel] = DelegateWorkToolSchema
|
args_schema: type[BaseModel] = DelegateWorkToolSchema
|
||||||
|
_agent_tools: Optional[List[BaseTool]] = None
|
||||||
|
|
||||||
def _run(
|
def _run(
|
||||||
self,
|
self,
|
||||||
@@ -27,5 +29,5 @@ class DelegateWorkTool(BaseAgentTool):
|
|||||||
**kwargs,
|
**kwargs,
|
||||||
) -> str:
|
) -> str:
|
||||||
coworker = self._get_coworker(coworker, **kwargs)
|
coworker = self._get_coworker(coworker, **kwargs)
|
||||||
tools = getattr(self, '_agent_tools', None) or kwargs.get('tools')
|
tools = self._get_tools(**kwargs)
|
||||||
return self._execute(coworker, task, context, tools)
|
return self._execute(coworker, task, context, tools)
|
||||||
|
|||||||
Reference in New Issue
Block a user