If your chat UI has "web search" enabled but the model still makes up a chocolate cake recipe, the toggle has no backend selected and is doing nothing. An LLM can only generate text. It cannot fetch a page. The web-search extension in SillyTavern, Open WebUI, or LibreChat needs an actual backend (Selenium, a SERP API, or visit-by-URL) picked in its dropdown. If that dropdown is empty, the toggle is on but no request ever leaves your machine, and the model fills the gap with whatever sounds right. The fix is to point it at a real search backend that returns results you inject into the prompt.
Prerequisites
- A running chat UI: SillyTavern, Open WebUI, or LibreChat
- A Scavio API key (50 free credits on signup, no card)
- Python 3.9+ or Node 18+ if wiring search as a custom tool
- Basic familiarity with editing your UI's extension/tool settings
Walkthrough
Step 1: Confirm the backend dropdown is actually set
Open your web-search extension settings. SillyTavern's Web Search extension has a "Source" dropdown (Selenium, SerpApi, Visit by URL, etc.). Open WebUI has a Web Search engine selector under Admin > Settings > Web Search. LibreChat reads a search provider from its config. If the selector is blank or set to a backend you never configured, the toggle is a no-op: it never fires a request and the model invents an answer. Pick a real source and give it credentials before doing anything else.
# Symptom of an empty backend: toggle ON, but no outbound request,
# and the model 'answers' from training data instead of live results.
# Check your UI's network log or extension console - if nothing
# is sent when you ask a fresh question, the backend is unset.Step 2: Get a search API that returns clean results
You need a backend that turns a query into real results. A SERP API does this in one call. Scavio's Google endpoint is POST https://api.scavio.dev/api/v1/google, authenticated with a Bearer token. It returns an organic results array you can format into grounding context. One Google call is 1 credit by default; set light_request:false for 2 credits if you also want people-also-ask, knowledge graph, and related searches in the same response.
curl -X POST https://api.scavio.dev/api/v1/google \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-d '{"query": "easy chocolate cake recipe"}'Step 3: Format results into a grounding block
Take the top entries from the results array and flatten each into title, snippet, and link. This becomes a context block you prepend to the user's turn. The model now answers from text you handed it, not from memory. Keep it short: 3 to 5 results is enough to ground most answers without blowing your context window.
Web search results for: easy chocolate cake recipe
[1] One-Bowl Chocolate Cake
Moist cake from flour, sugar, cocoa, baking soda... 35 min at 350F.
https://example.com/one-bowl-cake
[2] Best Moist Chocolate Cake
Hot water thins the batter for a tender crumb...
https://example.com/moist-cake
Use these results to answer. Cite the source number.Step 4: Inject the block and fix the cutoff separately
Send the grounding block as a system message (or prepend it to the user message) right before the model generates. The mid-sentence cutoff is a different bug: it's almost always max_tokens set too low or a streaming connection dropping, not a search problem. Raise the response/output token limit in your UI's generation settings and confirm streaming completes. Search grounding and truncation are two separate switches - fix both.
messages = [
{"role": "system", "content": grounding_block},
{"role": "user", "content": user_query},
]
# In your UI: raise max_tokens / response length so the answer
# doesn't get chopped mid-sentence. That's unrelated to search.Python Example
import os
import requests
API_KEY = os.environ["SCAVIO_API_KEY"]
def search_web(query: str, top_k: int = 4) -> str:
"""Call a SERP API and return a clean grounding block."""
resp = requests.post(
"https://api.scavio.dev/api/v1/google",
headers={"Authorization": f"Bearer {API_KEY}"},
json={"query": query},
timeout=30,
)
resp.raise_for_status()
results = resp.json().get("results", [])[:top_k]
lines = [f"Web search results for: {query}", ""]
for i, r in enumerate(results, 1):
title = r.get("title", "")
snippet = r.get("snippet", "")
link = r.get("link", "")
lines.append(f"[{i}] {title}\n{snippet}\n{link}\n")
lines.append("Use these results to answer. Cite the source number.")
return "\n".join(lines)
def build_messages(user_query: str) -> list:
grounding = search_web(user_query)
return [
{"role": "system", "content": grounding},
{"role": "user", "content": user_query},
]
if __name__ == "__main__":
msgs = build_messages("easy chocolate cake recipe")
print(msgs[0]["content"])
# Pass msgs to your model. The model now answers from real
# results instead of inventing a recipe to fill the silence.JavaScript Example
const API_KEY = process.env.SCAVIO_API_KEY;
async function searchWeb(query, topK = 4) {
const resp = await fetch("https://api.scavio.dev/api/v1/google", {
method: "POST",
headers: {
Authorization: `Bearer ${API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({ query }),
});
if (!resp.ok) throw new Error(`Search failed: ${resp.status}`);
const data = await resp.json();
const results = (data.results || []).slice(0, topK);
const lines = [`Web search results for: ${query}`, ""];
results.forEach((r, i) => {
lines.push(`[${i + 1}] ${r.title || ""}\n${r.snippet || ""}\n${r.link || ""}\n`);
});
lines.push("Use these results to answer. Cite the source number.");
return lines.join("\n");
}
async function buildMessages(userQuery) {
const grounding = await searchWeb(userQuery);
return [
{ role: "system", content: grounding },
{ role: "user", content: userQuery },
];
}
buildMessages("easy chocolate cake recipe").then((msgs) => {
console.log(msgs[0].content);
// Pass msgs to your model - it answers from real results now.
});Expected Output
Web search results for: easy chocolate cake recipe
[1] One-Bowl Chocolate Cake
Moist cake from flour, sugar, cocoa, baking soda. 35 min at 350F.
https://example.com/one-bowl-cake
[2] Best Moist Chocolate Cake
Hot water thins the batter for a tender crumb.
https://example.com/moist-cake
Use these results to answer. Cite the source number.