mirror of
https://github.com/crewAIInc/crewAI.git
synced 2025-12-16 12:28:30 +00:00
Compare commits
4 Commits
devin/1765
...
devin/1763
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
57f386d748 | ||
|
|
922165d110 | ||
|
|
37b4252fa7 | ||
|
|
d5bc82be21 |
@@ -35,23 +35,35 @@ class CrewAgentExecutorMixin:
|
||||
self.crew
|
||||
and self.agent
|
||||
and self.task
|
||||
and "Action: Delegate work to coworker" not in output.text
|
||||
):
|
||||
try:
|
||||
if (
|
||||
hasattr(self.crew, "_short_term_memory")
|
||||
and self.crew._short_term_memory
|
||||
):
|
||||
self.crew._short_term_memory.save(
|
||||
value=output.text,
|
||||
metadata={
|
||||
"observation": self.task.description,
|
||||
},
|
||||
is_delegation = any(
|
||||
pattern in output.text
|
||||
for pattern in [
|
||||
"Action: Delegate work to coworker",
|
||||
"Action: delegate_work",
|
||||
"Action: delegate_work_to_coworker",
|
||||
"Action: Ask question to coworker",
|
||||
"Action: ask_question",
|
||||
"Action: ask_question_to_coworker",
|
||||
]
|
||||
)
|
||||
|
||||
if not is_delegation:
|
||||
try:
|
||||
if (
|
||||
hasattr(self.crew, "_short_term_memory")
|
||||
and self.crew._short_term_memory
|
||||
):
|
||||
self.crew._short_term_memory.save(
|
||||
value=output.text,
|
||||
metadata={
|
||||
"observation": self.task.description,
|
||||
},
|
||||
)
|
||||
except Exception as e:
|
||||
self.agent._logger.log(
|
||||
"error", f"Failed to add to short term memory: {e}"
|
||||
)
|
||||
except Exception as e:
|
||||
self.agent._logger.log(
|
||||
"error", f"Failed to add to short term memory: {e}"
|
||||
)
|
||||
|
||||
def _create_external_memory(self, output) -> None:
|
||||
"""Create and save a external-term memory item if conditions are met."""
|
||||
|
||||
@@ -240,10 +240,18 @@ class ToolUsage:
|
||||
|
||||
if result is None:
|
||||
try:
|
||||
if calling.tool_name in [
|
||||
normalized_tool_name = self._normalize_tool_name(calling.tool_name)
|
||||
is_delegation_tool = normalized_tool_name in [
|
||||
"delegate_work_to_coworker",
|
||||
"delegate_work",
|
||||
"ask_question_to_coworker",
|
||||
"ask_question",
|
||||
] or calling.tool_name in [
|
||||
"Delegate work to coworker",
|
||||
"Ask question to coworker",
|
||||
]:
|
||||
]
|
||||
|
||||
if is_delegation_tool:
|
||||
coworker = (
|
||||
calling.arguments.get("coworker") if calling.arguments else None
|
||||
)
|
||||
@@ -400,7 +408,78 @@ class ToolUsage:
|
||||
return f"Tool '{tool_name}' has reached its usage limit of {tool.max_usage_count} times and cannot be used anymore."
|
||||
return None
|
||||
|
||||
def _normalize_tool_name(self, name: str) -> str:
|
||||
"""Normalize tool name for language-agnostic matching.
|
||||
|
||||
Converts to lowercase, removes extra whitespace, and replaces
|
||||
spaces/hyphens with underscores for consistent matching.
|
||||
|
||||
Args:
|
||||
name: The tool name to normalize
|
||||
|
||||
Returns:
|
||||
Normalized tool name
|
||||
"""
|
||||
if not name:
|
||||
return ""
|
||||
normalized = name.lower().strip()
|
||||
normalized = normalized.replace(" ", "_").replace("-", "_")
|
||||
while "__" in normalized:
|
||||
normalized = normalized.replace("__", "_")
|
||||
return normalized
|
||||
|
||||
def _get_tool_aliases(self, tool: Any) -> list[str]:
|
||||
"""Get all possible aliases for a tool including stable identifiers.
|
||||
|
||||
Args:
|
||||
tool: The tool object
|
||||
|
||||
Returns:
|
||||
List of possible tool name aliases
|
||||
"""
|
||||
aliases = [tool.name] # Original name
|
||||
|
||||
normalized = self._normalize_tool_name(tool.name)
|
||||
if normalized and normalized != tool.name:
|
||||
aliases.append(normalized)
|
||||
|
||||
if tool.name == "Delegate work to coworker":
|
||||
aliases.extend(["delegate_work", "delegate_work_to_coworker"])
|
||||
elif tool.name == "Ask question to coworker":
|
||||
aliases.extend(["ask_question", "ask_question_to_coworker"])
|
||||
|
||||
return aliases
|
||||
|
||||
def _select_tool(self, tool_name: str) -> Any:
|
||||
"""Select a tool by name with language-agnostic matching support.
|
||||
|
||||
Supports matching against:
|
||||
1. Exact tool name (case-insensitive)
|
||||
2. Normalized/slugified tool name
|
||||
3. Stable short identifiers (delegate_work, ask_question)
|
||||
4. Fuzzy matching as fallback (0.85 threshold)
|
||||
|
||||
Args:
|
||||
tool_name: The name of the tool to select
|
||||
|
||||
Returns:
|
||||
The selected tool object
|
||||
|
||||
Raises:
|
||||
Exception: If no matching tool is found
|
||||
"""
|
||||
normalized_input = self._normalize_tool_name(tool_name)
|
||||
|
||||
for tool in self.tools:
|
||||
aliases = self._get_tool_aliases(tool)
|
||||
|
||||
if tool.name.lower().strip() == tool_name.lower().strip():
|
||||
return tool
|
||||
|
||||
for alias in aliases:
|
||||
if self._normalize_tool_name(alias) == normalized_input:
|
||||
return tool
|
||||
|
||||
order_tools = sorted(
|
||||
self.tools,
|
||||
key=lambda tool: SequenceMatcher(
|
||||
@@ -410,13 +489,13 @@ class ToolUsage:
|
||||
)
|
||||
for tool in order_tools:
|
||||
if (
|
||||
tool.name.lower().strip() == tool_name.lower().strip()
|
||||
or SequenceMatcher(
|
||||
SequenceMatcher(
|
||||
None, tool.name.lower().strip(), tool_name.lower().strip()
|
||||
).ratio()
|
||||
> 0.85
|
||||
):
|
||||
return tool
|
||||
|
||||
if self.task:
|
||||
self.task.increment_tools_errors()
|
||||
tool_selection_data: dict[str, Any] = {
|
||||
|
||||
@@ -742,3 +742,174 @@ def test_tool_usage_finished_event_with_cached_result():
|
||||
assert isinstance(event.started_at, datetime.datetime)
|
||||
assert isinstance(event.finished_at, datetime.datetime)
|
||||
assert event.type == "tool_usage_finished"
|
||||
|
||||
|
||||
def test_normalize_tool_name():
|
||||
"""Test tool name normalization for language-agnostic matching."""
|
||||
tool_usage = ToolUsage(
|
||||
tools_handler=MagicMock(),
|
||||
tools=[],
|
||||
task=MagicMock(),
|
||||
function_calling_llm=None,
|
||||
agent=MagicMock(),
|
||||
action=MagicMock(),
|
||||
)
|
||||
|
||||
assert tool_usage._normalize_tool_name("Delegate work to coworker") == "delegate_work_to_coworker"
|
||||
assert tool_usage._normalize_tool_name("Ask question to coworker") == "ask_question_to_coworker"
|
||||
assert tool_usage._normalize_tool_name("delegate_work") == "delegate_work"
|
||||
assert tool_usage._normalize_tool_name("DELEGATE WORK") == "delegate_work"
|
||||
assert tool_usage._normalize_tool_name("delegate-work") == "delegate_work"
|
||||
assert tool_usage._normalize_tool_name(" delegate work ") == "delegate_work"
|
||||
assert tool_usage._normalize_tool_name("") == ""
|
||||
|
||||
|
||||
def test_get_tool_aliases_for_delegate_work():
|
||||
"""Test that delegate work tool has correct aliases."""
|
||||
from crewai.tools.agent_tools.delegate_work_tool import DelegateWorkTool
|
||||
|
||||
delegate_tool = DelegateWorkTool(agents=[], description="Test delegate tool")
|
||||
|
||||
tool_usage = ToolUsage(
|
||||
tools_handler=MagicMock(),
|
||||
tools=[delegate_tool],
|
||||
task=MagicMock(),
|
||||
function_calling_llm=None,
|
||||
agent=MagicMock(),
|
||||
action=MagicMock(),
|
||||
)
|
||||
|
||||
aliases = tool_usage._get_tool_aliases(delegate_tool)
|
||||
|
||||
assert "Delegate work to coworker" in aliases
|
||||
assert "delegate_work" in aliases
|
||||
assert "delegate_work_to_coworker" in aliases
|
||||
|
||||
|
||||
def test_get_tool_aliases_for_ask_question():
|
||||
"""Test that ask question tool has correct aliases."""
|
||||
from crewai.tools.agent_tools.ask_question_tool import AskQuestionTool
|
||||
|
||||
ask_tool = AskQuestionTool(agents=[], description="Test ask question tool")
|
||||
|
||||
tool_usage = ToolUsage(
|
||||
tools_handler=MagicMock(),
|
||||
tools=[ask_tool],
|
||||
task=MagicMock(),
|
||||
function_calling_llm=None,
|
||||
agent=MagicMock(),
|
||||
action=MagicMock(),
|
||||
)
|
||||
|
||||
aliases = tool_usage._get_tool_aliases(ask_tool)
|
||||
|
||||
assert "Ask question to coworker" in aliases
|
||||
assert "ask_question" in aliases
|
||||
assert "ask_question_to_coworker" in aliases
|
||||
|
||||
|
||||
def test_select_tool_with_short_identifier():
|
||||
"""Test tool selection using short identifiers like delegate_work."""
|
||||
from crewai.tools.agent_tools.delegate_work_tool import DelegateWorkTool
|
||||
from crewai.tools.agent_tools.ask_question_tool import AskQuestionTool
|
||||
|
||||
delegate_tool = DelegateWorkTool(agents=[], description="Test delegate tool")
|
||||
ask_tool = AskQuestionTool(agents=[], description="Test ask question tool")
|
||||
|
||||
tool_usage = ToolUsage(
|
||||
tools_handler=MagicMock(),
|
||||
tools=[delegate_tool, ask_tool],
|
||||
task=MagicMock(),
|
||||
function_calling_llm=None,
|
||||
agent=MagicMock(),
|
||||
action=MagicMock(),
|
||||
)
|
||||
|
||||
# Test short identifiers
|
||||
selected = tool_usage._select_tool("delegate_work")
|
||||
assert selected.name == "Delegate work to coworker"
|
||||
|
||||
selected = tool_usage._select_tool("ask_question")
|
||||
assert selected.name == "Ask question to coworker"
|
||||
|
||||
# Test slugified versions
|
||||
selected = tool_usage._select_tool("delegate_work_to_coworker")
|
||||
assert selected.name == "Delegate work to coworker"
|
||||
|
||||
selected = tool_usage._select_tool("ask_question_to_coworker")
|
||||
assert selected.name == "Ask question to coworker"
|
||||
|
||||
|
||||
def test_select_tool_with_exact_name():
|
||||
"""Test tool selection with exact English name still works."""
|
||||
from crewai.tools.agent_tools.delegate_work_tool import DelegateWorkTool
|
||||
|
||||
delegate_tool = DelegateWorkTool(agents=[], description="Test delegate tool")
|
||||
|
||||
tool_usage = ToolUsage(
|
||||
tools_handler=MagicMock(),
|
||||
tools=[delegate_tool],
|
||||
task=MagicMock(),
|
||||
function_calling_llm=None,
|
||||
agent=MagicMock(),
|
||||
action=MagicMock(),
|
||||
)
|
||||
|
||||
# Test exact name matching (backward compatibility)
|
||||
selected = tool_usage._select_tool("Delegate work to coworker")
|
||||
assert selected.name == "Delegate work to coworker"
|
||||
|
||||
|
||||
def test_select_tool_case_insensitive():
|
||||
"""Test tool selection is case-insensitive."""
|
||||
from crewai.tools.agent_tools.delegate_work_tool import DelegateWorkTool
|
||||
|
||||
delegate_tool = DelegateWorkTool(agents=[], description="Test delegate tool")
|
||||
|
||||
tool_usage = ToolUsage(
|
||||
tools_handler=MagicMock(),
|
||||
tools=[delegate_tool],
|
||||
task=MagicMock(),
|
||||
function_calling_llm=None,
|
||||
agent=MagicMock(),
|
||||
action=MagicMock(),
|
||||
)
|
||||
|
||||
# Test case variations
|
||||
selected = tool_usage._select_tool("DELEGATE_WORK")
|
||||
assert selected.name == "Delegate work to coworker"
|
||||
|
||||
selected = tool_usage._select_tool("Delegate_Work")
|
||||
assert selected.name == "Delegate work to coworker"
|
||||
|
||||
|
||||
def test_memory_filter_with_short_identifiers():
|
||||
"""Test that memory filter recognizes short identifiers."""
|
||||
from crewai.agents.agent_builder.base_agent_executor_mixin import CrewAgentExecutorMixin
|
||||
|
||||
class TestMixin(CrewAgentExecutorMixin):
|
||||
def __init__(self):
|
||||
self.crew = MagicMock()
|
||||
self.crew._short_term_memory = MagicMock()
|
||||
self.agent = MagicMock()
|
||||
self.agent._logger = MagicMock()
|
||||
self.task = MagicMock()
|
||||
self.task.description = "test task"
|
||||
|
||||
mixin = TestMixin()
|
||||
|
||||
# Test with short identifier - should NOT save to memory
|
||||
output = MagicMock()
|
||||
output.text = "Action: delegate_work\nAction Input: {...}"
|
||||
mixin._create_short_term_memory(output)
|
||||
mixin.crew._short_term_memory.save.assert_not_called()
|
||||
|
||||
# Test with English name - should NOT save to memory
|
||||
output.text = "Action: Delegate work to coworker\nAction Input: {...}"
|
||||
mixin._create_short_term_memory(output)
|
||||
mixin.crew._short_term_memory.save.assert_not_called()
|
||||
|
||||
# Test with non-delegation action - should save to memory
|
||||
output.text = "Action: Some other tool\nAction Input: {...}"
|
||||
mixin._create_short_term_memory(output)
|
||||
mixin.crew._short_term_memory.save.assert_called_once()
|
||||
|
||||
Reference in New Issue
Block a user