feat(web): multi-source fallback chain and search_engine parameter - #53151
Closed
Icather wants to merge 2 commits into
Closed
feat(web): multi-source fallback chain and search_engine parameter#53151Icather wants to merge 2 commits into
Icather wants to merge 2 commits into
Conversation
…u-baidu, serpapi, jina, google-cse, sogou, 360-search) v0.17 removed the Parallel free MCP fallback (NousResearch#46350), sharply reducing free search options. This adds 9 new providers as plugins following the existing brave-free/ddgs pattern, registered in backend_candidates in composite quality-score order after the existing backends. New functional providers (search-only): - serper — Google SERP, 2,500 free queries, no credit card - baidu — Baidu AI Search, 100/day free, native Chinese content - bocha — Chinese market leader, 1,000 free starter queries - qiniu-baidu — Qiniu Cloud Baidu, 3M tokens for new users - serpapi — Multi-engine, 100/month free - jina — Full-page extraction, 10M tokens free (blocked in China) - google-cse — Google Custom Search, 100/day (blocked in China) Registry-only entries (no public API, listed for completeness): - sogou, 360-search Two prior attempts (NousResearch#41015 Serper by ViezeVingertjes, NousResearch#35690 fallback by jonathanwxh-cell) were self-closed without review. This salages their approach with a complete provider surface covering both Western and Chinese search engines. Changes: - plugins/web/*/ — 27 new files (provider.py, plugin.yaml, __init__.py per backend) - tools/web_tools.py — +9 entries in _KNOWN_WEB_BACKENDS set, +9 in backend_candidates tuple, +_check_provider_available() helper, generic plugin probe in _is_backend_available()
…r for web_search_tool When a search provider fails, Hermes previously returned an error immediately — no retry with another backend. This adds: - search_engine parameter to web_search_tool() — model can specify "baidu", "serper", "ddgs", or "auto" to walk the fallback chain. Valid values are dynamically sourced from list_provider_names(), so adding a new provider automatically extends the enum — zero changes. - web.fallback_backends config key — user-defined ordered list of providers to try. If unset, all registered providers are tried in registration order (paid first, free last). - _search_with_fallback() — walks the chain, skipping backends that are not found, not available, return errors, or return 0 results. Stops at first success. Returns error trace on total failure. This PR depends on feat/add-free-web-search-providers (PR NousResearch#53149) which adds the 9 new providers that this chain can fall back through.
Contributor
There was a problem hiding this comment.
Pull request overview
This PR extends Hermes’ web search tool to support multi-backend operation by introducing an "auto" fallback chain mode and a search_engine parameter intended to let the model (or config) select a specific provider, while also adding several new plugin-based search providers.
Changes:
- Adds fallback-chain logic (
_get_fallback_chain,_search_with_fallback) andsearch_engine="auto"support inweb_search_tool(). - Extends backend availability probing to include plugin-registered providers.
- Introduces new
plugins/web/*provider implementations (Baidu/Bocha/Serper/SerpAPI/Jina/Google CSE/Sogou/360-search/Qiniu-Baidu).
Reviewed changes
Copilot reviewed 28 out of 28 changed files in this pull request and generated 23 comments.
Show a summary per file
| File | Description |
|---|---|
| tools/web_tools.py | Adds fallback-chain search dispatch + generic plugin availability probing, and extends backend lists. |
| plugins/web/sogou/provider.py | Adds an “unavailable” registry-only Sogou provider implementation. |
| plugins/web/sogou/plugin.yaml | Declares the Sogou plugin manifest. |
| plugins/web/sogou/init.py | Adds Sogou plugin package stub (needs register(ctx)). |
| plugins/web/serper/provider.py | Adds Serper provider implementation. |
| plugins/web/serper/plugin.yaml | Declares the Serper plugin manifest. |
| plugins/web/serper/init.py | Adds Serper plugin package stub (needs register(ctx)). |
| plugins/web/serpapi/provider.py | Adds SerpAPI provider implementation. |
| plugins/web/serpapi/plugin.yaml | Declares the SerpAPI plugin manifest. |
| plugins/web/serpapi/init.py | Adds SerpAPI plugin package stub (needs register(ctx)). |
| plugins/web/qiniu-baidu/provider.py | Adds Qiniu-Baidu provider implementation. |
| plugins/web/qiniu-baidu/plugin.yaml | Declares the Qiniu-Baidu plugin manifest. |
| plugins/web/qiniu-baidu/init.py | Adds Qiniu-Baidu plugin package stub (needs register(ctx) and relative import due to hyphenated dir). |
| plugins/web/jina/provider.py | Adds Jina provider implementation. |
| plugins/web/jina/plugin.yaml | Declares the Jina plugin manifest. |
| plugins/web/jina/init.py | Adds Jina plugin package stub (needs register(ctx)). |
| plugins/web/google-cse/provider.py | Adds Google CSE provider implementation. |
| plugins/web/google-cse/plugin.yaml | Declares the Google CSE plugin manifest. |
| plugins/web/google-cse/init.py | Adds Google CSE plugin package stub (needs register(ctx) and relative import due to hyphenated dir). |
| plugins/web/bocha/provider.py | Adds Bocha provider implementation. |
| plugins/web/bocha/plugin.yaml | Declares the Bocha plugin manifest. |
| plugins/web/bocha/init.py | Adds Bocha plugin package stub (needs register(ctx)). |
| plugins/web/baidu/provider.py | Adds Baidu provider implementation. |
| plugins/web/baidu/plugin.yaml | Declares the Baidu plugin manifest. |
| plugins/web/baidu/init.py | Adds Baidu plugin package stub (needs register(ctx)). |
| plugins/web/_360_search/provider.py | Adds an “unavailable” registry-only 360-search provider implementation. |
| plugins/web/_360_search/plugin.yaml | Declares the 360-search plugin manifest. |
| plugins/web/_360_search/init.py | Adds 360-search plugin package stub (needs register(ctx)). |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Comment on lines
+291
to
+295
| try: | ||
| from agent.web_search_registry import list_provider_names | ||
| return set(list_provider_names()) | ||
| except Exception: | ||
| return set() |
Comment on lines
+312
to
+332
| primary = (cfg.get("backend") or "").lower().strip() | ||
| if primary: | ||
| chain.append(primary) | ||
|
|
||
| user_fbs = cfg.get("fallback_backends", []) | ||
| if isinstance(user_fbs, str): | ||
| user_fbs = [b.strip() for b in user_fbs.split(",") if b.strip()] | ||
| for b in user_fbs: | ||
| if b not in chain: | ||
| chain.append(b) | ||
|
|
||
| # Append remaining registered providers (those not already listed). | ||
| # The registry returns providers in registration order, which for | ||
| # bundled plugins is the backend_candidates tuple order. | ||
| try: | ||
| from agent.web_search_registry import list_provider_names | ||
| for name in list_provider_names(): | ||
| if name not in chain: | ||
| chain.append(name) | ||
| except Exception: | ||
| pass |
Comment on lines
+1029
to
+1030
| # Attach fallback trace for observability | ||
| response_data.setdefault("_fallback_trace", errors) |
Comment on lines
+926
to
+933
| def web_search_tool(query: str, limit: int = 5, search_engine: str = "auto") -> str: | ||
| """ | ||
| Search the web for information using available search API backend. | ||
| Search the web for information using available search API backends. | ||
|
|
||
| This function provides a generic interface for web search that can work | ||
| with multiple backends (Parallel or Firecrawl). | ||
| with multiple backends. When ``search_engine`` is ``"auto"`` (default), | ||
| backends from ``web.fallback_backends`` are tried in order until one | ||
| succeeds. Explicit engine names run a single backend with no fallback. |
Comment on lines
+926
to
+933
| def web_search_tool(query: str, limit: int = 5, search_engine: str = "auto") -> str: | ||
| """ | ||
| Search the web for information using available search API backend. | ||
| Search the web for information using available search API backends. | ||
|
|
||
| This function provides a generic interface for web search that can work | ||
| with multiple backends (Parallel or Firecrawl). | ||
| with multiple backends. When ``search_engine`` is ``"auto"`` (default), | ||
| backends from ``web.fallback_backends`` are tried in order until one | ||
| succeeds. Explicit engine names run a single backend with no fallback. |
Comment on lines
+51
to
+52
| except Exception as e: | ||
| return {"success": False, "error": str(e)} |
Comment on lines
+54
to
+55
| except Exception as e: | ||
| return {"success": False, "error": str(e)} |
Comment on lines
+53
to
+54
| except Exception as e: | ||
| return {"success": False, "error": str(e)} |
Comment on lines
+5
to
+11
| import logging | ||
| import os | ||
| from typing import Any, Dict | ||
|
|
||
| from agent.web_search_provider import WebSearchProvider | ||
|
|
||
| logger = logging.getLogger(__name__) |
Comment on lines
+5
to
+11
| import logging | ||
| import os | ||
| from typing import Any, Dict | ||
|
|
||
| from agent.web_search_provider import WebSearchProvider | ||
|
|
||
| logger = logging.getLogger(__name__) |
8 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What does this PR do?
When a search provider fails, Hermes previously returned an error immediately — no retry with another backend. This adds a configurable fallback chain and a
search_engineparameter so the model can pick providers and Hermes retries on failure.Depends on: #53149 (add 9 new providers)
Type of Change
Related Issue
Follows the intent of #35690 (closed without review) and extends it with dynamic engine names from the registry.
Changes Made
web_search_tool(query, limit, search_engine="auto")— newsearch_engineparameter;"auto"walks the fallback chain, explicit names run a single backend_get_fallback_chain()— readsweb.fallback_backendsfrom config, appends auto-detected providers_search_with_fallback()— walks chain, skips unavailable/error/0-results backends, stops at first success_get_valid_engine_names()— dynamically sources valid engine names fromlist_provider_names(), so adding a new provider (PR 1) automatically extends the enumHow to Test
BRAVE_SEARCH_API_KEYandSERPER_API_KEYweb.fallback_backends: [brave-free, serper, ddgs]web_search— should try brave-free first; unset the key to verify fallbackChecklist