I asked a Qwen model, running locally, for the latest version of Next.js. It answered 15. The latest release was 16.4.6 (as of November 2026). When I asked how it determined this answer, it admitted that it can't search the web and answers from its training data.

Qwen conversation: with vs without search

A local model is limited to its training data. This post shows how Qwen can use web search, with multiple search engines such as Google, Baidu and Naver, and compares the performance of different models.

Qwen offers open-weight local models, Qwen Code and the Qwen-Agent framework. All of them can be given access to web search.

Qwen local model

Open-weight models such as qwen3.8:27b, qwen3.5:9b or qwen3-coder running on your own device through Ollama, vLLM or LM Studio.

  • Built-in search: Not available. The model can request search through a tool call, but it can't run one on its own.
  • Add web search: Through tool calling in your application, or by connecting an MCP server through an MCP-capable client.

Qwen Code

A terminal coding agent that works with Qwen models and other providers through compatible APIs, including local models served through Ollama.

  • Built-in search: Not available for local models or third-party providers such as OpenRouter and DeepSeek.
  • Add web search: Through MCP servers such as SerpApi MCP.

Qwen-Agent

A framework for building LLM applications and agents using Qwen models, with built-in support for function calling, MCP, a code interpreter and RAG.

  • Built-in search: Yes. web_search and web_extractor tools are present.
  • Add web search: Add a custom tool or connect an MCP server.

This post covers the Qwen local model with tool calling. I'll cover connecting Qwen Code and Qwen-Agent with SerpApi's MCP server in a follow-up post.

How do Qwen and SerpApi work together?

Local models can use tools to perform actions not natively available to them. We can provide web search as a tool to our Qwen model, and the model uses it when necessary to answer a question. Below is a flow diagram of how this works.

Local Qwen + SerpApi flow diagram

Suppose we ask the model about the latest PHP critical vulnerability. Below is the sequence of actions performed.

  1. Our question, along with the prompt and the available tools, is sent to Qwen.
  2. Qwen understands that the latest vulnerabilities won't be in its training data, so it requests a web search.
  3. A real-time web search is performed through SerpApi, and the results are returned in token-efficient markdown format.
  4. Qwen checks whether the search results are enough to answer the question. If not, it requests another search.
  5. Once Qwen has enough information, it answers the question.

How to set this up?

Let's begin with what's required to build this.

  • Ollama to run Qwen locally on your machine. vLLM and LM Studio should also work. Only base_url in the sample code changes.
  • Qwen model with tool support that fits your machine (see Step 1).
  • SerpApi API key: Get it here. Free plan includes 250 searches per month.

Note: The commands below are for macOS and Linux.

Step 1: Pick a Qwen model and start Ollama

Choose a model based on the available memory on your device. A model needs memory for its weights (download size) plus additional memory for the context (conversation and search results).

ModelDownload sizeMemory needed
qwen3.5:4b3.4 GB6 GB
qwen3.5:9b6.6 GB8 GB
qwen3.8:27b18 GB24 GB
qwen3-coder:30b19 GB24 GB

Any Qwen model with the tools tag on Ollama works. Download your pick using the command below. For example, for qwen3.8:27b:

ollama pull qwen3.8:27b

Download Qwen from Ollama

To keep memory in check, start Ollama with a 32k context and an 8-bit KV cache. This allows Qwen to perform several rounds of searches. Keep this terminal tab open.

OLLAMA_CONTEXT_LENGTH=32768 OLLAMA_FLASH_ATTENTION=1 OLLAMA_KV_CACHE_TYPE=q8_0 ollama serve

Start Ollama with settings for low memory usage

Step 2: Set up Python environment

In a new terminal, create a project, install three packages and add your API key.

mkdir qwen-search && cd qwen-search
python3 -m venv .venv && source .venv/bin/activate
pip install openai serpapi python-dotenv
echo "SERPAPI_API_KEY=your_api_key" > .env

Replace your_api_key with the key from your SerpApi dashboard.

Step 3: Add the code

Create qwen_search.py in the same folder and paste the code below. The script uses qwen3.8:27b by default. If you pulled a different model in Step 1, add LOCAL_MODEL=<model name> to your .env file.

