Skip to content

feat(web): configurable fallback chain for web_extract - #68524

Open
victory-c wants to merge 4 commits into
NousResearch:mainfrom
victory-c:feat-web-extract-fallback
Open

feat(web): configurable fallback chain for web_extract#68524
victory-c wants to merge 4 commits into
NousResearch:mainfrom
victory-c:feat-web-extract-fallback

Conversation

@victory-c

Copy link
Copy Markdown

This PR implements a fallback chain for web_extract_tool, addressing Issue #68516.

Problem

Previously, web_extract relied on a single backend. If that backend became unavailable (API credits expired, timeouts, rate limits), the extraction failed entirely, requiring manual configuration changes and a gateway restart.

Solution

Users can now configure an array of fallback providers:

web:
  extract_backends:
    - firecrawl
    - direct
    - browser

The dispatcher iterates through these configured backends. If a backend fails or returns an empty response, it logs a warning and seamlessly attempts the next provider in the chain. It only surfaces an error if all configured backends fail.

Legacy backward compatibility is fully preserved: if only web.extract_backend or web.backend is set, the system behaves exactly as it did before.

Closes #68516.

This PR implements a fallback chain for `web_extract_tool`, addressing Issue NousResearch#68516.
Instead of relying on a single `web.extract_backend`, users can now configure an array
under `web.extract_backends`.

The dispatcher will iterate through the configured backends. If a backend fails
(e.g., due to exhausted credits, timeout, or an empty response), it catches the
exception or failure state, logs a warning, and seamlessly attempts the next
provider in the chain. It only surfaces an error if all configured backends fail.

Legacy backward compatibility is fully preserved if only `web.extract_backend`
or `web.backend` is set.

Closes NousResearch#68516
@alt-glitch alt-glitch added type/feature New feature or request P3 Low — cosmetic, nice to have comp/tools Tool registry, model_tools, toolsets tool/web Web search and extraction duplicate This issue or pull request already exists labels Jul 21, 2026
@alt-glitch

Copy link
Copy Markdown
Collaborator

This was generated by AI during triage.

Duplicate of #23366: both PRs implement configurable web_extract provider fallback chains in tools/web_tools.py, including retry after extraction failures. #23366 is the earlier open implementation.

@teknium1 teknium1 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for adapting the fallback idea to the current provider registry. The single-backend premise remains valid on current main (tools/web_tools.py:858-944), but the fallback dispatch needs correction before it can preserve registry behavior.

Problems

  • tools/web_tools.py:872 resolves and availability-filters the chain before plugin discovery at line 874. This can drop a custom fallback in a cold-start process; main already requires discovery before extract registry lookup (tests/tools/test_web_providers.py:350-435).
  • tools/web_tools.py:902 substitutes the scalar active provider for an unregistered configured chain entry. The active resolver only reads web.extract_backend / web.backend (agent/web_search_registry.py:291-298), so this can silently dispatch outside the configured chain.
  • tools/web_tools.py:953 compares names with the final entry instead of tracking position; duplicate entries can stop a chain before a remaining distinct fallback is attempted.

Suggested changes

  • Discover plugins before chain resolution, resolve explicit entries exactly, and deduplicate or index the chain.
  • Add dispatch tests for cold-start custom fallbacks, all-error/empty/exception outcomes, and duplicate entries.

Automated hermes-sweeper review.

Comment thread tools/web_tools.py
# detect coroutine functions and await; sync functions run
# inline (the policy gate, SSRF re-check, etc. live inside the
# provider itself for the firecrawl per-URL loop).
backends = _get_extract_backends()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

_get_extract_backends() filters via _is_backend_available(), which consults the provider registry, but discovery happens on the next line. In a cold-start process, an available built-in primary can cause a custom plugin fallback to be removed before that plugin is registered. Please run _ensure_web_plugins_loaded() before resolving the chain and cover this mixed built-in/custom case.

Comment thread tools/web_tools.py Outdated
provider = get_active_extract_provider()
continue

provider = provider or get_active_extract_provider()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For an explicit fallback list, an unregistered entry should fail/skip and continue to the next configured entry. Falling back to get_active_extract_provider() here reads only the scalar config keys, so it can invoke an unrelated provider that was not in extract_backends.

Comment thread tools/web_tools.py Outdated
continue

all_failed = all(r.get("error") for r in extracted_results)
if all_failed and len(backends) > 1 and backend != backends[-1]:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not a reliable "last attempt" test when the configured list contains duplicates: ["firecrawl", "tavily", "firecrawl"] stops after the first Firecrawl all-error result and never reaches Tavily. Deduplicate during normalization or use the loop index to decide whether entries remain.

