From a246cab5ea434c96ad4b41192e4914ef84476943 Mon Sep 17 00:00:00 2001 From: Tavily PR Agent Date: Sun, 22 Mar 2026 22:47:37 +0000 Subject: [PATCH 1/2] feat: add Tavily as configurable search provider alongside DuckDuckGo --- config/config.yaml.example | 4 ++++ env.example | 4 +++- requirements.txt | 1 + tools/web_search.py | 31 +++++++++++++++++++++++++++++-- 4 files changed, 37 insertions(+), 3 deletions(-) diff --git a/config/config.yaml.example b/config/config.yaml.example index 5766c15..4ddbf39 100644 --- a/config/config.yaml.example +++ b/config/config.yaml.example @@ -40,6 +40,10 @@ email: # ssl: true # enabled: true +search: + provider: "duckduckgo" # duckduckgo or tavily + tavily_api_key: "" # Required when provider is tavily + servers: - name: "Local System" type: "local" diff --git a/env.example b/env.example index 0b3d562..8a403fd 100644 --- a/env.example +++ b/env.example @@ -1,4 +1,6 @@ TELEGRAM_BOT_TOKEN= TELEGRAM_CHAT_ID= GBYTE_ERP_URL="" -API_KEY=secret-agent-key \ No newline at end of file +API_KEY=secret-agent-key +SEARCH_PROVIDER=duckduckgo # duckduckgo or tavily +TAVILY_API_KEY= # Required when SEARCH_PROVIDER=tavily \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index a1dec54..21a9c1a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -27,6 +27,7 @@ pyyaml dateparser pytz ddgs +tavily-python youtube-transcript-api python-dateutil playwright diff --git a/tools/web_search.py b/tools/web_search.py index 94737ec..7a37967 100644 --- a/tools/web_search.py +++ b/tools/web_search.py @@ -2,19 +2,20 @@ Web Search Tool — Search the web and summarize content. """ import logging +import os import re from langchain_core.tools import tool import config as app_config -def perform_web_search(query): +def perform_duckduckgo_search(query): """Performs a DuckDuckGo search.""" from ddgs import DDGS try: results = DDGS().text(query, max_results=5) if not results: return "No results found." - + summary = "" for r in results: summary += f"- [{r['title']}]({r['href']}): {r['body']}\n" @@ -23,6 +24,32 @@ def perform_web_search(query): return f"Error performing search: {e}" +def perform_tavily_search(query): + """Performs a Tavily search.""" + from tavily import TavilyClient + try: + client = TavilyClient() + response = client.search(query=query, max_results=5) + results = response.get("results", []) + if not results: + return "No results found." + + summary = "" + for r in results: + summary += f"- [{r['title']}]({r['url']}): {r.get('content', '')}\n" + return summary + except Exception as e: + return f"Error performing search: {e}" + + +def perform_web_search(query): + """Dispatches search to the configured provider (duckduckgo or tavily).""" + provider = os.environ.get("SEARCH_PROVIDER", "duckduckgo").lower() + if provider == "tavily": + return perform_tavily_search(query) + return perform_duckduckgo_search(query) + + def get_youtube_video_id(url): """Extracts YouTube video ID from URL.""" patterns = [ From 88dbb407025d5e2b8932f5e8f41739c2bd7dbe66 Mon Sep 17 00:00:00 2001 From: Tavily PR Agent Date: Sun, 22 Mar 2026 22:50:20 +0000 Subject: [PATCH 2/2] =?UTF-8?q?fix:=20address=20review=20feedback=20(attem?= =?UTF-8?q?pt=202)=20=E2=80=94=20Add=20Tavily=20as=20configurable=20search?= =?UTF-8?q?=20provider=20in=20tools/web=5Fsearch.py?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config/__init__.py | 9 +++++++-- env.example | 2 +- tools/web_search.py | 9 ++++++++- 3 files changed, 16 insertions(+), 4 deletions(-) diff --git a/config/__init__.py b/config/__init__.py index 2dcc824..4962922 100644 --- a/config/__init__.py +++ b/config/__init__.py @@ -53,10 +53,15 @@ def load_config(): }) # Default values if missing - + # Default values if missing if 'ollama' not in config: config['ollama'] = {'model': 'llama3'} - + + # Search config + if 'search' not in config: config['search'] = {} + config['search']['provider'] = os.getenv('SEARCH_PROVIDER', config['search'].get('provider', 'duckduckgo')) + config['search']['tavily_api_key'] = os.getenv('TAVILY_API_KEY', config['search'].get('tavily_api_key', '')) + return config except FileNotFoundError: logging.error(f"Configuration file {CONFIG_FILE} not found.") diff --git a/env.example b/env.example index 8a403fd..333d03b 100644 --- a/env.example +++ b/env.example @@ -3,4 +3,4 @@ TELEGRAM_CHAT_ID= GBYTE_ERP_URL="" API_KEY=secret-agent-key SEARCH_PROVIDER=duckduckgo # duckduckgo or tavily -TAVILY_API_KEY= # Required when SEARCH_PROVIDER=tavily \ No newline at end of file +TAVILY_API_KEY= # Required when SEARCH_PROVIDER=tavily diff --git a/tools/web_search.py b/tools/web_search.py index 7a37967..7cf2621 100644 --- a/tools/web_search.py +++ b/tools/web_search.py @@ -44,8 +44,15 @@ def perform_tavily_search(query): def perform_web_search(query): """Dispatches search to the configured provider (duckduckgo or tavily).""" - provider = os.environ.get("SEARCH_PROVIDER", "duckduckgo").lower() + conf = app_config.load_config() + provider = (conf.get("search", {}).get("provider", "duckduckgo") if conf else os.environ.get("SEARCH_PROVIDER", "duckduckgo")).lower() + if provider not in ("duckduckgo", "tavily"): + logging.warning(f'Unknown SEARCH_PROVIDER "{provider}", falling back to duckduckgo') + provider = "duckduckgo" if provider == "tavily": + tavily_key = conf.get("search", {}).get("tavily_api_key", "") if conf else "" + if tavily_key: + os.environ.setdefault("TAVILY_API_KEY", tavily_key) return perform_tavily_search(query) return perform_duckduckgo_search(query)