Standard RAG pipelines retrieve context from a vector store, but that context goes stale the moment your documents are indexed. For queries about pricing, recent events, or current rankings, stale context leads to confidently wrong answers. SERP augmentation fixes this by adding a parallel retrieval path: when the query looks time-sensitive, fetch live search results and merge them with vector results before passing to the LLM. This tutorial shows how to add SERP augmentation to any existing RAG pipeline. Each search call costs $0.005 via the Scavio API.
Prerequisites
- Python 3.9+ installed
- An existing RAG pipeline (any vector store)
- requests library installed
- A Scavio API key from scavio.dev
Walkthrough
Step 1: Detect time-sensitive queries
Build a classifier that determines whether a query needs live data. Queries about prices, dates, versions, or current events should trigger SERP augmentation.
import re
TIME_SIGNALS = [
r'\b202[4-9]\b', r'\blatest\b', r'\bcurrent\b', r'\bprice\b',
r'\bpricing\b', r'\btoday\b', r'\brecent\b', r'\bnew\b',
r'\bversion\b', r'\brelease\b', r'\bupdate\b'
]
def needs_live_data(query: str) -> bool:
query_lower = query.lower()
return any(re.search(p, query_lower) for p in TIME_SIGNALS)
# Examples:
for q in ['What is a transformer?', 'Latest Python version 2026', 'Semrush pricing today']:
print(f'{q}: live_data={needs_live_data(q)}')Step 2: Build the SERP retrieval function
Create a retriever that returns documents in the same format as your vector store, so they can be merged seamlessly.
import requests, os
API_KEY = os.environ['SCAVIO_API_KEY']
def serp_retrieve(query: str, k: int = 5) -> list:
resp = requests.post('https://api.scavio.dev/api/v1/search',
headers={'x-api-key': API_KEY, 'Content-Type': 'application/json'},
json={'query': query, 'country_code': 'us'})
resp.raise_for_status()
results = resp.json().get('organic_results', [])[:k]
return [{
'content': f'{r["title"]}\n{r.get("snippet", "")}',
'source': r['link'],
'retriever': 'serp',
'position': r['position']
} for r in results]Step 3: Merge vector and SERP results
Combine results from both retrievers, deduplicating by URL and giving SERP results a freshness boost in the ranking.
def merge_results(vector_docs: list, serp_docs: list) -> list:
seen_urls = set()
merged = []
# SERP results first (fresh data priority)
for doc in serp_docs:
url = doc.get('source', '')
if url not in seen_urls:
seen_urls.add(url)
merged.append(doc)
# Then vector results
for doc in vector_docs:
url = doc.get('source', '')
if url not in seen_urls:
seen_urls.add(url)
merged.append(doc)
return merged[:10] # cap at 10 context docs
# Example:
vector_results = [{'content': 'Old pricing data...', 'source': 'https://example.com/old', 'retriever': 'vector'}]
serp_results = serp_retrieve('Semrush pricing 2026')
merged = merge_results(vector_results, serp_results)
for doc in merged:
print(f'[{doc["retriever"]}] {doc["content"][:60]}...')Step 4: Integrate into your RAG pipeline
Wrap the augmentation logic into your existing retrieval step. Only call the SERP API when the query is time-sensitive to keep costs minimal.
def augmented_retrieve(query: str, vector_store) -> list:
# Always get vector results
vector_docs = vector_store.similarity_search(query, k=5)
vector_formatted = [{'content': d.page_content, 'source': d.metadata.get('source', ''),
'retriever': 'vector'} for d in vector_docs]
# Conditionally add SERP results
if needs_live_data(query):
serp_docs = serp_retrieve(query, k=5)
return merge_results(vector_formatted, serp_docs)
return vector_formatted
# Usage in your chain:
# docs = augmented_retrieve(user_query, my_vector_store)
# context = '\n\n'.join(d['content'] for d in docs)
# answer = llm(f'Context:\n{context}\n\nQuestion: {user_query}')Python Example
import os, re, requests
API_KEY = os.environ['SCAVIO_API_KEY']
TIME_SIGNALS = [r'\b202[4-9]\b', r'\blatest\b', r'\bprice\b', r'\bcurrent\b']
def needs_live_data(query: str) -> bool:
return any(re.search(p, query.lower()) for p in TIME_SIGNALS)
def serp_retrieve(query: str, k: int = 5) -> list:
resp = requests.post('https://api.scavio.dev/api/v1/search',
headers={'x-api-key': API_KEY, 'Content-Type': 'application/json'},
json={'query': query, 'country_code': 'us'})
return [{'content': f'{r["title"]}\n{r.get("snippet", "")}',
'source': r['link'], 'retriever': 'serp'}
for r in resp.json().get('organic_results', [])[:k]]
def augmented_rag(query: str, vector_docs: list) -> list:
if needs_live_data(query):
serp_docs = serp_retrieve(query)
return serp_docs + vector_docs
return vector_docs
query = 'Semrush pricing 2026'
result = augmented_rag(query, [{'content': 'old data', 'retriever': 'vector'}])
for r in result:
print(f'[{r["retriever"]}] {r["content"][:60]}')JavaScript Example
const API_KEY = process.env.SCAVIO_API_KEY;
const TIME_SIGNALS = [/\b202[4-9]\b/i, /\blatest\b/i, /\bprice\b/i, /\bcurrent\b/i];
function needsLiveData(query) {
return TIME_SIGNALS.some(p => p.test(query));
}
async function serpRetrieve(query, k = 5) {
const resp = await fetch('https://api.scavio.dev/api/v1/search', {
method: 'POST',
headers: { 'x-api-key': API_KEY, 'Content-Type': 'application/json' },
body: JSON.stringify({ query, country_code: 'us' })
});
const data = await resp.json();
return (data.organic_results || []).slice(0, k)
.map(r => ({ content: `${r.title}\n${r.snippet || ''}`, source: r.link, retriever: 'serp' }));
}
async function augmentedRag(query, vectorDocs) {
if (needsLiveData(query)) {
const serp = await serpRetrieve(query);
return [...serp, ...vectorDocs];
}
return vectorDocs;
}
augmentedRag('Semrush pricing 2026', [{ content: 'old', retriever: 'vector' }])
.then(docs => docs.forEach(d => console.log(`[${d.retriever}] ${d.content.slice(0, 60)}`)));Expected Output
What is a transformer?: live_data=False
Latest Python version 2026: live_data=True
Semrush pricing today: live_data=True
[serp] Semrush Pricing Plans 2026 - Complete Breakdown...
[serp] Semrush Review: Is the $139.95/mo Pro Plan Worth It?...
[serp] Semrush vs Ahrefs Pricing Comparison (May 2026)...
[vector] old data