Tutorial

How to Build a Search Fallback Stack for Local LLMs

Build a multi-provider search fallback for local LLMs. Try Scavio first, fall back to Brave, then Tavily. Never lose search grounding due to a single outage.

Relying on a single search provider for your local LLM is a single point of failure. When that provider goes down or hits rate limits, your agent loses web grounding entirely. This tutorial builds a search fallback stack that tries Scavio first ($0.005/query), falls back to Brave Search ($0.005/query), then Tavily ($0.008/credit) if both are unavailable. The stack normalizes responses into a common format so your LLM sees consistent data regardless of which provider responded.

Prerequisites

  • Python 3.9+ installed
  • requests library installed
  • API keys for Scavio, Brave Search, and Tavily
  • A local LLM setup (Ollama, llama.cpp, or vLLM)

Walkthrough

Step 1: Define the provider interface

Create a common interface that each search provider implements. All providers return the same normalized result format.

Python
import os, requests, time
from typing import Optional

SCAVIO_KEY = os.environ.get('SCAVIO_API_KEY', '')
BRAVE_KEY = os.environ.get('BRAVE_API_KEY', '')
TAVILY_KEY = os.environ.get('TAVILY_API_KEY', '')

def normalize_result(title: str, url: str, snippet: str, source: str) -> dict:
    return {'title': title, 'url': url, 'snippet': snippet, 'source': source}

Step 2: Implement each search provider

Write a search function for each provider that returns normalized results. Each function handles its own errors and returns None on failure.

Python
def search_scavio(query: str, num: int = 5) -> Optional[list]:
    try:
        resp = requests.post('https://api.scavio.dev/api/v1/search',
            headers={'x-api-key': SCAVIO_KEY, 'Content-Type': 'application/json'},
            json={'query': query, 'country_code': 'us', 'num_results': num}, timeout=10)
        resp.raise_for_status()
        return [normalize_result(r['title'], r['link'], r.get('snippet', ''), 'scavio')
                for r in resp.json().get('organic_results', [])]
    except Exception as e:
        print(f'Scavio failed: {e}')
        return None

def search_brave(query: str, num: int = 5) -> Optional[list]:
    try:
        resp = requests.get('https://api.search.brave.com/res/v1/web/search',
            headers={'X-Subscription-Token': BRAVE_KEY, 'Accept': 'application/json'},
            params={'q': query, 'count': num}, timeout=10)
        resp.raise_for_status()
        return [normalize_result(r['title'], r['url'], r.get('description', ''), 'brave')
                for r in resp.json().get('web', {}).get('results', [])]
    except Exception as e:
        print(f'Brave failed: {e}')
        return None

def search_tavily(query: str, num: int = 5) -> Optional[list]:
    try:
        resp = requests.post('https://api.tavily.com/search',
            json={'api_key': TAVILY_KEY, 'query': query, 'max_results': num}, timeout=10)
        resp.raise_for_status()
        return [normalize_result(r['title'], r['url'], r.get('content', ''), 'tavily')
                for r in resp.json().get('results', [])]
    except Exception as e:
        print(f'Tavily failed: {e}')
        return None

Step 3: Build the fallback stack

Chain providers in priority order. The first provider to return results wins. Log which provider served the request.

Python
PROVIDERS = [
    ('scavio', search_scavio, 0.005),
    ('brave', search_brave, 0.005),
    ('tavily', search_tavily, 0.008),
]

def search_with_fallback(query: str, num: int = 5) -> dict:
    for name, fn, cost in PROVIDERS:
        results = fn(query, num)
        if results is not None:
            return {'results': results, 'provider': name, 'cost': cost, 'query': query}
    return {'results': [], 'provider': 'none', 'cost': 0, 'query': query}

# Test the fallback
result = search_with_fallback('best python web frameworks 2026')
print(f'Provider: {result["provider"]} (${result["cost"]}/query)')
for r in result['results'][:3]:
    print(f'  {r["title"]}')
    print(f'  {r["url"]}')

Step 4: Integrate with your local LLM

Use the fallback search as a tool for your local LLM. The LLM never needs to know which provider responded.

Python
def format_for_llm(search_result: dict) -> str:
    lines = [f'Web search results for: {search_result["query"]}\n']
    for i, r in enumerate(search_result['results'], 1):
        lines.append(f'[{i}] {r["title"]}')
        lines.append(f'    URL: {r["url"]}')
        lines.append(f'    {r["snippet"]}')
        lines.append('')
    return '\n'.join(lines)

