Overview
Local LLM agents need web search but single-provider setups break when the API is down or rate-limited. This pipeline wraps Scavio as the primary search provider with configurable fallback logic: if the primary call fails or times out, it retries once, then falls back to a cached result or a secondary provider. Designed for on-demand use inside LangChain, LlamaIndex, or raw function-calling agents. Each search costs one credit ($0.005), and the failover adds zero cost unless you configure a paid secondary provider.
Trigger
On search request from local LLM agent
Schedule
On-demand
Workflow Steps
Receive Search Query
Accept the search query and optional parameters (platform, country) from the LLM agent.
Attempt Primary Search
Call Scavio search API with a 10-second timeout. Parse the response and check for valid results.
Retry on Failure
If the primary call fails or returns empty, retry once with exponential backoff.
Check Local Cache
If both attempts fail, check a local SQLite cache for a recent result for the same query.
Return Structured Results
Format the results into a standard schema the LLM agent can parse: title, snippet, URL, source.
Python Implementation
import requests, os, json, time, sqlite3
from pathlib import Path
API_KEY = os.environ["SCAVIO_API_KEY"]
SH = {"x-api-key": API_KEY, "Content-Type": "application/json"}
CACHE_DB = "search_cache.db"
def init_cache():
conn = sqlite3.connect(CACHE_DB)
conn.execute("CREATE TABLE IF NOT EXISTS cache (query TEXT PRIMARY KEY, result TEXT, ts REAL)")
conn.commit()
return conn
def scavio_search(query: str, platform: str = "google", retries: int = 1) -> dict | None:
for attempt in range(retries + 1):
try:
resp = requests.post(
"https://api.scavio.dev/api/v1/search",
headers=SH,
json={"query": query, "platform": platform},
timeout=10,
)
resp.raise_for_status()
data = resp.json()
if data.get("organic"):
return data
except Exception as e:
print(f"Attempt {attempt + 1} failed: {e}")
if attempt < retries:
time.sleep(2 ** attempt)
return None
def cached_search(conn: sqlite3.Connection, query: str, max_age: float = 86400) -> dict | None:
row = conn.execute("SELECT result, ts FROM cache WHERE query = ?", (query,)).fetchone()
if row and (time.time() - row[1]) < max_age:
return json.loads(row[0])
return None
def save_cache(conn: sqlite3.Connection, query: str, result: dict):
conn.execute("INSERT OR REPLACE INTO cache VALUES (?, ?, ?)", (query, json.dumps(result), time.time()))
conn.commit()
def resilient_search(query: str, platform: str = "google") -> list:
conn = init_cache()
result = scavio_search(query, platform, retries=1)
if result:
save_cache(conn, query, result)
else:
result = cached_search(conn, query)
if result:
print("Using cached result")
else:
return []
return [
{"title": r.get("title", ""), "snippet": r.get("snippet", ""), "url": r.get("url", ""), "source": platform}
for r in result.get("organic", [])[:5]
]
results = resilient_search("best python web framework 2026")
for r in results:
print(f" {r['title']} - {r['url']}")JavaScript Implementation
const SH = {'x-api-key': process.env.SCAVIO_API_KEY, 'Content-Type': 'application/json'};
const fs = await import('fs');
const CACHE_FILE = 'search_cache.json';
let cache = {};
try { cache = JSON.parse(fs.readFileSync(CACHE_FILE, 'utf8')); } catch {}
async function scavioSearch(query, platform='google', retries=1) {
for (let attempt = 0; attempt <= retries; attempt++) {
try {
const r = await fetch('https://api.scavio.dev/api/v1/search', {method:'POST', headers:SH, body:JSON.stringify({query, platform}), signal:AbortSignal.timeout(10000)});
const data = await r.json();
if (data.organic && data.organic.length > 0) return data;
} catch (e) {
console.log('Attempt '+(attempt+1)+' failed: '+e.message);
if (attempt < retries) await new Promise(r => setTimeout(r, 2**attempt * 1000));
}
}
return null;
}
function cachedSearch(query, maxAge=86400) {
const entry = cache[query];
if (entry && (Date.now()/1000 - entry.ts) < maxAge) return entry.result;
return null;
}
function saveCache(query, result) {
cache[query] = {result, ts: Date.now()/1000};
fs.writeFileSync(CACHE_FILE, JSON.stringify(cache, null, 2));
}
async function resilientSearch(query, platform='google') {
let result = await scavioSearch(query, platform, 1);
if (result) {
saveCache(query, result);
} else {
result = cachedSearch(query);
if (result) console.log('Using cached result');
else return [];
}
return (result.organic || []).slice(0,5).map(r => ({title:r.title||'', snippet:r.snippet||'', url:r.url||'', source:platform}));
}
const results = await resilientSearch('best python web framework 2026');
results.forEach(r => console.log(' '+r.title+' - '+r.url));Platforms Used
Web search with knowledge graph, PAA, and AI overviews