from __future__ import annotations import re from typing import Optional _SEARCH_TRIGGERS = frozenset({ # Time-sensitive single words "news", "weather", "today", "tonight", "yesterday", "price", "stock", # Phrases that imply freshness or external lookup "latest ", "right now", "this week", "this month", "recently", "who is ", "what is ", "when did ", "how much does", "how much is", "look up", "search for", "find out", "release date", "just released", "just announced", "just launched", "current version", "current price", }) def needs_web_search(message: str) -> bool: lower = message.lower() return any(kw in lower for kw in _SEARCH_TRIGGERS) def web_search(query: str, max_results: int = 4) -> str: """Search DuckDuckGo and return formatted result snippets. Returns an empty string on any failure so callers can treat it as optional context — a failed search should never break a chat response. """ try: from duckduckgo_search import DDGS with DDGS() as ddgs: results = list(ddgs.text(query, max_results=max_results)) if not results: return "" parts = [] for i, r in enumerate(results, 1): title = r.get("title", "").strip() body = r.get("body", "").strip() href = r.get("href", "").strip() parts.append(f"{i}. **{title}**\n{body}\nSource: {href}") return "\n\n".join(parts) except Exception: return ""