fix(web): per-capability backend key no longer reroutes the other capability
Fixes #113017. _get_backend() treated any web selection — including per-capability keys (web.search_backend / web.extract_backend) — as a stored shared selection and short-circuited to firecrawl with no ladder. Setting only web.extract_backend therefore silently rerouted web_search off autodetect. The no-ladder firecrawl branch now requires a shared selection (read_selection: use_gateway); per-capability keys name only their own capability, and the other keeps its normal cascade. Test: new tests/tools/test_web_backend_capability_isolation.py — red on base (search resolved to firecrawl), green after fix; managed use_gateway still resolves firecrawl with no ladder.
This commit is contained in:
46
tests/tools/test_web_backend_capability_isolation.py
Normal file
46
tests/tools/test_web_backend_capability_isolation.py
Normal file
@@ -0,0 +1,46 @@
|
||||
"""A per-capability web backend key must not reroute the other capability.
|
||||
|
||||
Regression for #113017: ``web.extract_backend: keenable`` (with no
|
||||
``web.backend``) made ``_get_search_backend()`` resolve to ``firecrawl``
|
||||
through the shared ``_get_backend()`` fallback, because
|
||||
``selection_exists("web")`` counts per-capability keys as a stored selection.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
|
||||
def _ladder_env(monkeypatch, raw_web):
|
||||
from tools import web_tools
|
||||
from tools import tool_backend_helpers as helpers
|
||||
|
||||
monkeypatch.setattr(web_tools, "_load_web_config", lambda: dict(raw_web))
|
||||
monkeypatch.setattr(
|
||||
helpers, "_raw_section",
|
||||
lambda section: dict(raw_web) if section == "web" else None,
|
||||
)
|
||||
# No keyed backends, no gateway: the ladder can only reach ddgs.
|
||||
for var in (
|
||||
"TAVILY_API_KEY", "PERPLEXITY_API_KEY", "EXA_API_KEY",
|
||||
"PARALLEL_API_KEY", "KEENABLE_API_KEY",
|
||||
"FIRECRAWL_API_KEY", "FIRECRAWL_API_URL",
|
||||
"BRAVE_SEARCH_API_KEY", "SEARXNG_URL",
|
||||
):
|
||||
monkeypatch.delenv(var, raising=False)
|
||||
monkeypatch.setattr(web_tools, "_ddgs_package_importable", lambda: True)
|
||||
monkeypatch.setattr(web_tools, "_is_tool_gateway_ready", lambda: False)
|
||||
|
||||
|
||||
def test_extract_only_config_does_not_reroute_search(monkeypatch):
|
||||
from tools import web_tools
|
||||
|
||||
_ladder_env(monkeypatch, {"extract_backend": "keenable"})
|
||||
# Broken before the fix: search resolved to "firecrawl" through the shared fallback.
|
||||
assert web_tools._get_search_backend() == "ddgs"
|
||||
assert web_tools._get_extract_backend() == "keenable"
|
||||
|
||||
|
||||
def test_managed_use_gateway_still_resolves_firecrawl(monkeypatch):
|
||||
from tools import web_tools
|
||||
|
||||
_ladder_env(monkeypatch, {"use_gateway": True})
|
||||
# Ladder would find ddgs if it ran — the managed selection must not reach it.
|
||||
assert web_tools._get_backend() == "firecrawl"
|
||||
@@ -18,7 +18,7 @@ _firecrawl_client = _firecrawl_client_config = _parallel_client = _async_paralle
|
||||
|
||||
from plugins.web.firecrawl.provider import _is_tool_gateway_ready, check_firecrawl_api_key
|
||||
from tools.debug_helpers import DebugSession
|
||||
from tools.tool_backend_helpers import NOUS_MANAGED_PROVIDER, selection_exists
|
||||
from tools.tool_backend_helpers import NOUS_MANAGED_PROVIDER, read_selection, selection_exists
|
||||
from tools.url_safety import async_is_safe_url
|
||||
from tools.web_tools_rescue import _rescue_eligible, _rescue_search
|
||||
from tools.web_tools_truncate import _effective_char_limit, _trim_results, _truncate_results, convert_base64_images_to_links
|
||||
@@ -100,13 +100,15 @@ def _probe(provider, method: str, context: str = "") -> Optional[bool]:
|
||||
def _get_backend() -> str:
|
||||
"""Shared web backend name. A stored ``web.backend`` is returned as-is — no availability probe, no
|
||||
fallback — so a broken selection surfaces the vendor's honest error rather than silently rerouting.
|
||||
Autodetect runs ONLY when no web selection has ever been stored."""
|
||||
The managed ``use_gateway`` selection also resolves to firecrawl with no ladder. Autodetect runs
|
||||
whenever no SHARED web selection was ever stored: per-capability keys (``web.search_backend``,
|
||||
``web.extract_backend``) name only their own capability and never reroute the other (#113017)."""
|
||||
configured = _configured_backend()
|
||||
if configured:
|
||||
# "nous" (managed subscription) is serviced by firecrawl, routed through the managed Tool Gateway.
|
||||
return "firecrawl" if configured == NOUS_MANAGED_PROVIDER else configured
|
||||
if selection_exists("web"):
|
||||
# Selection exists (use_gateway / per-capability keys) but no shared name: firecrawl, no ladder.
|
||||
if read_selection("web") is not None:
|
||||
# Shared selection exists (use_gateway) but no shared name: firecrawl, no ladder.
|
||||
return "firecrawl"
|
||||
|
||||
# Never-configured install. Explicit user credentials beat the managed-gateway probe (a Nous OAuth
|
||||
|
||||
Reference in New Issue
Block a user