fix(web): rescue eligibility asks the provider whether the ring was walked
Follow-up to the cherry-picked gateway fix: instead of re-inferring "keyless mode" from the key env var (wrong for Firecrawl, whose managed-gateway and self-hosted routes bypass the ring without a key), `_rescue_eligible` asks the ring vendor's own predicate — `_use_keyless_ring()` for Firecrawl, `use_keyless` for the others. That covers the persisted `nous` selection the contributor fix handled AND the legacy never-configured fallback onto a ready gateway, plus `FIRECRAWL_API_URL`. A ring vendor that actually walked the ring stays ineligible (its failure means the ring already failed). Docs mention the gateway route is rescued.
This commit is contained in:
@@ -91,14 +91,22 @@ class TestEligibility:
|
||||
assert web_tools_rescue._rescue_eligible(KeenableWebSearchProvider()) is False
|
||||
|
||||
def test_gateway_selected_ring_vendor_is_eligible_without_direct_key(self, monkeypatch):
|
||||
# The persisted Nous route uses its subscriber token, not the keyless ring.
|
||||
# The persisted Nous route uses its subscriber token, not the keyless ring — eligible.
|
||||
# The same keyless Firecrawl selected directly DID walk the ring — not eligible.
|
||||
monkeypatch.setattr(
|
||||
"agent.web_search_provider.get_provider_env", lambda name: ""
|
||||
)
|
||||
monkeypatch.setattr("plugins.web.firecrawl.provider._env", lambda name: "")
|
||||
monkeypatch.setattr("plugins.web.firecrawl.provider._is_tool_gateway_ready", lambda: True)
|
||||
monkeypatch.setattr(
|
||||
"tools.tool_backend_helpers.read_selection", lambda kind: "nous"
|
||||
)
|
||||
assert web_tools_rescue._rescue_eligible(_GatewayFirecrawlBoomProvider()) is True
|
||||
monkeypatch.setattr(
|
||||
"tools.tool_backend_helpers.read_selection", lambda kind: "firecrawl"
|
||||
)
|
||||
monkeypatch.setattr("plugins.web.keyless_mcp._web_config_selects", lambda name: name == "firecrawl")
|
||||
assert web_tools_rescue._rescue_eligible(_GatewayFirecrawlBoomProvider()) is False
|
||||
|
||||
def test_non_ring_backend_is_eligible(self):
|
||||
class _SearxProvider(_KeyedBoomProvider):
|
||||
|
||||
@@ -30,27 +30,36 @@ def _keyless_rescue_enabled() -> bool:
|
||||
return False
|
||||
|
||||
|
||||
def _ring_vendor_keyless(name: str) -> bool:
|
||||
"""Did *name*'s own provider route this call through the anonymous keyless ring?
|
||||
|
||||
Mirrors the predicate each ring provider evaluates before calling, so eligibility reflects what
|
||||
actually happened. Firecrawl owns extra routes that bypass the ring without a key — the managed
|
||||
Nous Tool Gateway (persisted ``nous`` selection, or the legacy never-configured fallback when the
|
||||
gateway is ready) and a self-hosted ``FIRECRAWL_API_URL`` — so it is asked directly.
|
||||
"""
|
||||
if name == "firecrawl":
|
||||
from plugins.web.firecrawl.provider import _use_keyless_ring
|
||||
return _use_keyless_ring()
|
||||
from agent.web_search_provider import get_provider_env
|
||||
from plugins.web.keyless_mcp import use_keyless
|
||||
key_var = _RING_KEY_VARS.get(name, "")
|
||||
return use_keyless(name, get_provider_env(key_var) if key_var else "")
|
||||
|
||||
|
||||
def _rescue_eligible(provider) -> bool:
|
||||
"""True when a failed call on *provider* should get a one-shot rescue.
|
||||
|
||||
Eligible: a keyed/configured path — any non-ring backend, a ring vendor in keyed mode, or a ring
|
||||
vendor selected through the persisted Nous Tool Gateway route. A ring vendor selected directly in
|
||||
keyless mode is NOT eligible: its failure means the ring was already walked.
|
||||
Eligible: any call that did NOT go through the keyless ring — a non-ring backend, a ring vendor
|
||||
in keyed mode, or a ring vendor routed through the managed gateway / a self-hosted instance. A
|
||||
ring vendor that walked the ring is NOT eligible: its failure means the ring already failed.
|
||||
"""
|
||||
if not _keyless_rescue_enabled() or provider is None:
|
||||
return False
|
||||
try:
|
||||
from plugins.web.keyless_mcp import _KEYLESS_RING, use_keyless
|
||||
from plugins.web.keyless_mcp import _KEYLESS_RING
|
||||
name = getattr(provider, "name", "")
|
||||
if name not in _KEYLESS_RING:
|
||||
return True
|
||||
from tools.tool_backend_helpers import NOUS_MANAGED_PROVIDER, read_selection
|
||||
if read_selection("web") == NOUS_MANAGED_PROVIDER:
|
||||
# The gateway uses the subscriber token, so this call has not walked the anonymous ring.
|
||||
return True
|
||||
from agent.web_search_provider import get_provider_env
|
||||
key_var = _RING_KEY_VARS.get(name, "")
|
||||
return not use_keyless(name, get_provider_env(key_var) if key_var else "")
|
||||
return name not in _KEYLESS_RING or not _ring_vendor_keyless(name)
|
||||
except Exception as exc: # noqa: BLE001 — rescue is best-effort
|
||||
logger.debug("rescue eligibility check failed: %s", exc)
|
||||
return False
|
||||
|
||||
@@ -425,7 +425,7 @@ If no backend has **ever** been selected (no `web.backend` / per-capability key
|
||||
|
||||
**Keyless free-tier ring:** when *no* credential above is present, requests rotate across the ring vendors' public free tiers (Exa, Parallel, Firecrawl, Keenable) so web tools work on a fresh install with zero setup — and a rate-limited request fails over to the next vendor in the ring automatically. Pin one vendor in `hermes tools` to stop the rotation (the ring is then only used as failover succession on throttles). All free tiers are vendor-rate-limited under burst load; sustained normal usage goes through fine. Set `web.keyless_fallback: false` to turn the tier off — with it off and no credentials, web tools are unavailable until a provider is configured.
|
||||
|
||||
**One-shot keyless rescue for keyed backends:** when your chosen/keyed backend fails a call (bad key, outage, upstream 5xx), that single call automatically retries on the keyless free-tier ring instead of erroring — the result notes which vendor served it and why (`rescued_from` / `backend_error`). The failover is never sticky: the very next `web_search`/`web_extract` call attempts your chosen backend again. Disable with `web.keyless_rescue: false` (also off whenever `keyless_fallback` is off).
|
||||
**One-shot keyless rescue for keyed backends:** when your chosen/keyed backend — including the Nous Tool Gateway route (`web.backend: nous`) — fails a call (bad key, outage, unreachable gateway, upstream 5xx), that single call automatically retries on the keyless free-tier ring instead of erroring — the result notes which vendor served it and why (`rescued_from` / `backend_error`). The failover is never sticky: the very next `web_search`/`web_extract` call attempts your chosen backend again. Disable with `web.keyless_rescue: false` (also off whenever `keyless_fallback` is off).
|
||||
|
||||
xAI Web Search is **not** in the auto-detection chain — having `XAI_API_KEY` set (or being signed in via xAI Grok OAuth) does not automatically route web traffic through xAI, since those credentials are also used for inference / TTS / image gen and the user may want a different backend for web. Opt in explicitly with `web.backend: "xai"`.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user