Overview
A LangChain retrieval chain that replaces vector store retrieval with live SERP API calls, injecting search results directly into the LLM context window to produce grounded, citation-backed answers.
Trigger
Per user query (synchronous, one chain invocation per question)
Schedule
Per user query (synchronous)
Workflow Steps
Receive user question
Accept the user's question string as chain input. Optionally rewrite the query for search using a query-expansion prompt.
Call SERP API as retriever
POST to Scavio search API with the user's question (or rewritten query). Retrieve top 5 results with title, snippet, and URL.
Format retrieved context
Build a context string from search results: '[Source 1: URL] title - snippet'. Limit to 3,000 characters to stay within token budget.
Construct grounding prompt
Combine system prompt (instruct to cite sources, admit uncertainty), retrieved context, and user question into the LLM input.
Generate grounded answer
Call LLM with the grounding prompt. The model produces an answer citing source URLs from the context.
Return structured response
Return answer text and list of cited source URLs extracted from the response.
Python Implementation
from langchain.schema.runnable import RunnableLambda, RunnablePassthrough
from langchain_core.prompts import ChatPromptTemplate
from langchain_openai import ChatOpenAI
from langchain_core.output_parsers import StrOutputParser
import requests
SCRAVIO_KEY = "YOUR_API_KEY"
def scavio_retriever(query: str) -> str:
"""Fetch search results and format as context string."""
resp = requests.post(
"https://api.scavio.dev/api/v1/search",
headers={"x-api-key": SCRAVIO_KEY},
json={"query": query, "platform": "google", "num": 5}
)
resp.raise_for_status()
results = resp.json().get("results", [])
context_parts = []
for i, r in enumerate(results[:5], start=1):
title = r.get("title", "")
snippet = r.get("snippet", "")
url = r.get("url", "")
context_parts.append(f"[Source {i}: {url}]\n{title}\n{snippet}")
return "\n\n".join(context_parts)[:3000]
SYSTEM_PROMPT = """You are a helpful assistant that answers questions using only the provided search results.
Always cite your sources using [Source N: URL] format.
If the search results don't contain enough information, say so — do not fabricate facts."""
prompt = ChatPromptTemplate.from_messages([
("system", SYSTEM_PROMPT),
("human", "Search results:\n{context}\n\nQuestion: {question}")
])
llm = ChatOpenAI(model="gpt-4o-mini", temperature=0.1)
# Build the chain
chain = (
RunnablePassthrough.assign(
context=RunnableLambda(lambda x: scavio_retriever(x["question"]))
)
| prompt
| llm
| StrOutputParser()
)
def grounded_answer(question: str) -> dict:
answer = chain.invoke({"question": question})
return {"question": question, "answer": answer}
if __name__ == "__main__":
result = grounded_answer("What does Scavio cost per month?")
print(result["answer"])
JavaScript Implementation
const { ChatOpenAI } = require('@langchain/openai');
const { ChatPromptTemplate } = require('@langchain/core/prompts');
const { StringOutputParser } = require('@langchain/core/output_parsers');
const { RunnablePassthrough, RunnableLambda } = require('@langchain/core/runnables');
const fetch = require('node-fetch');
const SCRAVIO_KEY = 'YOUR_API_KEY';
async function scavioRetriever(query) {
const res = await fetch('https://api.scavio.dev/api/v1/search', {
method: 'POST',
headers: { 'x-api-key': SCRAVIO_KEY, 'Content-Type': 'application/json' },
body: JSON.stringify({ query, platform: 'google', num: 5 })
});
const results = (await res.json()).results || [];
return results.slice(0, 5).map((r, i) =>
`[Source ${i+1}: ${r.url}]\n${r.title}\n${r.snippet}`
).join('\n\n').slice(0, 3000);
}
const SYSTEM = 'Answer using only the provided search results. Cite sources as [Source N: URL]. Do not fabricate facts.';
const prompt = ChatPromptTemplate.fromMessages([
['system', SYSTEM],
['human', 'Search results:\n{context}\n\nQuestion: {question}']
]);
const llm = new ChatOpenAI({ model: 'gpt-4o-mini', temperature: 0.1 });
const chain = RunnablePassthrough.assign({
context: new RunnableLambda({ func: async (x) => scavioRetriever(x.question) })
}).pipe(prompt).pipe(llm).pipe(new StringOutputParser());
async function groundedAnswer(question) {
const answer = await chain.invoke({ question });
return { question, answer };
}
groundedAnswer('What does Scavio cost per month?').then(r => console.log(r.answer));
Platforms Used
Web search with knowledge graph, PAA, and AI overviews