Newer
Older
navi-1 / navi / tools / web_search.py
"""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))