Skip to content
Merged
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
10 changes: 7 additions & 3 deletions docker/clients/serper_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,19 +35,22 @@ def _convert_organic_results(organic_results: list) -> List[APISearchResult]:
]


def fetch_search_results(query: str, api_key: str) -> List[APISearchResult]:
def fetch_search_results(
query: str, api_key: str, max_results: int = 5
) -> List[APISearchResult]:
"""
Fetches search results from the Serper API.

Args:
query: The search query
api_key: The Serper API key
max_results: Maximum number of results to return (default: 5)

Returns:
A list of APISearchResult objects from Serper API
"""
url = "https://google.serper.dev/search"
payload = json.dumps({"q": query})
payload = json.dumps({"q": query, "num": max_results})
headers = {"X-API-KEY": api_key, "Content-Type": "application/json"}

try:
Expand All @@ -57,7 +60,8 @@ def fetch_search_results(query: str, api_key: str) -> List[APISearchResult]:

# Process and return the search results
if "organic" in data:
return _convert_organic_results(data["organic"])
results = _convert_organic_results(data["organic"])
return results[:max_results] # Ensure we don't exceed max_results
return []
except Exception as e:
logger.error(f"Error fetching search results: {str(e)}")
Expand Down
7 changes: 4 additions & 3 deletions docker/services/search_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,15 @@


def fetch_with_fallback(
query: str, serper_api_key: str = ""
query: str, serper_api_key: str = "", max_results: int = 5
) -> Tuple[List[APISearchResult], str]:
"""
Fetch search results with automatic fallback from Serper to DuckDuckGo.

Args:
query: Search query string
serper_api_key: Optional Serper API key
max_results: Maximum number of results to return (default: 5)

Returns:
Tuple of (results list, source name)
Expand All @@ -35,13 +36,13 @@ def fetch_with_fallback(
if serper_api_key:
logger.info("Using Serper API for search")
search_source = "Serper API"
api_results = fetch_search_results(query, serper_api_key)
api_results = fetch_search_results(query, serper_api_key, max_results)

# Fall back to DuckDuckGo if no API key or no results from Serper
if not api_results:
_log_fallback_reason(serper_api_key)
search_source = "DuckDuckGo (free fallback)"
api_results = fetch_duckduckgo_search_results(query)
api_results = fetch_duckduckgo_search_results(query, max_results)

return api_results, search_source

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ def test_uses_serper_when_key_provided(self, mock_serper):
assert source == "Serper API"
assert len(results) == 1
assert results[0].title == "Serper Result"
mock_serper.assert_called_once_with("test query", "fake_key")
mock_serper.assert_called_once_with("test query", "fake_key", 5)

@patch("services.search_service.fetch_duckduckgo_search_results")
@patch("services.search_service.fetch_search_results")
Expand Down
7 changes: 4 additions & 3 deletions docker/tools/search_tool.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
SERPER_API_KEY = os.environ.get("SERPER_API_KEY", "")


async def search_tool(query: str, ctx=None) -> dict:
async def search_tool(query: str, ctx=None, max_results: int = 5) -> dict:
"""Search the web for information on a given query.

This MCP tool searches the web using Serper API (premium) or DuckDuckGo
Expand All @@ -30,11 +30,12 @@ async def search_tool(query: str, ctx=None) -> dict:
Args:
query: The search query string
ctx: Optional MCP context (may contain authentication headers)
max_results: Maximum number of results to return (default: 5)

Returns:
Dict representation of SearchResponse model (for MCP JSON serialization)
"""
logger.info(f"Processing search request: {query}")
logger.info(f"Processing search request: {query} (max {max_results} results)")

# Validate authentication if WEBCAT_API_KEY is set
is_valid, error_msg = validate_bearer_token(ctx)
Expand All @@ -49,7 +50,7 @@ async def search_tool(query: str, ctx=None) -> dict:
return response.model_dump()

# Fetch results with automatic fallback
api_results, search_source = fetch_with_fallback(query, SERPER_API_KEY)
api_results, search_source = fetch_with_fallback(query, SERPER_API_KEY, max_results)

# Check if we got any results
if not api_results:
Expand Down
Loading