From 0a577b7d056361440619ac2c6af734726fcf566b Mon Sep 17 00:00:00 2001 From: Gabe Milani Date: Wed, 17 Jun 2026 14:41:44 -0700 Subject: [PATCH] fix: remove duplicated Exa tool (#6205) * fix: remove duplicated Exa tool * fix tests --- .../src/crewai_tools/generate_tool_specs.py | 2 + .../tools/exa_tools/exa_search_tool.py | 4 +- .../tests/test_generate_tool_specs.py | 26 ++- .../tests/tools/exa_search_tool_test.py | 8 + lib/crewai-tools/tool.specs.json | 219 ------------------ 5 files changed, 36 insertions(+), 223 deletions(-) diff --git a/lib/crewai-tools/src/crewai_tools/generate_tool_specs.py b/lib/crewai-tools/src/crewai_tools/generate_tool_specs.py index 579adaa30..046454ede 100644 --- a/lib/crewai-tools/src/crewai_tools/generate_tool_specs.py +++ b/lib/crewai-tools/src/crewai_tools/generate_tool_specs.py @@ -32,6 +32,8 @@ class ToolSpecExtractor: if name.endswith("Tool") and name not in self.processed_tools: obj = getattr(tools, name, None) if inspect.isclass(obj) and issubclass(obj, BaseTool): + if getattr(obj, "is_deprecated_alias", False): + continue self.extract_tool_info(obj) self.processed_tools.add(name) return self.tools_spec diff --git a/lib/crewai-tools/src/crewai_tools/tools/exa_tools/exa_search_tool.py b/lib/crewai-tools/src/crewai_tools/tools/exa_tools/exa_search_tool.py index 8204b67bb..5a291043b 100644 --- a/lib/crewai-tools/src/crewai_tools/tools/exa_tools/exa_search_tool.py +++ b/lib/crewai-tools/src/crewai_tools/tools/exa_tools/exa_search_tool.py @@ -2,7 +2,7 @@ from __future__ import annotations from builtins import type as type_ import os -from typing import Any, TypedDict +from typing import Any, ClassVar, TypedDict import warnings from crewai.tools import BaseTool, EnvVar @@ -160,6 +160,8 @@ class ExaSearchTool(BaseTool): class EXASearchTool(ExaSearchTool): """Deprecated alias for :class:`ExaSearchTool`. Kept for backwards compatibility.""" + is_deprecated_alias: ClassVar[bool] = True + name: str = "ExaSearchTool" def __init__(self, *args: Any, **kwargs: Any) -> None: diff --git a/lib/crewai-tools/tests/test_generate_tool_specs.py b/lib/crewai-tools/tests/test_generate_tool_specs.py index 8868df96a..4c7046d01 100644 --- a/lib/crewai-tools/tests/test_generate_tool_specs.py +++ b/lib/crewai-tools/tests/test_generate_tool_specs.py @@ -1,3 +1,4 @@ +import builtins import json from unittest import mock @@ -7,6 +8,19 @@ from pydantic import BaseModel, Field import pytest +def _getattr_for(tool_name, tool_cls): + """Build a getattr side_effect that resolves the patched tool name to + ``tool_cls`` while delegating every other lookup (e.g. the + ``is_deprecated_alias`` check) to the real builtin.""" + + def _getattr(obj, name, *default): + if name == tool_name: + return tool_cls + return builtins.getattr(obj, name, *default) + + return _getattr + + class MockToolSchema(BaseModel): query: str = Field(..., description="The query parameter") count: int = Field(5, description="Number of results to return") @@ -84,7 +98,10 @@ def test_unwrap_schema(extractor): def mock_tool_extractor(extractor): with ( mock.patch("crewai_tools.generate_tool_specs.dir", return_value=["MockTool"]), - mock.patch("crewai_tools.generate_tool_specs.getattr", return_value=MockTool), + mock.patch( + "crewai_tools.generate_tool_specs.getattr", + side_effect=_getattr_for("MockTool", MockTool), + ), ): extractor.extract_all_tools() assert len(extractor.tools_spec) == 1 @@ -223,7 +240,7 @@ def test_intermediate_base_fields_preserved_for_derived_tool(extractor): ), mock.patch( "crewai_tools.generate_tool_specs.getattr", - return_value=MockDerivedTool, + side_effect=_getattr_for("MockDerivedTool", MockDerivedTool), ), ): extractor.extract_all_tools() @@ -253,7 +270,10 @@ def test_future_base_tool_field_auto_excluded(extractor): by checking that ONLY non-BaseTool fields appear.""" with ( mock.patch("crewai_tools.generate_tool_specs.dir", return_value=["MockTool"]), - mock.patch("crewai_tools.generate_tool_specs.getattr", return_value=MockTool), + mock.patch( + "crewai_tools.generate_tool_specs.getattr", + side_effect=_getattr_for("MockTool", MockTool), + ), ): extractor.extract_all_tools() tool_info = extractor.tools_spec[0] diff --git a/lib/crewai-tools/tests/tools/exa_search_tool_test.py b/lib/crewai-tools/tests/tools/exa_search_tool_test.py index 3e34480a2..341204cd9 100644 --- a/lib/crewai-tools/tests/tools/exa_search_tool_test.py +++ b/lib/crewai-tools/tests/tools/exa_search_tool_test.py @@ -111,3 +111,11 @@ def test_exasearchtool_alias_is_deprecated(): with pytest.warns(DeprecationWarning, match="ExaSearchTool"): tool = EXASearchTool(api_key="test_api_key") assert isinstance(tool, ExaSearchTool) + + +def test_deprecated_alias_excluded_from_tool_specs(): + from crewai_tools.generate_tool_specs import ToolSpecExtractor + + names = {tool["name"] for tool in ToolSpecExtractor().extract_all_tools()} + assert "ExaSearchTool" in names + assert "EXASearchTool" not in names diff --git a/lib/crewai-tools/tool.specs.json b/lib/crewai-tools/tool.specs.json index b957a3afc..795fa932c 100644 --- a/lib/crewai-tools/tool.specs.json +++ b/lib/crewai-tools/tool.specs.json @@ -9622,225 +9622,6 @@ "type": "object" } }, - { - "description": "Search the web with Exa, the fastest and most accurate web search API.", - "env_vars": [ - { - "default": null, - "description": "API key for Exa services", - "name": "EXA_API_KEY", - "required": false - }, - { - "default": null, - "description": "API url for the Exa services", - "name": "EXA_BASE_URL", - "required": false - } - ], - "humanized_name": "ExaSearchTool", - "init_params_schema": { - "$defs": { - "EnvVar": { - "properties": { - "default": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ], - "default": null, - "title": "Default" - }, - "description": { - "title": "Description", - "type": "string" - }, - "name": { - "title": "Name", - "type": "string" - }, - "required": { - "default": true, - "title": "Required", - "type": "boolean" - } - }, - "required": [ - "name", - "description" - ], - "title": "EnvVar", - "type": "object" - } - }, - "description": "Deprecated alias for :class:`ExaSearchTool`. Kept for backwards compatibility.", - "properties": { - "api_key": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ], - "description": "API key for Exa services", - "required": false, - "title": "Api Key" - }, - "base_url": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ], - "description": "API server url", - "required": false, - "title": "Base Url" - }, - "client": { - "anyOf": [ - {}, - { - "type": "null" - } - ], - "default": null, - "title": "Client" - }, - "content": { - "anyOf": [ - { - "type": "boolean" - }, - { - "additionalProperties": true, - "type": "object" - }, - { - "type": "null" - } - ], - "default": false, - "title": "Content" - }, - "highlights": { - "anyOf": [ - { - "type": "boolean" - }, - { - "additionalProperties": true, - "type": "object" - }, - { - "type": "null" - } - ], - "default": true, - "title": "Highlights" - }, - "summary": { - "anyOf": [ - { - "type": "boolean" - }, - { - "additionalProperties": true, - "type": "object" - }, - { - "type": "null" - } - ], - "default": false, - "title": "Summary" - }, - "type": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ], - "default": "auto", - "title": "Type" - } - }, - "required": [], - "title": "EXASearchTool", - "type": "object" - }, - "name": "EXASearchTool", - "package_dependencies": [ - "exa_py" - ], - "run_params_schema": { - "properties": { - "end_published_date": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ], - "default": null, - "description": "End date for the search", - "title": "End Published Date" - }, - "include_domains": { - "anyOf": [ - { - "items": { - "type": "string" - }, - "type": "array" - }, - { - "type": "null" - } - ], - "default": null, - "description": "List of domains to include in the search", - "title": "Include Domains" - }, - "search_query": { - "description": "Mandatory search query you want to use to search the internet", - "title": "Search Query", - "type": "string" - }, - "start_published_date": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ], - "default": null, - "description": "Start date for the search", - "title": "Start Published Date" - } - }, - "required": [ - "search_query" - ], - "title": "ExaBaseToolSchema", - "type": "object" - } - }, { "description": "Search the web with Exa, the fastest and most accurate web search API.", "env_vars": [