"""Web search tool using DuckDuckGo (no API key required)."""
import asyncio
from ddgs import DDGS
from .base import Tool, ToolResult
class WebSearchTool(Tool):
name = "web_search"
description = (
"Search the web (DuckDuckGo). Use when you need current info, real-time data, "
"documentation, or facts you're uncertain about. Returns titles, URLs, snippets. "
"Prefer this over your own training knowledge when recency or accuracy matters."
)
parameters = {
"type": "object",
"properties": {
"query": {"type": "string", "description": "Search query"},
"max_results": {
"type": "integer",
"description": "Number of results to return (default 5)",
"default": 5,
},
},
"required": ["query"],
}
async def execute(self, params: dict) -> ToolResult:
query = params["query"]
max_results = int(params.get("max_results", 5))
try:
# Run blocking DDGS call in a thread to avoid blocking the event loop
results = await asyncio.to_thread(
lambda: list(DDGS().text(query, max_results=max_results))
)
if not results:
return ToolResult(success=True, output="No results found.")
formatted = [
f"[{i+1}] {r['title']}\n URL: {r['href']}\n {r['body']}"
for i, r in enumerate(results)
]
output = "\n\n".join(formatted)
return ToolResult(success=True, output=output, metadata={"results": results})
except Exception as e:
return ToolResult(success=False, output=f"Search failed: {e}", error=str(e))