Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions lib/crewai-tools/src/crewai_tools/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,9 @@
from crewai_tools.tools.selenium_scraping_tool.selenium_scraping_tool import (
SeleniumScrapingTool,
)
from crewai_tools.tools.searchapi_tool.searchapi_search_tool import (
SearchApiSearchTool,
)
Comment thread
coderabbitai[bot] marked this conversation as resolved.
from crewai_tools.tools.serpapi_tool.serpapi_google_search_tool import (
SerpApiGoogleSearchTool,
)
Expand Down Expand Up @@ -300,6 +303,7 @@
"ScrapegraphScrapeTool",
"ScrapegraphScrapeToolSchema",
"ScrapflyScrapeWebsiteTool",
"SearchApiSearchTool",
"SeleniumScrapingTool",
"SerpApiGoogleSearchTool",
"SerpApiGoogleShoppingTool",
Expand Down
4 changes: 4 additions & 0 deletions lib/crewai-tools/src/crewai_tools/tools/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,9 @@
from crewai_tools.tools.selenium_scraping_tool.selenium_scraping_tool import (
SeleniumScrapingTool,
)
from crewai_tools.tools.searchapi_tool.searchapi_search_tool import (
SearchApiSearchTool,
)
from crewai_tools.tools.serpapi_tool.serpapi_google_search_tool import (
SerpApiGoogleSearchTool,
)
Expand Down Expand Up @@ -283,6 +286,7 @@
"ScrapegraphScrapeToolSchema",
"ScrapflyScrapeWebsiteTool",
"SeleniumScrapingTool",
"SearchApiSearchTool",
"SerpApiGoogleSearchTool",
"SerpApiGoogleShoppingTool",
"SerperDevTool",
Expand Down
84 changes: 84 additions & 0 deletions lib/crewai-tools/src/crewai_tools/tools/searchapi_tool/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
# SearchApi Tool

## Description
[SearchApi](https://www.searchapi.io) is a real-time SERP API that delivers structured data from 100+ search engines and sources. A single tool class supports multiple engines — no need for separate tool classes per search type.

To use this tool, set `SEARCHAPI_API_KEY` in your environment. Get your API key at [searchapi.io](https://www.searchapi.io).

## Installation
```shell
pip install 'crewai[tools]'
```

## Supported Engines

| Engine | Description |
Comment thread
coderabbitai[bot] marked this conversation as resolved.
|--------|-------------|
| `google` | Google web search (default) |
| `google_news` | Google News |
| `google_shopping` | Google Shopping |
| `google_jobs` | Google Jobs |
| `youtube` | YouTube video search |
| `bing` | Bing web search |
| `baidu` | Baidu search |

## Usage

### Google Search (default)
```python
from crewai_tools import SearchApiSearchTool

tool = SearchApiSearchTool()
```

### Google News
```python
from crewai_tools import SearchApiSearchTool

tool = SearchApiSearchTool(engine="google_news")
```

### Google Shopping
```python
from crewai_tools import SearchApiSearchTool

tool = SearchApiSearchTool(engine="google_shopping")
```

### YouTube Search
```python
from crewai_tools import SearchApiSearchTool

tool = SearchApiSearchTool(engine="youtube")
```

### With Location, Country and Language
```python
from crewai_tools import SearchApiSearchTool

tool = SearchApiSearchTool(
engine="google",
n_results=5,
country="us",
language="en",
)

# Location can also be passed per-query at runtime
result = tool.run(search_query="coffee shops", location="San Francisco")
```

## Configuration

| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `engine` | `str` | `"google"` | Search engine to use |
| `n_results` | `int` | `10` | Number of results to return |
| `country` | `str \| None` | `None` | Country code (e.g., `"us"`, `"uk"`) |
| `language` | `str \| None` | `None` | Language code (e.g., `"en"`, `"es"`) |

## Runtime Parameters

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `search_query` | `str` | Yes | The search query to execute |
| `location` | `str \| None` | No | Location for the search (e.g., `"New York"`) |
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
"""SearchApi search tool for CrewAI agents."""

import logging
import os
from typing import Any

from crewai.tools import BaseTool, EnvVar
from pydantic import BaseModel, ConfigDict, Field, PrivateAttr, field_validator
import requests


logger = logging.getLogger(__name__)

BASE_URL = "https://www.searchapi.io/api/v1/search"

SUPPORTED_ENGINES = [
"google",
"google_news",
"google_shopping",
"google_jobs",
"youtube",
"bing",
"baidu",
]


class SearchApiSearchToolSchema(BaseModel):
"""Input schema for SearchApi search tool."""

search_query: str = Field(
..., description="Mandatory search query to perform the search."
)
location: str | None = Field(
None, description="Location to perform the search from (e.g., 'New York')."
)


class SearchApiSearchTool(BaseTool):
"""Search the internet using SearchApi.

Supports multiple engines including Google, Google News, Google Shopping,
Google Jobs, YouTube, Bing, and Baidu. Configure the engine at initialization.
"""

model_config = ConfigDict(
arbitrary_types_allowed=True, validate_assignment=True, frozen=False
)
name: str = "SearchApi Search"
description: str = (
"A tool that searches the internet using SearchApi. "
"Supports multiple engines: google, google_news, google_shopping, "
"google_jobs, youtube, bing, and baidu."
)
args_schema: type[BaseModel] = SearchApiSearchToolSchema
package_dependencies: list[str] = Field(default_factory=lambda: ["requests"])
env_vars: list[EnvVar] = Field(
default_factory=lambda: [
EnvVar(
name="SEARCHAPI_API_KEY",
description="API key for SearchApi (https://www.searchapi.io)",
required=True,
),
]
)

engine: str = "google"
n_results: int = 10
country: str | None = None
language: str | None = None

_api_key: str | None = PrivateAttr(default=None)

@field_validator("engine")
@classmethod
def validate_engine(cls, v: str) -> str:
"""Validate the engine is supported."""
if v not in SUPPORTED_ENGINES:
raise ValueError(
f"Invalid engine: {v}. Must be one of: {', '.join(SUPPORTED_ENGINES)}"
)
return v

def __init__(self, **kwargs: Any) -> None:
"""Initialize the SearchApi tool and validate configuration."""
super().__init__(**kwargs)
api_key = os.getenv("SEARCHAPI_API_KEY")
if not api_key:
raise ValueError(
"Missing SEARCHAPI_API_KEY. Get your key at https://www.searchapi.io"
)
self._api_key = api_key
Comment thread
coderabbitai[bot] marked this conversation as resolved.

def _run(self, **kwargs: Any) -> Any:
"""Execute a search query against the configured SearchApi engine."""
search_query: str | None = kwargs.get("search_query") or kwargs.get("query")
if not search_query:
raise ValueError("search_query is required")

params: dict[str, Any] = {
"engine": self.engine,
"q": search_query,
"num": self.n_results,
}

location = kwargs.get("location")
if location:
params["location"] = location
if self.country:
params["gl"] = self.country
if self.language:
params["hl"] = self.language

headers = {"Authorization": f"Bearer {self._api_key}"}

try:
response = requests.get(
BASE_URL, params=params, headers=headers, timeout=30
)
response.raise_for_status()
results: dict[str, Any] = response.json()
except requests.RequestException as e:
error_msg = f"An error occurred while performing the search: {e!s}"
logger.error(error_msg)
return error_msg

for key in ["search_metadata", "search_parameters", "pagination"]:
results.pop(key, None)

return results
Loading