import json, os
from datetime import date
import serpapi
from dotenv import load_dotenv
from openai import OpenAI

load_dotenv()  # reads SERPAPI_API_KEY from .env
serpapi_client = serpapi.Client(api_key=os.environ["SERPAPI_API_KEY"], timeout=60)
qwen = OpenAI(base_url="http://localhost:11434/v1", api_key="ollama")  # Ollama's OpenAI-compatible API
MODEL = os.environ.get("LOCAL_MODEL", "qwen3.8:27b")  # set LOCAL_MODEL in .env to use another model
MAX_ROUNDS = 4  # calls to Qwen per question; the last one must answer

def serpapi_search(query):
    """Searches Google with SerpApi and returns the top 5 results as Markdown."""
    return serpapi_client.search(
        engine="google",
        q=query,
        json_restrictor="answer_box,organic_results[0:5].{title,link,snippet,date}",
        output="md",
    )

SYSTEM = f"""You are a helpful assistant with access to web search. Today's date is {date.today()}.
- Search when the answer depends on current or changing information, such as news, releases, prices or who holds a role. Answer directly when it can't have changed.
- Base your answer on the search results. If they don't answer the question, say so instead of guessing.
- Cite the sources you used as Markdown links.
- Reply in the user's language, and keep it concise."""
LAST_ROUND = "That was your last search. Answer now with the results you have."

TOOLS = [{"type": "function", "function": {
    "name": "serpapi_search",
    "description": "Web search for current information. Returns the top 5 results with titles, links, snippets and dates.",
    "parameters": {
        "type": "object",
        "properties": {"query": {"type": "string", "description": "A short, specific search query."}},
        "required": ["query"],
    },
}}]

def ask(question):
    """Lets Qwen search until it can answer, in at most MAX_ROUNDS calls."""
    messages = [{"role": "system", "content": SYSTEM}, {"role": "user", "content": question}]
    for round_number in range(1, MAX_ROUNDS + 1):
        tools = TOOLS
        if round_number == MAX_ROUNDS:
            tools = []
            messages.append({"role": "user", "content": LAST_ROUND})
        reply = qwen.chat.completions.create(model=MODEL, messages=messages, tools=tools).choices[0].message
        if not reply.tool_calls:
            return reply.content
        messages.append(reply.model_dump(exclude_none=True))
        for call in reply.tool_calls:
            query = json.loads(call.function.arguments)["query"]
            print(f"  [search] {query}")
            messages.append({"role": "tool", "tool_call_id": call.id, "content": serpapi_search(query)})

if __name__ == "__main__":
    while True:
        print("\nQwen:", ask(input("\nYou: ")))

Code: Qwen-SerpApi through tool calling

The code above has three parts:

  • serpapi_search: Runs the web search through SerpApi. json_restrictor keeps the top 5 results and the fields we need. output=md returns search results as Markdown, which uses fewer tokens than JSON.
  • SYSTEM prompt and TOOLS definition: TOOLS describes the search tool and its arguments, so Qwen knows how to call it. SYSTEM gives today's date, tells Qwen when to search and how to answer: from the search results, with cited sources.
  • ask() loop: Qwen can call the tool multiple times with different queries, including parallel searches in one round. It answers once it no longer needs the tool or reaches the MAX_ROUNDS limit, where the tools are taken away so it has to answer.

Step 4: Run

python3 qwen_search.py

Here's how Qwen used SerpApi and answered a question about the latest PHP vulnerabilities.

You: What is the latest PHP critical vulnerability and which version fixes it?
  [search] latest PHP critical vulnerability 2026
  [search] PHP CVE-2026-109618 fix version number required
  [search] PHP PHP.net CVE-2026-109618 fix minimum version number

Qwen: The latest critical PHP vulnerabilities discovered in October 2026 are **CVE-2026-109618** and **CVE-2026-109619**, collectively known as **"PHP2Shell"** - two remote code execution (RCE) vulnerabilities in the PHP core interpreter.

