Spaces:
Sleeping
Sleeping
| """DuckDuckGo web search – zero API key required. | |
| Uses the ddgs library (pip install ddgs). | |
| Falls back gracefully if the library is not installed. | |
| """ | |
| from __future__ import annotations | |
| import logging | |
| from concurrent.futures import ThreadPoolExecutor, TimeoutError as FuturesTimeout | |
| log = logging.getLogger(__name__) | |
| _HARD_TIMEOUT_S = 15 | |
| def _run_ddgs(query: str, max_results: int) -> list[dict]: | |
| from ddgs import DDGS | |
| with DDGS(timeout=10) as ddgs: | |
| return list(ddgs.text(query, max_results=max_results)) | |
| def web_search(query: str, max_results: int = 5) -> str: | |
| """Search the web via DuckDuckGo. Returns title/url/snippet results.""" | |
| try: | |
| import ddgs # noqa: F401 | |
| except ImportError: | |
| return "ERROR: ddgs not installed. Run: pip install ddgs" | |
| # Run in a worker thread with a hard wall-clock timeout: DDGS' pagination | |
| # loop can stall beyond its per-request timeout and block the agent. | |
| pool = ThreadPoolExecutor(max_workers=1, thread_name_prefix="web_search") | |
| try: | |
| future = pool.submit(_run_ddgs, query, max_results) | |
| try: | |
| results = future.result(timeout=_HARD_TIMEOUT_S) | |
| except FuturesTimeout: | |
| log.warning("[web_search] hard timeout after %ss, query=%r", _HARD_TIMEOUT_S, query) | |
| return f"ERROR: web_search timed out after {_HARD_TIMEOUT_S}s" | |
| finally: | |
| # Don't block on the possibly-stuck worker thread on shutdown. | |
| pool.shutdown(wait=False, cancel_futures=True) | |
| except Exception as e: | |
| log.warning("[web_search] DuckDuckGo error: %s", e) | |
| return f"ERROR: DuckDuckGo search failed: {e}" | |
| if not results: | |
| return "No results." | |
| lines: list[str] = [] | |
| for r in results: | |
| title = r.get("title", "No Title") | |
| url = r.get("href", "No URL") | |
| body = r.get("body", "No snippet available") | |
| lines.append(f"- {title} ({url})\n {body}") | |
| import json | |
| metadata = { | |
| "value": len(results), | |
| "data_source": "duckduckgo", | |
| "record_type": "web-snippet", | |
| "type_strictness": "broad", | |
| "note": "Web snippets are unstructured and may contain irrelevant or duplicate information." | |
| } | |
| res_text = "\n".join(lines) | |
| return f"{res_text}\n\nMETADATA:\n{json.dumps(metadata, indent=2)}" | |