Give a Local LLM Live Web Search With Ollama + Scrapeless
Senior Web Scraping Engineer
A model running on your own machine knows nothing past its training cutoff. Ask a local Llama, Qwen, or Mistral checkpoint about a headline from this week and it either declines or, worse, states something plausible and wrong. NIST's Generative AI risk profile has a name for the second failure mode: confabulation, defined as a system that "generate[s] and confidently present[s] erroneous or false content in response to prompts." A model with no path to current data cannot avoid it when the question is about today.
The fix is not a bigger model. It is a search tool the model can call, wired to a search API that returns real results, with the results fed back into the same conversation before the model writes its final answer. This guide wires that pattern end to end: Ollama running a tool-calling model entirely on your own hardware, and Scrapeless Deep SerpApi as the search backend the model reaches for. Every request and response shown below is a real captured run, not a mocked transcript.
What This Integration Enables
A local model with a search tool attached can answer questions its weights alone cannot:
- Current events and prices. A model trained months ago cannot know this quarter's numbers; a live search call can.
- Fact-checking its own recall. The model states an answer, then a search call confirms or corrects it before the final response ships.
- Offline-first agents with one narrow network dependency. Everything — the weights, the reasoning, the tool-selection logic — runs on the local machine. The only outbound call is the search request itself, scoped to exactly the query the model chose to ask.
- Small models punching above their training data. The model in this guide is 0.5 billion parameters. It does not need to know anything about Mars missions; it needs to recognize that the question calls for a search and phrase a reasonable query.
Why Deep SerpApi for the Search Tool
Ollama ships its own hosted search feature (ollama.com/api/web_search), and it's a reasonable default for a quick prototype. It also requires an Ollama account, an OLLAMA_API_KEY, and routes every query through Ollama's own cloud service — the model stays local, but the search step does not stay off Ollama's infrastructure any more than it would with any other hosted search vendor. Its documented default cap is 5 results per call, 10 at most.
Deep SerpApi is a dedicated structured search endpoint: one authenticated POST returns Google's organic results, related searches, pagination, and (depending on the query) video and knowledge-panel data as parsed JSON — not a trimmed summary. Scrapeless's own product page lists coverage across "20+ Google SERP scenarios and mainstream search engines" (Search, News, Maps, Shopping, Trends, and more), response times of "1-2 seconds," and a free tier of "2,000 Free API Calls" with no card required. Paid usage runs "as low as $1.05/1K queries." If a project already depends on Scrapeless for other data-collection work, or needs the fuller organic-result schema rather than a short answer summary, wiring the same account's Deep SerpApi key into the tool-calling loop keeps one provider and one bill instead of two.
Get a free API key by signing up — no card required: app.scrapeless.com.
Prerequisites
- A Linux, macOS, or WSL2 machine with at least 2 GB of free RAM (this guide's model needs well under 1 GB once loaded; a GPU is optional and only speeds up inference).
curland Python 3.9 or newer.- A Scrapeless account and API key from the dashboard's API Key Management page.
- No Ollama account and no
OLLAMA_API_KEY— this path never calls Ollama's hosted service.
Install and Run a Tool-Calling Model Locally
Install Ollama:
bash
curl -fsSL https://raw.githubusercontent.com/ollama/ollama/main/scripts/install.sh | sh
On a systemd-managed Linux host (including WSL2 with systemd enabled) the installer registers and starts an ollama service automatically, listening on 127.0.0.1:11434. Confirm the binary and the service:
bash
ollama --version
# ollama version is 0.31.2
Pull a small tool-calling-capable model. Qwen2.5's instruction-tuned checkpoints support function calling down to the 0.5-billion-parameter size, which keeps the download and the memory footprint small:
bash
ollama pull qwen2.5:0.5b
ollama list afterward confirms the model is local: a 397 MB entry named qwen2.5:0.5b, ready to serve without any further network access.
Not every locally-runnable model supports tool calling — check a model's page on Ollama's library for a "Tools" tag before building an agent loop around it. Larger Qwen2.5, Llama 3.1, and Mistral checkpoints carry the same tag if 0.5B proves too small for a given task.
Get a free Scrapeless API key while the model downloads — no card required: app.scrapeless.com.
Verify the Deep SerpApi Endpoint
Deep SerpApi takes one shape for every scenario: an actor name plus an input object, documented at docs.scrapeless.com. The Google Search scenario uses actor scraper.google.search:
bash
curl -s -X POST "https://api.scrapeless.com/api/v1/scraper/request" \
-H "x-api-token: $SCRAPELESS_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"actor": "scraper.google.search",
"input": {"q": "latest headlines about Mars Sample Return mission", "gl": "us", "hl": "en"}
}'
A live call for that query returns HTTP 200 with a JSON body carrying these top-level fields: organic_results, pagination, related_searches, search_information, inline_videos, video_results, and metadata. Each entry in organic_results carries position, title, link, redirect_link, favicon, snippet, snippet_highlighted_words, and source. The response is plain JSON — no streaming, no session to hold open, one request in and one document out. A request that hasn't finished server-side returns HTTP 201 with a taskId instead; the ordinary case for a Google Search query is the synchronous 200 shown above.
Define and Attach the Search Tool
Ollama's /api/chat endpoint accepts a tools array shaped as JSON Schema function definitions. Define one tool, web_search, and pass it alongside the conversation:
python
TOOLS = [
{
"type": "function",
"function": {
"name": "web_search",
"description": "Search the live web for current information and return the top organic results with titles, links, and snippets.",
"parameters": {
"type": "object",
"properties": {
"query": {"type": "string", "description": "The search query"}
},
"required": ["query"],
},
},
}
]
Attach TOOLS to every /api/chat request so the model always knows the tool exists:
python
import json
import urllib.request
OLLAMA_URL = "http://127.0.0.1:11434/api/chat"
MODEL = "qwen2.5:0.5b"
def ollama_chat(messages):
body = json.dumps({"model": MODEL, "stream": False, "messages": messages, "tools": TOOLS}).encode()
req = urllib.request.Request(OLLAMA_URL, data=body, headers={"Content-Type": "application/json"})
with urllib.request.urlopen(req, timeout=180) as resp:
return json.load(resp)
The model never calls Deep SerpApi itself — it only ever emits a tool_calls entry naming web_search with the arguments it chose. A thin Python function does the actual HTTP work and hands the shaped result back:
python
import os
def web_search(query: str) -> str:
body = json.dumps({
"actor": "scraper.google.search",
"input": {"q": query, "gl": "us", "hl": "en"},
}).encode()
req = urllib.request.Request(
"https://api.scrapeless.com/api/v1/scraper/request",
data=body,
headers={
"x-api-token": os.environ["SCRAPELESS_API_KEY"],
"Content-Type": "application/json",
},
)
with urllib.request.urlopen(req, timeout=60) as resp:
data = json.load(resp)
results = data.get("organic_results", [])[:3]
shaped = [
{"title": r.get("title"), "link": r.get("link"), "snippet": r.get("snippet")}
for r in results
]
return json.dumps(shaped)
Trimming to the top three results before returning the tool output keeps the second /api/chat call small — a 0.5-billion-parameter model has a limited context window, and the full response carries pagination links, favicons, and related-search blocks the model doesn't need to answer the question.
Prompt-Driven Use: Watching the Model Decide
Put the pieces together: send a prompt that needs current information, let the model request the tool, execute that request against Deep SerpApi, and send the result back for a grounded final answer.
python
import json
import os
import urllib.request
OLLAMA_URL = "http://127.0.0.1:11434/api/chat"
SCRAPELESS_URL = "https://api.scrapeless.com/api/v1/scraper/request"
MODEL = "qwen2.5:0.5b"
TOOLS = [
{
"type": "function",
"function": {
"name": "web_search",
"description": "Search the live web for current information and return the top organic results with titles, links, and snippets.",
"parameters": {
"type": "object",
"properties": {
"query": {"type": "string", "description": "The search query"}
},
"required": ["query"],
},
},
}
]
def ollama_chat(messages):
body = json.dumps({"model": MODEL, "stream": False, "messages": messages, "tools": TOOLS}).encode()
req = urllib.request.Request(OLLAMA_URL, data=body, headers={"Content-Type": "application/json"})
with urllib.request.urlopen(req, timeout=180) as resp:
return json.load(resp)
def web_search(query: str) -> str:
body = json.dumps({
"actor": "scraper.google.search",
"input": {"q": query, "gl": "us", "hl": "en"},
}).encode()
req = urllib.request.Request(
SCRAPELESS_URL,
data=body,
headers={"x-api-token": os.environ["SCRAPELESS_API_KEY"], "Content-Type": "application/json"},
)
with urllib.request.urlopen(req, timeout=60) as resp:
data = json.load(resp)
results = data.get("organic_results", [])[:3]
return json.dumps([
{"title": r.get("title"), "link": r.get("link"), "snippet": r.get("snippet")}
for r in results
])
messages = [{"role": "user", "content":
"What are the latest headlines about the Mars Sample Return mission today? "
"Use the search tool if you need current information."}]
first_turn = ollama_chat(messages)
print(json.dumps(first_turn["message"], indent=2))
tool_call = first_turn["message"]["tool_calls"][0]
query = tool_call["function"]["arguments"]["query"]
result = web_search(query)
messages.append(first_turn["message"])
messages.append({"role": "tool", "content": result, "tool_name": "web_search"})
second_turn = ollama_chat(messages)
print(json.dumps(second_turn["message"], indent=2))
A live run against qwen2.5:0.5b prints this for the first turn — no final answer yet, only a tool request:
json
{
"role": "assistant",
"content": "",
"tool_calls": [
{
"id": "call_dwndere1",
"function": {
"index": 0,
"name": "web_search",
"arguments": {
"query": "Mars Sample Return Mission Latest Headlines"
}
}
}
]
}
The model read the prompt, recognized it needed current information, and chose its own search query — this run's phrasing came from the model, not a copy of the user's sentence. The calling code executes web_search(query="Mars Sample Return Mission Latest Headlines") against Deep SerpApi, gets back three shaped results, and appends them as a role: "tool" message before sending the conversation to /api/chat a second time.
What You Get Back
The second print statement is the model's actual answer, grounded in what the search call returned:
json
{
"role": "assistant",
"content": "Here are some of the latest headlines about the Mars Sample Return mission:\n\n- [Mars Sample Return - Mars Missions](https://www.jpl.nasa.gov/missions/mars-sample-return-msr/)\n - This link takes you to NASA's website that contains information about the Mars Sample Return program.\n- [NASA-ESA Mars Sample Return](https://en.wikipedia.org/wiki/NASA-ESA_Mars_Sample_Return)\n - Wikipedia page on the project is also available, which provides more detailed information about the mission and its costs.\n\nHere are some links to articles related to the Mars Sample Return mission:\n\n1. [NASA's Mars Sample Return Is Dead, Paving The Way For China](https://www.universetoday.com/articles/nasas-mars-sample-return-is-dead-paving-the-way-for-china/)\n - This article provides more information about the current status of the Mars Sample Return mission.\n\nI hope this helps! Let me know if you need any other articles or updates."
}
Every link in that answer traces back to one of the three results web_search actually returned — the model summarized and reorganized them into its own list rather than inventing anything new this time. That is not a guarantee: a 0.5-billion-parameter model can still produce a plausible-looking detail that was never in the tool result, even with real search results sitting in its context. Grounding lowers the odds of that happening; it does not eliminate them, and a production system pulling links out of a model's answer should check each one against the tool output before treating it as fact. The same request-and-response pattern holds regardless of query topic: the model decides when to search, the tool call is the only network trip the model's own weights don't own, and the second completion never runs until the tool result is in the conversation.
Conclusion
Wiring a local model to Deep SerpApi took one tool definition, one HTTP function, and two /api/chat calls — the model handles the reasoning about when to search and what to ask, the same way a hosted-model agent framework would, but every token of generation stays on the machine that runs it. The pattern scales past this one example: swap the prompt, swap the model, or extend TOOLS with more functions, and the same request-tool-call-response loop carries the rest.
Start free — no card required: app.scrapeless.com. Full parameter reference at docs.scrapeless.com, and current per-query pricing at scrapeless.com/en/pricing. For the difference between a SERP endpoint like this one and Scrapeless's actors that capture a hosted AI platform's own answers instead, see the SERP-API-versus-LLM-scraper comparison.
FAQ
Q: Which local models support tool calling?
Any model tagged "Tools" on Ollama's model library works with this pattern. Qwen2.5 (down to 0.5B), Llama 3.1 and 3.2, Mistral, and IBM Granite all ship tool-calling-capable checkpoints. A model without that tag may still emit tool_calls-shaped JSON as plain text, which the calling code has to parse manually instead of reading a structured field — check the tag before building around a given model.
Q: Does any of this require an internet connection for the model itself?
No. The model, the prompt, and the reasoning all run on the local machine. The single outbound request is the web_search tool call to Deep SerpApi, scoped to exactly the query the model generated — nothing else about the run touches the network.
Q: Why not use Ollama's built-in web search instead of a separate API key?
Ollama's hosted search (ollama.com/api/web_search) is a legitimate option for a fast prototype, and it needs no separate vendor account beyond Ollama's own. It does require an OLLAMA_API_KEY tied to a free Ollama account, caps results at 10 per call, and returns a generic result list rather than Google's full organic-result schema (positions, related searches, pagination, verticals). Deep SerpApi is the better fit when a project needs that fuller schema, non-Google-Search scenarios, or already runs other work through a Scrapeless account.
Q: What happens if the search call fails?
A malformed request returns HTTP 400; an invalid or missing API key returns an authentication error; a query that hasn't finished server-side returns HTTP 201 with a taskId rather than a result body. Check the status code before assuming organic_results exists in the response, the same way any HTTP client checks a response before parsing its body.
Q: Can I target a country or language other than English?
Yes — gl sets the Google country code and hl sets the interface language on every scraper.google.search request; both are ordinary string fields in the input object, set per call.
Q: Does the model ever call Deep SerpApi directly?
No. The model only ever emits a tool_calls entry describing which function to run and with what arguments — it has no network access of its own. The calling Python code owns the actual HTTP request, which is also what keeps the API key out of the model's context entirely.
Q: Is it safe to point this at scraping search results instead of an API?
Deep SerpApi returns already-parsed Google data over an authenticated endpoint, so there's no robots.txt or rate-limiting concern on the caller's side — that infrastructure work happens on Scrapeless's end. Anyone building the equivalent by scraping Google's results pages directly should read the Robots Exclusion Protocol and the target's own terms first; a managed search endpoint exists specifically to avoid that class of problem.
Q: What's actually happening when the model "decides" to search?
This is the retrieve-then-generate pattern described in the original retrieval-augmented generation research: a model conditions its final output on documents fetched at inference time rather than only on what's baked into its weights. Tool calling is the mechanism modern chat-tuned models use to trigger that fetch themselves, mid-conversation, instead of a fixed retrieval step running before every prompt.
Q: Does grounding fully stop the model from making things up?
No. It reduces confabulation on the specific facts the search result actually covers, but a small model can still misattribute, over-summarize, or add a detail that was not in the tool output. Treat the second turn as a draft informed by real data, not an authoritative quote — for anything load-bearing, compare the model's claims against the organic_results payload it was actually given before trusting them.
At Scrapeless, we only access publicly available data while strictly complying with applicable laws, regulations, and website privacy policies. The content in this blog is for demonstration purposes only and does not involve any illegal or infringing activities. We make no guarantees and disclaim all liability for the use of information from this blog or third-party links. Before engaging in any scraping activities, consult your legal advisor and review the target website's terms of service or obtain the necessary permissions.



