Google deprecated its free Custom Search Engine JSON API tier in early 2026, breaking thousands of local LLM setups that relied on it for web grounding. If your Ollama-based assistant used CSE for search, you need a drop-in replacement that returns structured JSON, does not require OAuth, and works with a simple API key. The Scavio search endpoint accepts the same query-in, results-out pattern and returns richer data including AI Overviews and People Also Ask. This tutorial migrates your Ollama search integration from CSE to Scavio in under 20 lines of code.
Prerequisites
- Ollama installed and running locally
- Python 3.9+ installed
- requests library installed
- A Scavio API key from scavio.dev
Walkthrough
Step 1: Remove the old CSE integration
Identify and remove the Google CSE client code. The typical CSE pattern uses a cx parameter and googleapis.com endpoint that no longer works.
# OLD CSE code (no longer works as of 2026):
# response = requests.get(
# 'https://www.googleapis.com/customsearch/v1',
# params={'key': CSE_KEY, 'cx': CSE_ID, 'q': query}
# )
# NEW: Scavio replaces the above
import os, requests
SCAVIO_KEY = os.environ['SCAVIO_API_KEY']
H = {'x-api-key': SCAVIO_KEY, 'Content-Type': 'application/json'}
URL = 'https://api.scavio.dev/api/v1/search'Step 2: Build the Scavio search function for Ollama
Create a search function that matches the interface your Ollama tool expects. Return results in a format the LLM can parse.
def web_search(query: str, num_results: int = 5) -> str:
"""Search the web and return formatted results for the LLM."""
resp = requests.post(URL, headers=H,
json={'query': query, 'country_code': 'us', 'num_results': num_results})
resp.raise_for_status()
data = resp.json()
results = data.get('organic_results', [])
formatted = []
for r in results:
formatted.append(f"Title: {r['title']}\nURL: {r['link']}\nSnippet: {r.get('snippet', '')}")
return '\n\n'.join(formatted)Step 3: Wire search into Ollama's tool calling
Register the search function as a tool that Ollama can call during generation. This uses Ollama's Python client with tool support.
import ollama
def run_ollama_with_search(prompt: str, model: str = 'llama3.1') -> str:
tools = [{
'type': 'function',
'function': {
'name': 'web_search',
'description': 'Search the web for current information',
'parameters': {
'type': 'object',
'properties': {
'query': {'type': 'string', 'description': 'Search query'}
},
'required': ['query']
}
}
}]
response = ollama.chat(model=model, messages=[{'role': 'user', 'content': prompt}], tools=tools)
# Handle tool calls
if response.message.tool_calls:
for tc in response.message.tool_calls:
if tc.function.name == 'web_search':
search_results = web_search(tc.function.arguments.get('query', prompt))
# Feed results back to the model
messages = [
{'role': 'user', 'content': prompt},
response.message,
{'role': 'tool', 'content': search_results}
]
final = ollama.chat(model=model, messages=messages)
return final.message.content
return response.message.content
print(run_ollama_with_search('What are the top Python web frameworks in 2026?'))Python Example
import os, requests, ollama
SCAVIO_KEY = os.environ['SCAVIO_API_KEY']
H = {'x-api-key': SCAVIO_KEY, 'Content-Type': 'application/json'}
def web_search(query, num=5):
resp = requests.post('https://api.scavio.dev/api/v1/search', headers=H,
json={'query': query, 'country_code': 'us', 'num_results': num})
results = resp.json().get('organic_results', [])
return '\n'.join(f'{r["title"]}: {r.get("snippet","")}' for r in results)
def ask_with_search(prompt, model='llama3.1'):
tools = [{'type': 'function', 'function': {
'name': 'web_search', 'description': 'Search the web',
'parameters': {'type': 'object', 'properties': {
'query': {'type': 'string'}}, 'required': ['query']}}}]
resp = ollama.chat(model=model, messages=[{'role': 'user', 'content': prompt}], tools=tools)
if resp.message.tool_calls:
results = web_search(resp.message.tool_calls[0].function.arguments.get('query', prompt))
final = ollama.chat(model=model, messages=[
{'role': 'user', 'content': prompt}, resp.message,
{'role': 'tool', 'content': results}])
return final.message.content
return resp.message.content
print(ask_with_search('Latest AI news 2026'))JavaScript Example
const SCAVIO_KEY = process.env.SCAVIO_API_KEY;
async function webSearch(query) {
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 => `${r.title}: ${r.snippet || ''}`).join('\n');
}
async function askOllamaWithSearch(prompt) {
const resp = await fetch('http://localhost:11434/api/chat', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
model: 'llama3.1', messages: [{ role: 'user', content: prompt }],
tools: [{ type: 'function', function: { name: 'web_search',
description: 'Search the web', parameters: { type: 'object',
properties: { query: { type: 'string' } }, required: ['query'] }}}]
})
});
const data = await resp.json();
if (data.message?.tool_calls?.length) {
const results = await webSearch(data.message.tool_calls[0].function.arguments.query);
console.log('Search results:', results);
}
}
askOllamaWithSearch('Latest AI news 2026');Expected Output
Search results for: 'top Python web frameworks 2026'
Title: FastAPI 1.0 Released: The Modern Python Web Framework
URL: https://fastapi.tiangolo.com/release-notes/
Snippet: FastAPI 1.0 brings stable APIs and improved performance...
Title: Django 6.0 Features Overview
URL: https://docs.djangoproject.com/en/6.0/
Snippet: Django 6.0 introduces async-first ORM and built-in AI...
Ollama response: Based on current search results, the top Python web frameworks in 2026 are FastAPI 1.0, Django 6.0, and Litestar...