result = search_with_fallback('latest AI frameworks 2026')
formatted = format_for_llm(result)
print(formatted)
print(f'\n--- Served by: {result["provider"]} at ${result["cost"]}/query ---')

Python Example

Python
import os, requests
from typing import Optional

SCAVIO_KEY = os.environ.get('SCAVIO_API_KEY', '')
BRAVE_KEY = os.environ.get('BRAVE_API_KEY', '')

def search_scavio(query, num=5):
    try:
        resp = requests.post('https://api.scavio.dev/api/v1/search',
            headers={'x-api-key': SCAVIO_KEY, 'Content-Type': 'application/json'},
            json={'query': query, 'country_code': 'us', 'num_results': num}, timeout=10)
        resp.raise_for_status()
        return [{'title': r['title'], 'url': r['link'], 'snippet': r.get('snippet', '')}
                for r in resp.json().get('organic_results', [])]
    except: return None

def search_brave(query, num=5):
    try:
        resp = requests.get('https://api.search.brave.com/res/v1/web/search',
            headers={'X-Subscription-Token': BRAVE_KEY},
            params={'q': query, 'count': num}, timeout=10)
        resp.raise_for_status()
        return [{'title': r['title'], 'url': r['url'], 'snippet': r.get('description', '')}
                for r in resp.json().get('web', {}).get('results', [])]
    except: return None

def search(query):
    for name, fn in [('scavio', search_scavio), ('brave', search_brave)]:
        results = fn(query)
        if results:
            print(f'Served by {name}')
            return results
    return []

for r in search('python web frameworks 2026')[:3]:
    print(f'  {r["title"]}')

JavaScript Example

JavaScript
const SCAVIO_KEY = process.env.SCAVIO_API_KEY;
const BRAVE_KEY = process.env.BRAVE_API_KEY;

async function searchScavio(query) {
  try {
    const resp = await fetch('https://api.scavio.dev/api/v1/search', {
      method: 'POST',
      headers: { 'x-api-key': SCAVIO_KEY, 'Content-Type': 'application/json' },
      body: JSON.stringify({ query, country_code: 'us', num_results: 5 })
    });
    const data = await resp.json();
    return (data.organic_results || []).map(r => ({ title: r.title, url: r.link, snippet: r.snippet || '' }));
  } catch { return null; }
}

async function searchBrave(query) {
  try {
    const resp = await fetch(`https://api.search.brave.com/res/v1/web/search?q=${encodeURIComponent(query)}&count=5`, {
      headers: { 'X-Subscription-Token': BRAVE_KEY, Accept: 'application/json' }
    });
    const data = await resp.json();
    return (data.web?.results || []).map(r => ({ title: r.title, url: r.url, snippet: r.description || '' }));
  } catch { return null; }
}

async function search(query) {
  for (const [name, fn] of [['scavio', searchScavio], ['brave', searchBrave]]) {
    const results = await fn(query);
    if (results) { console.log(`Served by ${name}`); return results; }
  }
  return [];
}

search('AI frameworks 2026').then(r => r.slice(0, 3).forEach(x => console.log(x.title)));

Expected Output

JSON
Provider: scavio ($0.005/query)
  FastAPI 1.0: The Modern Python Web Framework
  https://fastapi.tiangolo.com/release-notes/
  Django 6.0 Features and Migration Guide
  https://docs.djangoproject.com/en/6.0/

--- Served by: scavio at $0.005/query ---

Related Tutorials

Frequently Asked Questions

Most developers complete this tutorial in 15 to 30 minutes. You will need a Scavio API key (free tier works) and a working Python or JavaScript environment.

Python 3.9+ installed. requests library installed. API keys for Scavio, Brave Search, and Tavily. A local LLM setup (Ollama, llama.cpp, or vLLM). A Scavio API key gives you 250 free credits per month.

Yes. The free tier includes 250 credits per month, which is more than enough to complete this tutorial and prototype a working solution.

Scavio has a native LangChain package (langchain-scavio), an MCP server, and a plain REST API that works with any HTTP client. This tutorial uses the raw REST API, but you can adapt to your framework of choice.

Start Building

Build a multi-provider search fallback for local LLMs. Try Scavio first, fall back to Brave, then Tavily. Never lose search grounding due to a single outage.