mirror of
https://github.com/crewAIInc/crewAI.git
synced 2026-05-05 09:12:39 +00:00
Some checks failed
* feat(crewai-tools): add highlights to ExaSearchTool, rename from EXASearchTool - Add a highlights init param so agents can get token-efficient excerpts instead of full pages - Rename EXASearchTool to ExaSearchTool; keep EXASearchTool as a deprecated alias so existing imports keep working - Update the docs and example to use highlights as the recommended option - Add a small note that says Exa is the fastest and most accurate web search API - Add tests for the new highlights param and the deprecation alias * fix(crewai-tools): import order and module-level Exa for tests - Reorder std-lib imports so ruff is happy with force-sort-within-sections. - Import Exa at module level (with a fallback) so the existing test mocks resolve. The lazy install prompt still works if exa_py is missing. - Allow content and summary to be a dict, matching highlights. - Trim test file to the cases this PR introduces (highlights param and the EXASearchTool deprecation alias). Existing init-shape tests stay. Co-Authored-By: ishan <ishan@exa.ai> * chore(crewai-tools): drop self-explanatory comment on schema alias Co-Authored-By: ishan <ishan@exa.ai> * docs(crewai-tools): default highlights to True, drop summary from examples Co-Authored-By: ishan <ishan@exa.ai> * docs(crewai-tools): simplify highlights examples to highlights=True Co-Authored-By: ishan <ishan@exa.ai> * feat(crewai-tools): add x-exa-integration header for usage tracking Co-Authored-By: ishan <ishan@exa.ai> * docs(crewai-tools): add Exa MCP section and resources links Co-Authored-By: ishan <ishan@exa.ai> --------- Co-authored-by: ishan <ishan@exa.ai> Co-authored-by: Greyson LaLonde <greyson.r.lalonde@gmail.com> Co-authored-by: Lorenze Jay <63378463+lorenzejay@users.noreply.github.com>
114 lines
3.8 KiB
Python
114 lines
3.8 KiB
Python
import os
|
|
from unittest.mock import MagicMock, patch
|
|
|
|
from crewai_tools import EXASearchTool, ExaSearchTool
|
|
import pytest
|
|
|
|
|
|
@pytest.fixture
|
|
def exa_search_tool():
|
|
return ExaSearchTool(api_key="test_api_key")
|
|
|
|
|
|
@pytest.fixture(autouse=True)
|
|
def mock_exa_api_key():
|
|
with patch.dict(os.environ, {"EXA_API_KEY": "test_key_from_env"}):
|
|
yield
|
|
|
|
|
|
def test_exa_search_tool_initialization():
|
|
with patch.dict(os.environ, {}, clear=True):
|
|
with patch(
|
|
"crewai_tools.tools.exa_tools.exa_search_tool.Exa"
|
|
) as mock_exa_class:
|
|
api_key = "test_api_key"
|
|
tool = ExaSearchTool(api_key=api_key)
|
|
|
|
assert tool.api_key == api_key
|
|
assert tool.content is False
|
|
assert tool.summary is False
|
|
assert tool.highlights is True
|
|
assert tool.type == "auto"
|
|
mock_exa_class.assert_called_once_with(api_key=api_key)
|
|
|
|
|
|
def test_exa_search_tool_initialization_with_env(mock_exa_api_key):
|
|
with patch.dict(os.environ, {"EXA_API_KEY": "test_key_from_env"}, clear=True):
|
|
with patch(
|
|
"crewai_tools.tools.exa_tools.exa_search_tool.Exa"
|
|
) as mock_exa_class:
|
|
ExaSearchTool()
|
|
mock_exa_class.assert_called_once_with(api_key="test_key_from_env")
|
|
|
|
|
|
def test_exa_search_tool_initialization_with_base_url():
|
|
with patch.dict(os.environ, {}, clear=True):
|
|
with patch(
|
|
"crewai_tools.tools.exa_tools.exa_search_tool.Exa"
|
|
) as mock_exa_class:
|
|
api_key = "test_api_key"
|
|
base_url = "https://custom.exa.api.com"
|
|
tool = ExaSearchTool(api_key=api_key, base_url=base_url)
|
|
|
|
assert tool.api_key == api_key
|
|
assert tool.base_url == base_url
|
|
assert tool.content is False
|
|
assert tool.summary is False
|
|
assert tool.highlights is True
|
|
assert tool.type == "auto"
|
|
mock_exa_class.assert_called_once_with(api_key=api_key, base_url=base_url)
|
|
|
|
|
|
@pytest.fixture
|
|
def mock_exa_base_url():
|
|
with patch.dict(os.environ, {"EXA_BASE_URL": "https://env.exa.api.com"}):
|
|
yield
|
|
|
|
|
|
def test_exa_search_tool_initialization_with_env_base_url(
|
|
mock_exa_api_key, mock_exa_base_url
|
|
):
|
|
with patch("crewai_tools.tools.exa_tools.exa_search_tool.Exa") as mock_exa_class:
|
|
ExaSearchTool()
|
|
mock_exa_class.assert_called_once_with(
|
|
api_key="test_key_from_env", base_url="https://env.exa.api.com"
|
|
)
|
|
|
|
|
|
def test_exa_search_tool_initialization_without_base_url():
|
|
with patch.dict(os.environ, {}, clear=True):
|
|
with patch(
|
|
"crewai_tools.tools.exa_tools.exa_search_tool.Exa"
|
|
) as mock_exa_class:
|
|
api_key = "test_api_key"
|
|
tool = ExaSearchTool(api_key=api_key)
|
|
|
|
assert tool.api_key == api_key
|
|
assert tool.base_url is None
|
|
mock_exa_class.assert_called_once_with(api_key=api_key)
|
|
|
|
|
|
def test_exa_search_tool_highlights_uses_search_and_contents():
|
|
with patch("crewai_tools.tools.exa_tools.exa_search_tool.Exa") as mock_exa_class:
|
|
mock_client = MagicMock()
|
|
mock_exa_class.return_value = mock_client
|
|
tool = ExaSearchTool(
|
|
api_key="test_api_key", highlights={"max_characters": 4000}
|
|
)
|
|
|
|
tool._run(search_query="hello world")
|
|
|
|
mock_client.search_and_contents.assert_called_once_with(
|
|
"hello world",
|
|
highlights={"max_characters": 4000},
|
|
type="auto",
|
|
)
|
|
mock_client.search.assert_not_called()
|
|
|
|
|
|
def test_exasearchtool_alias_is_deprecated():
|
|
with patch("crewai_tools.tools.exa_tools.exa_search_tool.Exa"):
|
|
with pytest.warns(DeprecationWarning, match="ExaSearchTool"):
|
|
tool = EXASearchTool(api_key="test_api_key")
|
|
assert isinstance(tool, ExaSearchTool)
|