feat: add Tavily as configurable search backend alongside Serper#4
Conversation
There was a problem hiding this comment.
Code Review
This pull request introduces support for Tavily as an alternative search provider alongside Serper. It adds the google_search_with_tavily method, updates configuration files, and registers TAVILY_API_KEY as a sensitive environment variable. The review feedback suggests adding defensive type checks on the Tavily API response and individual result pages to prevent runtime errors, as well as explicitly validating the SEARCH_PROVIDER environment variable to avoid silent fallbacks.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
| results = response.get("results", []) | ||
| if not results: | ||
| return f"No results found for '{query}'. Try with a more general query." |
There was a problem hiding this comment.
Defensively check if the response returned by Tavily is a dictionary before calling .get() on it to avoid potential AttributeError if the API returns an unexpected response type.
| results = response.get("results", []) | |
| if not results: | |
| return f"No results found for '{query}'. Try with a more general query." | |
| if not isinstance(response, dict): | |
| return f"[WebSearch] Tavily returned an invalid response format." | |
| results = response.get("results", []) | |
| if not results: | |
| return f"No results found for '{query}'. Try with a more general query." |
| for idx, page in enumerate(results, start=1): | ||
| title = str(page.get("title", "Untitled result")) | ||
| link = str(page.get("url", "")) | ||
| snippet = f"\n{page['content']}" if page.get("content") else "" | ||
| redacted_version = f"{idx}. [{title}]({link}){snippet}" | ||
| web_snippets.append(redacted_version) |
There was a problem hiding this comment.
Add a type check to ensure each page in results is a dictionary before accessing its keys, similar to the existing check in google_search_with_serp. Also, avoid querying page.get("content") twice by storing the result in a variable.
| for idx, page in enumerate(results, start=1): | |
| title = str(page.get("title", "Untitled result")) | |
| link = str(page.get("url", "")) | |
| snippet = f"\n{page['content']}" if page.get("content") else "" | |
| redacted_version = f"{idx}. [{title}]({link}){snippet}" | |
| web_snippets.append(redacted_version) | |
| for idx, page in enumerate(results, start=1): | |
| if not isinstance(page, dict): | |
| continue | |
| title = str(page.get("title", "Untitled result")) | |
| link = str(page.get("url", "")) | |
| content_val = page.get("content") | |
| snippet = f"\n{content_val}" if content_val else "" | |
| redacted_version = f"{idx}. [{title}]({link}){snippet}" | |
| web_snippets.append(redacted_version) |
| provider = os.getenv("SEARCH_PROVIDER", "serper").strip().lower() | ||
| if provider == "tavily": | ||
| return self.google_search_with_tavily(query.strip()) | ||
| return self.google_search_with_serp(query.strip()) |
There was a problem hiding this comment.
Validate the SEARCH_PROVIDER environment variable. If a user configures an unsupported or misspelled provider, returning an explicit error message is much better than silently falling back to serper.
| provider = os.getenv("SEARCH_PROVIDER", "serper").strip().lower() | |
| if provider == "tavily": | |
| return self.google_search_with_tavily(query.strip()) | |
| return self.google_search_with_serp(query.strip()) | |
| provider = os.getenv("SEARCH_PROVIDER", "serper").strip().lower() | |
| if provider == "tavily": | |
| return self.google_search_with_tavily(query.strip()) | |
| elif provider == "serper": | |
| return self.google_search_with_serp(query.strip()) | |
| else: | |
| return f"[WebSearch] Unknown search provider '{provider}'. Supported providers are 'serper' and 'tavily'." |
Summary
Adds Tavily Search as a configurable alternative to the existing Serper backend in the
WebSearchtool. Both providers coexist — Serper remains the default and is completely untouched.A new
SEARCH_PROVIDERenvironment variable (serper|tavily, defaultserper) controls which backend is used at runtime.Changes
agent_base/tools/tool_web.pygoogle_search_with_tavily()method usingTavilyClient.search()with output formatted to match the existing Serper snippet structurecall()to readSEARCH_PROVIDERenv var and dispatch to the appropriate backendagent_base/tools/tooling.pyTAVILY_API_KEYtoSENSITIVE_ENV_EXACTset so it is masked from subprocess environmentsrequirements.txttavily-python>=0.5.0dependency.env.exampleSEARCH_PROVIDERandTAVILY_API_KEYas optional env varsDependency changes
tavily-python>=0.5.0torequirements.txtEnvironment variable changes
TAVILY_API_KEY— new, optional (required whenSEARCH_PROVIDER=tavily)SEARCH_PROVIDER— new, optional (default:serper; values:serper|tavily)SERPER_KEY— unchangedNotes for reviewers
ScholarSearchis excluded from this migration (no Tavily equivalent for/scholar)Automated Review
client.search()call, and response shape). Existing Serper functionality is entirely preserved with the default unchanged. Dependencies, sensitive-env masking, and.env.exampledocumentation are all properly updated. Three minor issues noted but none are blocking.