From ff2ac6bd4f674a78fc274e30cb5f58d9a466f2d4 Mon Sep 17 00:00:00 2001 From: lorenzejay Date: Sun, 8 Mar 2026 14:50:34 -0700 Subject: [PATCH] dont tool search if there is only one tool --- .../llms/providers/anthropic/completion.py | 17 +++++----- .../tests/llms/anthropic/test_anthropic.py | 31 ++++++++++++++++--- 2 files changed, 35 insertions(+), 13 deletions(-) diff --git a/lib/crewai/src/crewai/llms/providers/anthropic/completion.py b/lib/crewai/src/crewai/llms/providers/anthropic/completion.py index f1f564fd3..9723c4a8f 100644 --- a/lib/crewai/src/crewai/llms/providers/anthropic/completion.py +++ b/lib/crewai/src/crewai/llms/providers/anthropic/completion.py @@ -471,19 +471,20 @@ class AnthropicCompletion(BaseLLM): if tools and self.supports_tools: converted_tools = self._convert_tools_for_interference(tools) - # When tool_search is enabled, inject the tool search tool and - # mark all regular tools with defer_loading=True - if self.tool_search is not None: - converted_tools = self._apply_tool_search(converted_tools) - - params["tools"] = converted_tools - - # Count only regular tools (not tool search tools) for tool_choice + # When tool_search is enabled and there are 2+ regular tools, + # inject the search tool and mark regular tools with defer_loading. + # With only 1 tool there's nothing to search — skip tool search + # entirely so the normal forced tool_choice optimisation still works. regular_tools = [ t for t in converted_tools if t.get("type", "") not in TOOL_SEARCH_TOOL_TYPES ] + if self.tool_search is not None and len(regular_tools) >= 2: + converted_tools = self._apply_tool_search(converted_tools) + + params["tools"] = converted_tools + if available_functions and len(regular_tools) == 1: tool_name = regular_tools[0].get("name") if tool_name and tool_name in available_functions: diff --git a/lib/crewai/tests/llms/anthropic/test_anthropic.py b/lib/crewai/tests/llms/anthropic/test_anthropic.py index 3d12a8c86..89418ca0e 100644 --- a/lib/crewai/tests/llms/anthropic/test_anthropic.py +++ b/lib/crewai/tests/llms/anthropic/test_anthropic.py @@ -1189,8 +1189,20 @@ def test_tool_search_regex_config(): { "type": "function", "function": { - "name": "test_tool", - "description": "A test tool", + "name": "tool_a", + "description": "First tool", + "parameters": { + "type": "object", + "properties": {"q": {"type": "string"}}, + "required": ["q"], + }, + }, + }, + { + "type": "function", + "function": { + "name": "tool_b", + "description": "Second tool", "parameters": { "type": "object", "properties": {"q": {"type": "string"}}, @@ -1315,8 +1327,9 @@ def test_tool_search_passthrough_preserves_tool_search_type(): assert "input_schema" in converted[1] -def test_tool_search_tool_choice_excludes_search_tool(): - """When tool_search is enabled with a single regular tool, tool_choice should still work.""" +def test_tool_search_single_tool_skips_search_and_forces_choice(): + """With only 1 tool, tool_search is skipped (nothing to search) and the + normal forced tool_choice optimisation still applies.""" llm = LLM(model="anthropic/claude-sonnet-4-5", tool_search=True) crewai_tools = [ @@ -1344,10 +1357,18 @@ def test_tool_search_tool_choice_excludes_search_tool(): available_functions={"test_tool": lambda q: "result"}, ) - # Should have tool_choice forcing the single regular tool + # Single tool — tool_search skipped, tool_choice forced as normal assert "tool_choice" in params assert params["tool_choice"]["name"] == "test_tool" + # No tool search tool should be injected + tool_types = [t.get("type", "") for t in params["tools"]] + for ts_type in ("tool_search_tool_bm25_20251119", "tool_search_tool_regex_20251119"): + assert ts_type not in tool_types + + # No defer_loading on the single tool + assert "defer_loading" not in params["tools"][0] + def test_tool_search_via_llm_class(): """Verify tool_search param passes through LLM class correctly."""