Skip to content

[Feature]: web_search provider fallback chain (auto-switch on quota/rate-limit errors) #35647

Description

@cathyjay

Summary

Add a provider fallback chain to web_search so that when the primary provider's free quota is exhausted (HTTP 429/402), the tool automatically retries with the next available provider instead of failing entirely.

Problem to solve

Currently web_search binds to a single provider at startup (resolveSearchProvider()) and has no fallback mechanism. When the chosen provider's free quota is exhausted (e.g., Brave's 2,000 requests/month limit), the tool throws an error and the agent loses web search capability entirely for the rest of the billing period.

Many users configure multiple provider API keys (e.g., BRAVE_API_KEY + GEMINI_API_KEY + KIMI_API_KEY) but can only use one at a time. The existing auto-detection logic picks the highest-priority provider with an available key, but never revisits that decision at runtime. Free tiers across providers add up to significant capacity (Brave 2k/mo + Gemini 5k/mo + Kimi free tier), but none of it is utilized when the primary provider is down or rate-limited.

Proposed solution

Configuration: Add an optional providers array for ordered fallback:

{
  tools: {
    web: {
      search: {
        // Ordered fallback chain
        providers: ["brave", "gemini", "kimi"],
        // Existing single-provider config still works (no fallback)
        // provider: "brave",
      },
    },
  },
}

Runtime behavior:

  1. On each web_search call, try the primary provider
  2. If it returns a quota/rate-limit error (429, 402, or known quota error patterns), catch it and retry with the next provider in the chain
  3. If all providers are exhausted, return the last error
  4. Log which provider was used via logVerbose

Schema handling: Use the union of common parameters (query, count, country, language, freshness, date_after, date_before) for the tool schema. Provider-specific params are silently ignored when falling back to a provider that does not support them.

Backward compatibility: Existing single provider: "brave" config continues to work unchanged (no fallback). The providers: [...] array is opt-in. Auto-detection priority order remains the same when no explicit config is set.

Alternatives considered

  1. Manual provider switching: Users can change config when a provider is exhausted, but this requires manual intervention and gateway restart — impractical for automated agents.
  2. Single "best" provider: Just use Gemini (highest free tier) — but this doesn't maximize free capacity across providers, and some users prefer Brave's structured results over Gemini's AI-synthesized format for certain queries.
  3. SearXNG self-hosted: Eliminates quota issues entirely, but requires server infrastructure and is a separate feature request.

Impact

  • Affected users: All users relying on free-tier search API keys, especially hobbyists and small teams
  • Severity: Medium — blocks web search entirely once quota is hit
  • Frequency: Monthly (when free quotas reset), more frequent for active users
  • Consequence: Agent loses web search capability for days/weeks until quota resets; users must manually reconfigure to a different provider

Evidence/examples

  • Current provider resolution: src/agents/tools/web-search.ts resolveSearchProvider() — picks one provider, no retry
  • Error handling: all providers throw on HTTP errors with no catch-and-retry path
  • Precedent in codebase: tools.media.models already supports ordered fallback lists for media understanding
  • Free tier capacities: Brave (2k/mo), Gemini grounding (5k/mo), Tavily (1k/mo), Serper (2.5k one-time) — combined these cover most personal use cases

Additional information

  • Must remain backward-compatible with existing single-provider config
  • Consider exposing a provider field in the tool result (already present) so agents/users can see which provider served each query
  • Long-term: could add Tavily and SearXNG as additional providers to further expand the fallback pool

Metadata

Metadata

Assignees

No one assigned

    Labels

    staleMarked as stale due to inactivity

    Type

    No type

    Fields

    Priority

    None yet

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions