fix(web): light up web tools when a registered custom provider is available but none configured - #57808
Conversation
There was a problem hiding this comment.
Pull request overview
This PR fixes a regression in the web tools availability gate (check_web_api_key) where web_search / web_extract could be hidden when multiple plugin-registered custom providers were available but no web.backend was configured, even though the router (_get_backend) could still successfully resolve a provider.
Changes:
- Update
check_web_api_key()to mirror_get_backend()’s “registered custom providers” fallback walk so tool gating and routing agree. - Add a regression test covering the “2+ available custom providers, no config key” scenario.
- Ensure the test asserts both the gate result and that
_get_backend()resolves one of the custom providers.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
| tools/web_tools.py | Aligns tool availability gating with router fallback behavior when multiple custom providers are registered. |
| tests/tools/test_web_tools_config.py | Adds regression coverage for multiple custom providers with no configured backend. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| for provider in _list_registered_web_providers(): | ||
| if provider.name in _LEGACY_WEB_BACKENDS: | ||
| continue | ||
| try: | ||
| if provider.is_available(): | ||
| return True | ||
| except Exception as exc: # noqa: BLE001 — a broken provider is skipped | ||
| logger.debug( | ||
| "web provider %r.is_available() raised: %s", provider.name, exc | ||
| ) |
Related: follow-up to the just-merged #57779. That refactor threaded the registry through |
|
@copilot Addressed in 3a914fe0. The custom-provider fallback walk in |
fba9d8d to
3a914fe
Compare
|
Thanks for identifying a real current-main regression: Problems
Suggested changes
Automated hermes-sweeper review. |
…ilable but none configured check_web_api_key() gates whether web_search / web_extract appear in the toolset. After the a9cd0e0 refactor it delegated its plugin-provider probe to the registry's single-active get_active_search_provider() / get_active_extract_provider(), i.e. _resolve(None, ...). With no web.backend config key, _resolve only returns a provider when exactly one is eligible (len(eligible) == 1) or the name matches the legacy preference order; custom plugin names never match. So with 2+ registered custom providers, all available and capable, and no config key, both resolvers return None and the gate returns False — hiding the web tools entirely. Meanwhile _get_backend() has a final fallback that walks _list_registered_web_providers() and returns the first available non-legacy provider, so it DOES resolve one of them. Gate and router diverge: the tools are filtered out even though the router would happily dispatch to a custom provider. Mirror _get_backend()'s final registered-provider walk in the gate so the two agree. Works for the single-custom-provider case (already covered); this closes the 2+ case.
…lity helper The custom-provider fallback walk in check_web_api_key() duplicated the registry is_available() try/except/log already in _registered_web_provider_available(). Route the loop through that helper so broken-provider handling stays identical between backend selection and tool gating, with a single source of truth. Behavior is unchanged: legacy names are still skipped and a provider whose is_available() raises is treated as unavailable.
3a914fe to
2ec6db4
Compare
|
Re-verified against current main — this no longer reproduces; the behaviour is already correct on main (see |
What does this PR do?
check_web_api_key()is thecheck_fngate that decides whetherweb_searchandweb_extractappear in the toolset. Today's refactor (a9cd0e07c, "single registry authority for custom-provider availability") changed the plugin-provider branch of that gate to delegate to the registry's single-active resolvers:get_active_*_provider()calls_resolve(None, capability=...)(no config key). Withexplicit=None,_resolveonly returns a provider when exactly one is eligible (len(eligible) == 1) or the name matches the_LEGACY_PREFERENCEorder — and custom plugin names never appear in that legacy order. So when 2+ registered custom providers are available and capable and noweb.backendconfig key is set,_resolvereturnsNonefor both capabilities,check_web_api_key()returnsFalse, andweb_search/web_extractare filtered out of the toolset entirely.Meanwhile
_get_backend()(the router) has a final fallback loop that walks_list_registered_web_providers()and returns the first available non-legacy provider — so it does resolve one of them. The gate and the router now diverge: the tools are hidden even though the router would dispatch to a custom provider without issue. This is exactly the "second resolution path that could diverge" the refactor set out to remove, reintroduced in the opposite direction.check_web_api_key's own docstring states the intended contract: a plugin-registered provider that reportsis_available()must light the tools up even when no built-in backend has credentials (issues #28651, #31873).The fix mirrors
_get_backend()'s final registered-provider walk in the gate so the two agree. The single-custom-provider case already worked (and is covered by the test landed with the refactor); this closes the 2+ case.Related Issue
Code-originated regression from
a9cd0e07c— no separate issue filed.Type of Change
Changes Made
tools/web_tools.py— incheck_web_api_key(), replace theget_active_search_provider()/get_active_extract_provider()delegation tail with the same registered-provider walk_get_backend()uses (skip_LEGACY_WEB_BACKENDS; returnTrueon the firstprovider.is_available()), so the gate and the router agree.tests/tools/test_web_tools_config.py— add a regression test registering two available custom providers with no config key, assertingcheck_web_api_key()isTrueand_get_backend()resolves one of them.How to Test
WebSearchProviders (e.g.custom-a,custom-b), bothis_available() == True, both search/extract-capable.web.backendconfig key.check_web_api_key()returnsFalse(tools hidden) while_get_backend()returns one of the two names — gate and router disagree.check_web_api_key()returnsTrue, matching_get_backend().Regression test
test_check_web_api_key_true_for_multiple_custom_providers_none_configuredfails before / passes after. Full touched-area suite (tests/tools/test_web_tools_config.py,test_web_providers.py,test_web_tools_tavily.py,test_web_tools_truncate.py) passes: 96 passed.Checklist
Code
fix(scope):,feat(scope):, etc.)pytest tests/ -qand all tests passDocumentation & Housekeeping
docs/, docstrings) — or N/Acli-config.yaml.exampleif I added/changed config keys — or N/ACONTRIBUTING.mdorAGENTS.mdif I changed architecture or workflows — or N/A