@teknium1 teknium1 added sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades sweeper:blast-moderate Sweeper blast radius: moderate — a subsystem or single platform labels Jul 30, 2026
victory-c and others added 2 commits July 30, 2026 11:18
Brings the extract-fallback branch up to date with current main and
resolves the one conflict in tests/tools/test_web_providers.py.

main's "prune wave 2" (3997561) deleted four TestPerCapabilityBackendSelection
methods; this branch had updated those same methods for the
_get_extract_backend -> _get_extract_backends rename, producing a
modify/delete conflict. Resolution honors the prune (drops the four
methods). Feature coverage is retained by the new
tests/tools/test_web_tools_extract_fallback.py and by the surviving
test_fully_backward_compatible_with_web_backend_only (which already
asserts _get_extract_backends() == ["tavily"]).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@victory-c

Copy link
Copy Markdown
Author

Thanks for the review, @teknium1 — all three points are addressed in 6b5e5451 ("harden extract fallback dispatch"), and the branch is now merged up to current main (the tests/tools/test_web_providers.py conflict from the test-prune wave is resolved, so the PR is mergeable again).

1. Discovery before chain resolution. _ensure_web_plugins_loaded() now runs before _get_extract_backends() resolves/filters the chain. On a cold-start process a custom plugin fallback that only registers at discovery time is no longer dropped by the availability filter.

2. Explicit chain entries resolve exactly. The get_active_extract_provider() substitution is removed. An entry that doesn't resolve to a registered extract-capable provider now records a typed error and continues to the next configured backend, so dispatch can never step outside the configured extract_backends onto an unrelated web.extract_backend/web.backend scalar.

3. Duplicate-safe last-attempt check. The "is this the final attempt" test now uses the loop index (idx != len(backends) - 1) instead of comparing names, so a chain like ["firecrawl", "tavily", "firecrawl"] no longer stops after the first Firecrawl all-error result and still reaches Tavily.

New regression coverage in tests/tools/test_web_tools_extract_fallback.py exercises exactly the cases you called out: cold-start discovery ordering, all-error / empty / exception fall-through, and duplicate entries. The web-tools suite is green locally.

Ready for another look whenever you have a chance — thanks again. 🙏

@Darko893

Darko893 commented Aug 2, 2026

Copy link
Copy Markdown

Affiliation: I maintain hermes-plugin-haunt.

I tested this PR at commit 5d823fc8 with the real published hermes-plugin-haunt 0.1.0 entry point:

  • the PR's web provider and fallback tests passed: 31/31;
  • cold entry-point discovery loaded the Haunt provider successfully;
  • extract_backends: [haunt] returned the expected successful extraction;
  • Haunt's typed blocked response was treated as an all-error result and correctly advanced to the next configured provider;
  • the compatibility check passed Ruff and ty.

One setup naming detail for third-party users:

plugins:
  enabled:
    - web-haunt
web:
  extract_backends:
    - haunt
    - <next-provider>

web-haunt is the installable plugin entry-point name, while haunt is the provider name used in the extraction chain.

The HTTP transport was mocked, so the test made no live API calls and consumed no credits. From the third-party provider side, the fallback behaviour is working as intended on this head.

@alt-glitch alt-glitch added needs-decision Awaiting maintainer decision before any implementation duplicate This issue or pull request already exists and removed duplicate This issue or pull request already exists needs-decision Awaiting maintainer decision before any implementation labels Aug 2, 2026
@alt-glitch

Copy link
Copy Markdown
Collaborator

This was generated by AI during triage.

Re-triage correction: this is related to #23366 rather than a duplicate. Both add extract fallback chains, but #68524 uses one ordered web.extract_backends contract whereas #23366 uses a separate extract_fallback_backends contract and different provider resolution behavior. Maintainer selection is needed.

@alt-glitch alt-glitch added area/config Config system, migrations, profiles needs-decision Awaiting maintainer decision before any implementation labels Aug 2, 2026
@alt-glitch alt-glitch added duplicate This issue or pull request already exists and removed needs-decision Awaiting maintainer decision before any implementation labels Aug 4, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/config Config system, migrations, profiles comp/tools Tool registry, model_tools, toolsets duplicate This issue or pull request already exists P3 Low — cosmetic, nice to have sweeper:blast-moderate Sweeper blast radius: moderate — a subsystem or single platform sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades tool/web Web search and extraction type/feature New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Feature Request: Configurable Fallback Chain for web_extract

4 participants