File size: 4,529 Bytes
d9162ac
 
 
 
 
 
 
 
 
 
 
 
 
 
e3c2163
d9162ac
 
e3c2163
 
 
d9162ac
 
 
e3c2163
d9162ac
 
 
 
e3c2163
 
 
 
 
 
d9162ac
e3c2163
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
b4f9ff5
e3c2163
 
 
 
 
 
 
 
 
 
 
 
 
b4f9ff5
e3c2163
 
 
 
 
 
 
d9162ac
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
"""Factory for creating web search tools based on configuration."""

import structlog

from src.tools.base import SearchTool
from src.tools.searchxng_web_search import SearchXNGWebSearchTool
from src.tools.serper_web_search import SerperWebSearchTool
from src.tools.web_search import WebSearchTool
from src.utils.config import settings
from src.utils.exceptions import ConfigurationError

logger = structlog.get_logger()


def create_web_search_tool(provider: str | None = None) -> SearchTool | None:
    """Create a web search tool based on configuration.

    Args:
        provider: Override provider selection. If None, uses settings.web_search_provider.

    Returns:
        SearchTool instance, or None if not available/configured

    The tool is selected based on provider (or settings.web_search_provider if None):
    - "serper": SerperWebSearchTool (requires SERPER_API_KEY)
    - "searchxng": SearchXNGWebSearchTool (requires SEARCHXNG_HOST)
    - "duckduckgo": WebSearchTool (always available, no API key)
    - "brave" or "tavily": Not yet implemented, returns None
    - "auto": Auto-detect best available provider (prefers Serper > SearchXNG > DuckDuckGo)

    Auto-detection logic (when provider is "auto" or not explicitly set):
    1. Try Serper if SERPER_API_KEY is available (best quality - Google search + full content scraping)
    2. Try SearchXNG if SEARCHXNG_HOST is available
    3. Fall back to DuckDuckGo (always available, but lower quality - snippets only)
    """
    provider = provider or settings.web_search_provider

    # Auto-detect best available provider if "auto" or if provider is duckduckgo but better options exist
    if provider == "auto" or (provider == "duckduckgo" and settings.serper_api_key):
        # Prefer Serper if API key is available (better quality)
        if settings.serper_api_key:
            try:
                logger.info(
                    "Auto-detected Serper web search (SERPER_API_KEY found)",
                    provider="serper",
                )
                return SerperWebSearchTool()
            except Exception as e:
                logger.warning(
                    "Failed to initialize Serper, falling back",
                    error=str(e),
                )

        # Try SearchXNG as second choice
        if settings.searchxng_host:
            try:
                logger.info(
                    "Auto-detected SearchXNG web search (SEARCHXNG_HOST found)",
                    provider="searchxng",
                )
                return SearchXNGWebSearchTool()
            except Exception as e:
                logger.warning(
                    "Failed to initialize SearchXNG, falling back",
                    error=str(e),
                )

        # Fall back to DuckDuckGo
        if provider == "auto":
            logger.info(
                "Auto-detected DuckDuckGo web search (no API keys found)",
                provider="duckduckgo",
            )
        return WebSearchTool()

    try:
        if provider == "serper":
            if not settings.serper_api_key:
                logger.warning(
                    "Serper provider selected but no API key found",
                    hint="Set SERPER_API_KEY environment variable",
                )
                return None
            return SerperWebSearchTool()

        elif provider == "searchxng":
            if not settings.searchxng_host:
                logger.warning(
                    "SearchXNG provider selected but no host found",
                    hint="Set SEARCHXNG_HOST environment variable",
                )
                return None
            return SearchXNGWebSearchTool()

        elif provider == "duckduckgo":
            # DuckDuckGo is always available (no API key required)
            return WebSearchTool()

        elif provider in ("brave", "tavily"):
            logger.warning(
                f"Web search provider '{provider}' not yet implemented",
                hint="Use 'serper', 'searchxng', or 'duckduckgo'",
            )
            return None

        else:
            logger.warning(f"Unknown web search provider '{provider}', falling back to DuckDuckGo")
            return WebSearchTool()

    except ConfigurationError as e:
        logger.error("Failed to create web search tool", error=str(e), provider=provider)
        return None
    except Exception as e:
        logger.error("Unexpected error creating web search tool", error=str(e), provider=provider)
        return None