diff --git a/.env.example b/.env.example index cd6c51f..2069c94 100644 --- a/.env.example +++ b/.env.example @@ -18,6 +18,10 @@ ANTHROPIC_MODEL_CHAT=claude-3-5-sonnet-latest # --- Google Gemini (optional, used by MCP server) --- GOOGLE_API_KEY= +# --- Search Provider --- +SEARCH_PROVIDER=duckduckgo # duckduckgo | tavily +TAVILY_API_KEY= # Required when SEARCH_PROVIDER=tavily + # --- Storage --- CHROMA_DIR=.chroma SQLITE_PATH=.sqlite/agent.db diff --git a/mcp/server.py b/mcp/server.py index 87a4bf8..4279828 100644 --- a/mcp/server.py +++ b/mcp/server.py @@ -71,8 +71,8 @@ async def summarize(req: SummarizeRequest) -> Dict[str, Any]: @self.app.get("/search") async def search(q: str, max_results: int = 5) -> Dict[str, Any]: - """Perform a web search using DuckDuckGo.""" - results = await webtools.search_ddg(q, max_results=max_results) + """Perform a web search.""" + results = await webtools.search_web(q, max_results=max_results) return {"query": q, "results": results} @self.app.get("/browse") @@ -84,7 +84,7 @@ async def browse(url: str) -> Dict[str, Any]: @self.app.get("/research") async def research(q: str, max_results: int = 3) -> Dict[str, Any]: """Conduct a search and fetch the contents of top results.""" - results = await webtools.search_ddg(q, max_results=max_results) + results = await webtools.search_web(q, max_results=max_results) pages: List[Dict[str, str]] = [] for res in results: url = res.get("href") or res.get("url") diff --git a/mcp/tools/web.py b/mcp/tools/web.py index 5f3ba36..9d6dc3d 100644 --- a/mcp/tools/web.py +++ b/mcp/tools/web.py @@ -1,5 +1,7 @@ from __future__ import annotations +import os + import httpx import trafilatura from bs4 import BeautifulSoup @@ -14,6 +16,33 @@ async def search_ddg(q: str, max_results: int = 5): return [] +async def search_tavily(q: str, max_results: int = 5): + """Search using the Tavily API, returning results normalised to {title, href, body}.""" + from tavily import AsyncTavilyClient + + try: + client = AsyncTavilyClient(api_key=os.environ.get("TAVILY_API_KEY", "")) + response = await client.search(query=q, max_results=max_results) + except Exception: + return [] + results = [] + for r in response.get("results", []): + results.append({ + "title": r.get("title", ""), + "href": r.get("url", ""), + "body": r.get("content", ""), + }) + return results + + +async def search_web(q: str, max_results: int = 5): + """Dispatch to the configured search provider (duckduckgo or tavily).""" + provider = os.environ.get("SEARCH_PROVIDER", "duckduckgo").lower() + if provider == "tavily": + return await search_tavily(q, max_results=max_results) + return await search_ddg(q, max_results=max_results) + + async def fetch_page(url: str) -> str: async with httpx.AsyncClient() as client: resp = await client.get(url, timeout=15) diff --git a/requirements.txt b/requirements.txt index d10d119..2c0f0c6 100644 --- a/requirements.txt +++ b/requirements.txt @@ -17,6 +17,7 @@ typing-extensions>=4.12.2 # Discovery duckduckgo-search>=6.2.10 +tavily-python>=0.3.0 httpx>=0.27.0 trafilatura>=1.10.0 beautifulsoup4>=4.12.3