### Fix Versions:
| PHP Major Version | Fixed Version |
|------------------|---------------|
| 8.5 | **8.5.12** |
| 8.4 | **8.4.27** |
| 8.3 | **8.3.36** |
| 8.2 | **8.2.35** |

### Important Notes:
*   Older versions (PHP 7.3 and below, including PHP 5) have no fix available [1][2]. Many distributions backport security patches without changing the version number in their package manager [1].

For complete remediation, update your PHP packages to at least **8.5.12** (or equivalent for your major version) and restart all PHP processes.

Sources:
*   [CERT-EU Security Advisory 2026-018](https://cert.europa.eu/publications/security-advisories/2026-018)
*   [CloudLinux Blog - PHP CVE Patch Details](https://blog.cloudlinux.com/php-cve-2026-109618-and-cve-2026-109619)

Run on 11 Nov 2026

A "[search]" line means Qwen called the tool and your code ran a SerpApi search. Press Ctrl+C to quit.

If something fails

Below are some possible errors and how to fix them.

ErrorFix
address already in use when you start Ollama Ollama is already running. Quit the Ollama app, then start it again with the command in Step 1.
openai.APIConnectionError: Connection error Ollama isn't running. Start it as in Step 1.
model "qwen3.8:27b" not found Pull the model with ollama pull, or set LOCAL_MODEL in .env to a name from ollama list.
KeyError: 'SERPAPI_API_KEY' The .env file isn't in the same folder as qwen_search.py.
HTTPError: 401 Unauthorized The key in .env is wrong. Enter a valid API key from your SerpApi dashboard.

Let Qwen choose from a wide range of search engines

Different search engines are strong in different areas. Baidu is China's main search engine, Naver is South Korea's, and Google News covers news from outlets worldwide.
Through SerpApi, our model can use 100+ search engines, so instead of only using Google, we let Qwen pick the engine for each search.

Only three parts of the earlier code change.

# Update `serpapi_search` to accept `engine` as an argument
# Add `RESTRICTORS` for `json_restrictor` across different engines
RESTRICTORS = {
    "google": "answer_box,organic_results[0:5].{title,link,snippet,date}",
    "google_news": "news_results[0:5].{title,link,source.name,date}",
    "baidu": "answer_box,organic_results[0:5].{title,link,snippet}",
    "naver": "web_results[0:5].{title,link,snippet}",
}

def serpapi_search(engine, query):
    """Searches one SerpApi engine and returns the top 5 results as Markdown."""
    if engine not in RESTRICTORS:  # Qwen sometimes invents engine names; Ollama doesn't enforce the enum
        return f"Unknown engine '{engine}'. Use one of: {', '.join(RESTRICTORS)}."
    query_key = "query" if engine == "naver" else "q"  # Naver calls its query parameter "query"
    return serpapi_client.search({
        "engine": engine,
        query_key: query,
        "json_restrictor": RESTRICTORS[engine],
        "output": "md",
    })

# Replace TOOLS: Qwen now chooses the engine as well as the query.
TOOLS = [{"type": "function", "function": {
    "name": "serpapi_search",
    "description": "Search the web with SerpApi. Returns the top 5 results with titles, links, snippets and dates.",
    "parameters": {
        "type": "object",
        "properties": {
            "engine": {
                "type": "string",
                "enum": list(RESTRICTORS),  # the SerpApi engines Qwen can choose from
                "description": "google: Google web search. "
                               "google_news: Google News, recent news articles. "
                               "baidu: China's main search engine, Chinese-language web. "
                               "naver: South Korea's main search engine, Korean-language web.",
            },
            "query": {"type": "string", "description": "A short, specific search query."},
        },
        "required": ["engine", "query"],
    },
}}]

# Inside ask(), update the loop over Qwen's tool calls. 
for call in reply.tool_calls:  # Qwen can call several searches at once, for different engines
    args = json.loads(call.function.arguments)
    print(f"  [search] {args['engine']}: {args['query']}")
    results = serpapi_search(args["engine"], args["query"])
    messages.append({"role": "tool", "tool_call_id": call.id, "content": results})

Code: Qwen-SerpApi Multiple Engines

The engine argument is SerpApi's own engine parameter, so Qwen can directly pass the required search engine.
Complete code: qwen_search_multi_engines.py.

Add a chat UI

A chat interface makes it easier to compare answers, so I built a local chat app. You can run Qwen with or without search, see token usage, time taken and number of searches for each answer, and compare results side by side in a "battleground".

Dedicated mode on the app

Here's a link to the GitHub repo to access it.

GitHub - serpapi/qwen-web-search: Run Qwen locally with web search through SerpApi
Run Qwen locally with web search through SerpApi. Contribute to serpapi/qwen-web-search development by creating an account on GitHub.

Benchmark

Curious to find out how much model size matters, whether Qwen needs more search engines than Google, and whether a small model with search can beat a bigger model without it, I ran a few experiments with qwen3.5:4b, qwen3.5:9b and qwen3.8:27b on Ollama.

Setup: Each experiment used 100 questions in English, Chinese and Korean on a MacBook Pro with Ollama's default Q4_K_M quantization for the model weights. Answers were graded by text match followed by an AI agent review. With a single run, treat small gaps as noise. Below, 27b, 9b and 4b are short for qwen3.8:27b, qwen3.5:9b and qwen3.5:4b.

Experiment 1: Which model searches best?

Every question was about a recent event in 2026, so the models had to search.

Model performance with web search

Winner: qwen3.8:27b

Highlights

  • qwen3.8:27b wrote sharper queries, searched again when the first results fell short, and was the only model to use Baidu and Naver regularly. The smaller models mostly failed because their searches didn't surface the right answer.
  • Biggest gap: questions that combine two facts. Of 18, 27b answered 16 correctly, 9b 12 and 4b only 6.
  • qwen3.5:9b was the fastest model at 18.5s per answer. 4b spent more time thinking, and 27b took about twice as long as 9b.
  • When 27b was wrong, it usually trusted its memory: it skipped the search or filled its queries with outdated facts.

Experiment 2: Google only vs Multiple search engines

These were local, long-tail questions mostly in Chinese and Korean, asked to qwen3.8:27b. I ran them once with Google only and once with a choice of Google, Google News, Baidu and Naver.

Google only vs Multiple engines on qwen3.8:27b
Engine choice for Chinese and Korean questions

Verdict: Google alone is enough for most questions

Highlights

  • Google through SerpApi is a strong default, even for regional questions. The score gap (93 vs 95 out of 100) is small enough to be noise.
  • Extra engines helped on very fresh or hyper-local facts. For example, Naver found the new head of a Seoul district that six Google searches missed. But answers took 22% longer.
  • Smaller models default to Google. In a separate test, qwen3.5:9b and qwen3.5:4b sent none of 20 Chinese and Korean questions to Baidu or Naver. Changing the tool descriptions barely helped, but an explicit line in the system prompt did. It worked well for Korean (Naver on 8 to 9 of 10 questions), while Baidu needed firmer wording to reach 6 to 7 of 10.

Experiment 3: Small model with search vs Big model without

These were old, stable facts that a model could know from training.

Small model with search vs Big model without

Winner: Small model with search

Highlights

  • A small model with search beats a big model answering from memory (97 and 95 vs 82).
  • qwen3.8:27b lost points because it skipped search on many old facts. It used search for only 31 out of 100 questions, and 7/9 of its wrong answers came from questions where it skipped search.
  • Search made the smaller models faster and token-efficient. Without search, qwen3.5:9b and qwen3.5:4b spent over 4,000 thinking tokens and about a minute per answer. With it, the average was around 8s.
  • Without search, the models mostly guessed instead of admitting they didn't know. Old Korean facts were the worst: the models got 2 to 7 out of 20 right. With search, 9b got 19.

Detailed benchmark results on GitHub

Summary

Giving a local Qwen model web search can substantially improve its accuracy on factual questions. In these experiments, small models with search beat the largest model answering from memory, and search made them far faster than when they tried to recall answers on their own.

Other takeaways:

  • qwen3.8:27b was the best at searching. When questions required a search, it wrote sharper queries and scored highest.
  • Google is a good default engine. Add Baidu, Naver or Google News for very recent or hyper-local questions, and tell smaller models in the system prompt when to use them.