From e40b29b5f5a0b469b730f55268c0a5ca01621c3c Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Thu, 2 Apr 2026 23:12:11 +0000 Subject: [PATCH] fix: use None sentinel instead of 'unknown' in _extract_tool_name MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Addresses Cursor Bugbot feedback: the string 'unknown' could collide with a tool legitimately named 'unknown'. Now _extract_tool_name returns None for unrecognised formats, and _should_parallelize checks 'is None' instead of '== "unknown"'. Co-Authored-By: João --- .../src/crewai/experimental/agent_executor.py | 19 ++++++++++++------- .../tests/agents/test_agent_executor.py | 8 ++++---- 2 files changed, 16 insertions(+), 11 deletions(-) diff --git a/lib/crewai/src/crewai/experimental/agent_executor.py b/lib/crewai/src/crewai/experimental/agent_executor.py index f091648af..1eb4472e6 100644 --- a/lib/crewai/src/crewai/experimental/agent_executor.py +++ b/lib/crewai/src/crewai/experimental/agent_executor.py @@ -1690,7 +1690,7 @@ class AgentExecutor(Flow[AgentExecutorState], CrewAgentExecutorMixin): for tool_call in tool_calls: func_name = self._extract_tool_name(tool_call) - if func_name == "unknown": + if func_name is None: continue original_tool = self._resolve_original_tool(func_name) @@ -1908,8 +1908,14 @@ class AgentExecutor(Flow[AgentExecutorState], CrewAgentExecutorMixin): "original_tool": original_tool, } - def _extract_tool_name(self, tool_call: Any) -> str: - """Extract tool name from various tool call formats.""" + def _extract_tool_name(self, tool_call: Any) -> str | None: + """Extract tool name from various tool call formats. + + Returns: + The sanitized tool name, or ``None`` when the format is + unrecognised (avoids collisions with a tool legitimately + named "unknown"). + """ if hasattr(tool_call, "function"): return sanitize_tool_name(tool_call.function.name) if hasattr(tool_call, "function_call") and tool_call.function_call: @@ -1918,10 +1924,9 @@ class AgentExecutor(Flow[AgentExecutorState], CrewAgentExecutorMixin): return sanitize_tool_name(tool_call.name) if isinstance(tool_call, dict): func_info = tool_call.get("function", {}) - return sanitize_tool_name( - func_info.get("name", "") or tool_call.get("name", "unknown") - ) - return "unknown" + name = func_info.get("name", "") or tool_call.get("name", "") + return sanitize_tool_name(name) if name else None + return None @router(execute_native_tool) def check_native_todo_completion( diff --git a/lib/crewai/tests/agents/test_agent_executor.py b/lib/crewai/tests/agents/test_agent_executor.py index 53aead6f9..ded34e563 100644 --- a/lib/crewai/tests/agents/test_agent_executor.py +++ b/lib/crewai/tests/agents/test_agent_executor.py @@ -2141,12 +2141,12 @@ class TestExtractToolName: assert executor._extract_tool_name(tc) == "bedrock_tool" - def test_unknown_format_returns_unknown(self, mock_dependencies): - """Completely unrecognized object returns 'unknown'.""" + def test_unknown_format_returns_none(self, mock_dependencies): + """Completely unrecognized object returns None (not 'unknown').""" executor = _build_executor(**mock_dependencies) - assert executor._extract_tool_name(42) == "unknown" - assert executor._extract_tool_name("not a tool call") == "unknown" + assert executor._extract_tool_name(42) is None + assert executor._extract_tool_name("not a tool call") is None def test_sanitizes_names(self, mock_dependencies): """Tool names with special characters are sanitized."""