feat(crewai-tools): add highlights to ExaSearchTool, rename from EXASearchTool
Some checks failed
CodeQL Advanced / Analyze (actions) (push) Has been cancelled
CodeQL Advanced / Analyze (python) (push) Has been cancelled
Check Documentation Broken Links / Check broken links (push) Has been cancelled
Vulnerability Scan / pip-audit (push) Has been cancelled

* 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>
This commit is contained in:
Ishan Goswami
2026-05-01 18:55:23 +05:30
committed by GitHub
parent b30fdbaa0e
commit 07c4a30f2e
15 changed files with 286 additions and 93 deletions

View File

@@ -76,7 +76,7 @@ from crewai_tools.tools.e2b_sandbox_tool import (
E2BFileTool,
E2BPythonTool,
)
from crewai_tools.tools.exa_tools.exa_search_tool import EXASearchTool
from crewai_tools.tools.exa_tools.exa_search_tool import EXASearchTool, ExaSearchTool
from crewai_tools.tools.file_read_tool.file_read_tool import FileReadTool
from crewai_tools.tools.file_writer_tool.file_writer_tool import FileWriterTool
from crewai_tools.tools.files_compressor_tool.files_compressor_tool import (
@@ -258,6 +258,7 @@ __all__ = [
"E2BPythonTool",
"EXASearchTool",
"EnterpriseActionTool",
"ExaSearchTool",
"FileCompressorTool",
"FileReadTool",
"FileWriterTool",

View File

@@ -65,7 +65,7 @@ from crewai_tools.tools.e2b_sandbox_tool import (
E2BFileTool,
E2BPythonTool,
)
from crewai_tools.tools.exa_tools.exa_search_tool import EXASearchTool
from crewai_tools.tools.exa_tools.exa_search_tool import EXASearchTool, ExaSearchTool
from crewai_tools.tools.file_read_tool.file_read_tool import FileReadTool
from crewai_tools.tools.file_writer_tool.file_writer_tool import FileWriterTool
from crewai_tools.tools.files_compressor_tool.files_compressor_tool import (
@@ -242,6 +242,7 @@ __all__ = [
"E2BFileTool",
"E2BPythonTool",
"EXASearchTool",
"ExaSearchTool",
"FileCompressorTool",
"FileReadTool",
"FileWriterTool",

View File

@@ -1,7 +1,7 @@
# EXASearchTool Documentation
# ExaSearchTool Documentation
## Description
This tool is designed to perform a semantic search for a specified query from a text's content across the internet. It utilizes the `https://exa.ai/` API to fetch and display the most relevant search results based on the query provided by the user.
This tool lets CrewAI agents search the web using [Exa](https://exa.ai/), the fastest and most accurate web search API. By default the tool returns token-efficient highlights of the most relevant results for any query; you can also opt in to full page content.
## Installation
To incorporate this tool into your project, follow the installation instructions below:
@@ -10,21 +10,23 @@ uv add crewai[tools] exa_py
```
## Example
The following example demonstrates how to initialize the tool and execute a search with a given query:
The following example demonstrates how to initialize the tool and run a search:
```python
from crewai_tools import EXASearchTool
from crewai_tools import ExaSearchTool
# Initialize the tool for internet searching capabilities
tool = EXASearchTool(api_key="your_api_key")
# Default: results with token-efficient highlights
tool = ExaSearchTool(api_key="your_api_key", highlights=True)
```
## Steps to Get Started
To effectively use the `EXASearchTool`, follow these steps:
To effectively use the `ExaSearchTool`, follow these steps:
1. **Package Installation**: Confirm that the `crewai[tools]` package is installed in your Python environment.
2. **API Key Acquisition**: Acquire a `https://exa.ai/` API key by registering for a free account at `https://exa.ai/`.
3. **Environment Configuration**: Store your obtained API key in an environment variable named `EXA_API_KEY` to facilitate its use by the tool.
2. **API Key Acquisition**: Get an Exa API key from the [Exa dashboard](https://dashboard.exa.ai/api-keys).
3. **Environment Configuration**: Store your API key in an environment variable named `EXA_API_KEY` so the tool can pick it up automatically.
## Conclusion
By integrating the `EXASearchTool` into Python projects, users gain the ability to conduct real-time, relevant searches across the internet directly from their applications. By adhering to the setup and usage guidelines provided, incorporating this tool into projects is streamlined and straightforward.
For details on choosing between highlights and full content, see the [Exa search best practices](https://exa.ai/docs/reference/search-best-practices).
## Note
`EXASearchTool` is a deprecated alias for `ExaSearchTool`. Existing imports continue to work but emit a deprecation warning; please migrate to `ExaSearchTool`.

View File

@@ -3,12 +3,19 @@ from __future__ import annotations
from builtins import type as type_
import os
from typing import Any, TypedDict
import warnings
from crewai.tools import BaseTool, EnvVar
from pydantic import BaseModel, ConfigDict, Field
from typing_extensions import Required
try:
from exa_py import Exa
except ImportError:
Exa = None # type: ignore[assignment,misc]
class SearchParams(TypedDict, total=False):
"""Parameters for Exa search API."""
@@ -18,7 +25,7 @@ class SearchParams(TypedDict, total=False):
include_domains: list[str]
class EXABaseToolSchema(BaseModel):
class ExaBaseToolSchema(BaseModel):
search_query: str = Field(
..., description="Mandatory search query you want to use to search the internet"
)
@@ -31,14 +38,20 @@ class EXABaseToolSchema(BaseModel):
)
class EXASearchTool(BaseTool):
EXABaseToolSchema = ExaBaseToolSchema
class ExaSearchTool(BaseTool):
model_config = ConfigDict(arbitrary_types_allowed=True)
name: str = "EXASearchTool"
description: str = "Search the internet using Exa"
args_schema: type_[BaseModel] = EXABaseToolSchema
name: str = "ExaSearchTool"
description: str = (
"Search the web with Exa, the fastest and most accurate web search API."
)
args_schema: type_[BaseModel] = ExaBaseToolSchema
client: Any | None = None
content: bool | None = False
summary: bool | None = False
content: bool | dict[str, Any] | None = False
summary: bool | dict[str, Any] | None = False
highlights: bool | dict[str, Any] | None = True
type: str | None = "auto"
package_dependencies: list[str] = Field(default_factory=lambda: ["exa_py"])
api_key: str | None = Field(
@@ -68,17 +81,17 @@ class EXASearchTool(BaseTool):
def __init__(
self,
content: bool | None = False,
summary: bool | None = False,
content: bool | dict[str, Any] | None = False,
summary: bool | dict[str, Any] | None = False,
highlights: bool | dict[str, Any] | None = True,
type: str | None = "auto",
**kwargs: Any,
) -> None:
super().__init__(
**kwargs,
)
try:
from exa_py import Exa
except ImportError as e:
global Exa
if Exa is None:
import click
if click.confirm(
@@ -88,12 +101,13 @@ class EXASearchTool(BaseTool):
subprocess.run(["uv", "add", "exa_py"], check=True) # noqa: S607
# Re-import after installation
from exa_py import Exa
from exa_py import Exa as _Exa
Exa = _Exa # type: ignore[misc]
else:
raise ImportError(
"You are missing the 'exa_py' package. Would you like to install it?"
) from e
"You are missing the 'exa_py' package. Please install it to use ExaSearchTool."
)
client_kwargs: dict[str, str] = {}
if self.api_key:
@@ -101,8 +115,10 @@ class EXASearchTool(BaseTool):
if self.base_url:
client_kwargs["base_url"] = self.base_url
self.client = Exa(**client_kwargs)
self.client.headers["x-exa-integration"] = "crewai"
self.content = content
self.summary = summary
self.highlights = highlights
self.type = type
def _run(
@@ -126,10 +142,31 @@ class EXASearchTool(BaseTool):
if include_domains:
search_params["include_domains"] = include_domains
contents_kwargs: dict[str, Any] = {}
if self.content:
results = self.client.search_and_contents(
search_query, summary=self.summary, **search_params
contents_kwargs["text"] = self.content
if self.highlights:
contents_kwargs["highlights"] = self.highlights
if self.summary:
contents_kwargs["summary"] = self.summary
if contents_kwargs:
return self.client.search_and_contents(
search_query, **contents_kwargs, **search_params
)
else:
results = self.client.search(search_query, **search_params)
return results
return self.client.search(search_query, **search_params)
class EXASearchTool(ExaSearchTool):
"""Deprecated alias for :class:`ExaSearchTool`. Kept for backwards compatibility."""
name: str = "ExaSearchTool"
def __init__(self, *args: Any, **kwargs: Any) -> None:
warnings.warn(
"EXASearchTool is deprecated and will be removed in a future release; "
"use ExaSearchTool instead.",
DeprecationWarning,
stacklevel=2,
)
super().__init__(*args, **kwargs)