diff --git a/.env.example b/.env.example index 74e03a4..f27dc0a 100644 --- a/.env.example +++ b/.env.example @@ -19,6 +19,7 @@ DATABASE_URL=postgresql://postgres:postgres@localhost:5432/openworkers ANTHROPIC_API_KEY=your_anthropic_api_key_here OPENAI_API_KEY=your_openai_api_key_here DEEPSEEK_API_KEY=your_deepseek_api_key_here +TAVILY_API_KEY=your_tavily_api_key_here # ─── Per-Mode Provider + Model ────────────────────────────────── # Three modes control which provider AND model each agent uses: diff --git a/core/config.py b/core/config.py index 15ead24..b9149d4 100644 --- a/core/config.py +++ b/core/config.py @@ -59,6 +59,7 @@ class Settings(BaseSettings): anthropic_api_key: str = "" openai_api_key: str = "" deepseek_api_key: str = "" + tavily_api_key: str = "" # ── budget guard ──────────────────────────────────────────────────── max_budget_usd: Optional[float] = None diff --git a/pyproject.toml b/pyproject.toml index 35cf4bd..bf65825 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -14,6 +14,7 @@ dependencies = [ "anthropic>=0.25.0", "openai>=1.14.0", "duckduckgo-search>=5.2.0", + "tavily-python>=0.5.0", "pymupdf>=1.24.0", "httpx>=0.27.0", "tenacity>=9.0.0", diff --git a/tools/mcp/engine.py b/tools/mcp/engine.py index 2a275da..ecd7cf4 100644 --- a/tools/mcp/engine.py +++ b/tools/mcp/engine.py @@ -5,7 +5,9 @@ from typing import Any from duckduckgo_search import DDGS +from tavily import TavilyClient +from core.config import get_settings from tools.cache import SearchCache, get_default_cache @@ -93,6 +95,28 @@ async def execute_impl(self, params: dict[str, Any]) -> dict[str, Any]: if not query: return {"results": []} + tavily_api_key = get_settings().tavily_api_key + if tavily_api_key: + try: + return await self._tavily_search(query, tavily_api_key) + except Exception as e: + logging.warning(f"Tavily search failed, falling back to DDGS: {e}") + + return await self._ddgs_search(query) + + async def _tavily_search(self, query: str, api_key: str) -> dict[str, Any]: + def _search(): + client = TavilyClient(api_key=api_key) + return client.search(query=query, max_results=5) + + response = await asyncio.to_thread(_search) + formatted_results = [ + f"{r.get('title', '')} - {r.get('content', '')} ({r.get('url', '')})" + for r in response.get("results", []) + ] + return {"results": formatted_results} + + async def _ddgs_search(self, query: str) -> dict[str, Any]: def _search(): with DDGS() as ddgs: return list(ddgs.text(query, max_results=5))