import asyncio from typing import List, Dict, Optional from fastapi import Request from langchain_tavily import TavilySearch from ..core.logger import SingletonLogger from ..utils.api_key_utils import get_api_key_for_service class TavilyWebSearch: """Wrapper for Tavily search (langchain_tavily).""" @staticmethod async def search( query: str, num: int = 10, topic: str = "general", api_key: Optional[str] = None, request: Optional[Request] = None, ) -> List[Dict[str, str]]: if not api_key and request: api_key = get_api_key_for_service(request, "tavily") tavily_api_key = api_key logger = SingletonLogger().get_logger() def _call(): search = TavilySearch(tavily_api_key=tavily_api_key, topic=topic) return search.run(query, num_results=num // 2) loop = asyncio.get_event_loop() try: raw = await loop.run_in_executor(None, _call) except Exception as e: logger.exception("Error calling Tavily search: %s", e) return [] results: List[Dict[str, str]] = [] for item in raw.get("results", []) if isinstance(raw, dict) else []: try: title = item.get("title") or "(no title)" url = item.get("link") or item.get("url") or "(no url)" snippet = item.get("content") or item.get("snippet") or "(no snippet)" results.append({"title": title, "url": url, "snippet": snippet}) except Exception: logger.exception("Error parsing Tavily result: %s", item) return results