| | """Web search tool using DuckDuckGo.""" |
| |
|
| | import asyncio |
| |
|
| | import structlog |
| | from duckduckgo_search import DDGS |
| |
|
| | from src.utils.models import Citation, Evidence, SearchResult |
| |
|
| | logger = structlog.get_logger() |
| |
|
| |
|
| | class WebSearchTool: |
| | """Tool for searching the web using DuckDuckGo.""" |
| |
|
| | def __init__(self) -> None: |
| | self._ddgs = DDGS() |
| |
|
| | async def search(self, query: str, max_results: int = 10) -> SearchResult: |
| | """Execute a web search.""" |
| | try: |
| | loop = asyncio.get_running_loop() |
| |
|
| | def _do_search() -> list[dict[str, str]]: |
| | |
| | return list(self._ddgs.text(query, max_results=max_results)) |
| |
|
| | raw_results = await loop.run_in_executor(None, _do_search) |
| |
|
| | evidence = [] |
| | for r in raw_results: |
| | ev = Evidence( |
| | content=r.get("body", ""), |
| | citation=Citation( |
| | title=r.get("title", "No Title"), |
| | url=r.get("href", ""), |
| | source="web", |
| | date="Unknown", |
| | authors=[], |
| | ), |
| | relevance=0.0, |
| | ) |
| | evidence.append(ev) |
| |
|
| | return SearchResult( |
| | query=query, evidence=evidence, sources_searched=["web"], total_found=len(evidence) |
| | ) |
| |
|
| | except Exception as e: |
| | logger.error("Web search failed", error=str(e)) |
| | return SearchResult( |
| | query=query, evidence=[], sources_searched=["web"], total_found=0, errors=[str(e)] |
| | ) |
| |
|