如何將本地 LLM 與即時 Google 搜尋結果頁(SERP)資料連接起來

了解如何利用 SERP API 將本機大語言模型(LLM)與即時 Google 搜尋結果頁面(SERP)資料連接起來。建立一個 Python 工作流程,用於執行 Google 搜尋、篩選 SERP 結果,並將最新的搜尋上下文傳送給本機模型。

talor ai
Last updated on
7 min read

Local LLM 很適合 private assistants、offline experiments 和 cost-controlled workflows。

但它本身無法看到即時 Google Search results

如果你問 local model:

What are the current top-ranking pages for “best SERP API for AI agents”?

它可能會猜、根據過期 training data 回答,或直接說無法 access the web。

SERP API 可以解決這個問題。它能為 local LLM 提供 live search layer。你可以查詢 Google,取得 structured SERP data,精簡 response,然後只把有用欄位傳給模型。

LM Studio 支援透過 local API server 運行 local LLM,並支援 OpenAI-compatible endpoints。Ollama 也提供 OpenAI-compatible API support,包含使用 http://localhost:11434/v1 的示例。

為什麼 Local LLM 需要 Google SERP Data?

Local model 可以用於 reasoning、summarizing、rewriting 和 private assistant。但它不會自動知道:

  • 哪些頁面今天正在排名

  • 哪些 competitors 出現在 Google

  • 使用者目前看到哪些 snippets

  • 哪些 sources 足夠新,可以被引用

  • 模型訓練後發生了什麼變化

Google SERP data 可以給模型提供最新 search context。

對 developer workflow 來說,最有用的欄位通常是:

欄位

為什麼重要

title

幫助模型理解頁面主題

link

提供 source URL

snippet

提供頁面簡短摘要

position

顯示 ranking order

domain

用於 deduplication 和 source quality

collected_at

方便後續 audit

你不需要把完整 search response 都傳給模型。對多數 local LLM workflow 來說,compact search context 已經足夠。

我們要做什麼?

整體 workflow 如下:

User question
→ Define a Google search query
→ Call a SERP API
→ Extract title, link, snippet, position, and domain
→ Send compact search context to a local LLM
→ Generate an answer with live Google SERP context

這個 workflow 適合:

  • local AI search assistants

  • SEO research tools

  • RAG source discovery

  • competitor monitoring

  • content research

  • private internal AI tools

  • lightweight market research

下面的 code 是 provider-neutral 的。你可以改成 Talordata、SerpApi、Serper、Bright Data、SearchAPI 或其他返回 structured JSON 的 SERP API。

Step 1:啟動 Local LLM Server

你需要一個可以暴露 API endpoint 的 local model runtime。

LM Studio 常見 local endpoint 是:

http://localhost:1234/v1

Ollama 常見 OpenAI-compatible endpoint 是:

http://localhost:11434/v1

實際 model name 取決於你安裝的模型,例如:

llama3
qwen2.5
mistral
local-model

先安裝 Python packages:

pip install requests openai

Step 2:設定 Environment Variables

不要把 API keys 和 endpoints 直接寫進 code。

export SERP_API_KEY="your_serp_api_key"
export SERP_API_ENDPOINT="https://your-serp-api-endpoint.com/search"

export LOCAL_LLM_BASE_URL="http://localhost:1234/v1"
export LOCAL_LLM_MODEL="your_local_model_name"

如果使用 Ollama:

export LOCAL_LLM_BASE_URL="http://localhost:11434/v1"
export LOCAL_LLM_MODEL="llama3"

Windows PowerShell:

$env:SERP_API_KEY="your_serp_api_key"
$env:SERP_API_ENDPOINT="https://your-serp-api-endpoint.com/search"
$env:LOCAL_LLM_BASE_URL="http://localhost:1234/v1"
$env:LOCAL_LLM_MODEL="your_local_model_name"

Step 3:使用 SERP API 查詢 Google SERP Data

SERP API request 通常包含 search engine、query、location、device 和 output format。

import os
import requests


SERP_API_KEY = os.getenv("SERP_API_KEY")
SERP_API_ENDPOINT = os.getenv("SERP_API_ENDPOINT")


def search_google_serp(query, location="United States", device="desktop"):
    params = {
        "engine": "google",
        "query": query,
        "location": location,
        "device": device,
        "output": "json"
    }

    headers = {
        "Authorization": f"Bearer {SERP_API_KEY}"
    }

    response = requests.get(
        SERP_API_ENDPOINT,
        params=params,
        headers=headers,
        timeout=30
    )

    response.raise_for_status()
    return response.json()

不同 provider 可能使用 q 而不是 query,也可能把 API key 放在 query parameter 裡。把這個 function 獨立出來,可以避免切換 provider 時影響 local LLM logic。

Talordata docs 列出了 Google、Bing、Yandex 和 DuckDuckGo 的 query parameter sections。其 pricing page 也描述了 real-time Google、Bing、Yandex 和 DuckDuckGo search data,並支援 JSON / HTML responses 和 response-based plans。

Step 4:為 Local Model 精簡 SERP Results

不要把完整 SERP response 直接傳給 local model。

完整 response 可能包含 ads、sitelinks、local blocks、rich snippets、related questions、thumbnails、metadata 和其他欄位。這會浪費 context window,也會拖慢 generation。

可以先保留 compact format:

from urllib.parse import urlparse


def clean_text(value):
    if not value:
        return ""
    return " ".join(str(value).split())


def get_domain(url):
    if not url:
        return ""
    parsed = urlparse(url)
    return parsed.netloc.replace("www.", "") if parsed.netloc else ""


def extract_compact_results(serp_json, max_results=5):
    organic_results = (
        serp_json.get("organic_results")
        or serp_json.get("organic")
        or serp_json.get("results", {}).get("organic")
        or []
    )

    compact = []

    for index, item in enumerate(organic_results[:max_results], start=1):
        link = item.get("link") or item.get("url")

        if not link:
            continue

        compact.append({
            "position": item.get("position") or item.get("rank") or index,
            "title": clean_text(item.get("title")),
            "url": link,
            "domain": get_domain(link),
            "snippet": clean_text(item.get("snippet") or item.get("description"))
        })

    return compact

這樣只保留模型通常需要的資訊:title、URL、domain、snippet 和 ranking position。

Step 5:將 Search Context 傳給 Local LLM

接著把 local model 當作 OpenAI-compatible endpoint 使用。

from openai import OpenAI


LOCAL_LLM_BASE_URL = os.getenv("LOCAL_LLM_BASE_URL")
LOCAL_LLM_MODEL = os.getenv("LOCAL_LLM_MODEL")


client = OpenAI(
    base_url=LOCAL_LLM_BASE_URL,
    api_key="local-key"
)


def answer_with_search_context(user_question, search_results):
    sources_text = ""

    for result in search_results:
        sources_text += (
            f"[{result['position']}] {result['title']}\n"
            f"URL: {result['url']}\n"
            f"Domain: {result['domain']}\n"
            f"Snippet: {result['snippet']}\n\n"
        )

    prompt = f"""
You are answering with live Google SERP context.

User question:
{user_question}

Search results:
{sources_text}

Instructions:
- Use only the search results above when making factual claims.
- Cite sources using their result positions, such as [1] or [2].
- Do not invent URLs, rankings, sources, or statistics.
- If the snippets are not enough, say that full-page retrieval is needed.
- Keep the answer concise and practical.
"""

    response = client.chat.completions.create(
        model=LOCAL_LLM_MODEL,
        messages=[{"role": "user", "content": prompt}],
        temperature=0.2
    )

    return response.choices[0].message.content

Prompt 要稍微嚴格一點。Local model 不應該只因為拿到幾條 snippets,就自己編造 sources。

完整 Python 示例

import os
import requests

from urllib.parse import urlparse
from openai import OpenAI


def require_env(name):
    value = os.getenv(name)
    if not value:
        raise RuntimeError(f"Missing required environment variable: {name}")
    return value


SERP_API_KEY = require_env("SERP_API_KEY")
SERP_API_ENDPOINT = require_env("SERP_API_ENDPOINT")

LOCAL_LLM_BASE_URL = require_env("LOCAL_LLM_BASE_URL")
LOCAL_LLM_MODEL = require_env("LOCAL_LLM_MODEL")

client = OpenAI(
    base_url=LOCAL_LLM_BASE_URL,
    api_key="local-key"
)


def clean_text(value):
    if not value:
        return ""
    return " ".join(str(value).split())


def get_domain(url):
    if not url:
        return ""
    parsed = urlparse(url)
    return parsed.netloc.replace("www.", "") if parsed.netloc else ""


def search_google_serp(query, location="United States", device="desktop"):
    params = {
        "engine": "google",
        "query": query,
        "location": location,
        "device": device,
        "output": "json"
    }

    headers = {
        "Authorization": f"Bearer {SERP_API_KEY}"
    }

    response = requests.get(
        SERP_API_ENDPOINT,
        params=params,
        headers=headers,
        timeout=30
    )

    response.raise_for_status()
    return response.json()


def extract_compact_results(serp_json, max_results=5):
    organic_results = (
        serp_json.get("organic_results")
        or serp_json.get("organic")
        or serp_json.get("results", {}).get("organic")
        or []
    )

    compact = []

    for index, item in enumerate(organic_results[:max_results], start=1):
        link = item.get("link") or item.get("url")

        if not link:
            continue

        compact.append({
            "position": item.get("position") or item.get("rank") or index,
            "title": clean_text(item.get("title")),
            "url": link,
            "domain": get_domain(link),
            "snippet": clean_text(item.get("snippet") or item.get("description"))
        })

    return compact


def answer_with_search_context(user_question, search_results):
    sources_text = ""

    for result in search_results:
        sources_text += (
            f"[{result['position']}] {result['title']}\n"
            f"URL: {result['url']}\n"
            f"Domain: {result['domain']}\n"
            f"Snippet: {result['snippet']}\n\n"
        )

    prompt = f"""
You are answering with live Google SERP context.

User question:
{user_question}

Search results:
{sources_text}

Instructions:
- Use only the search results above when making factual claims.
- Cite sources using their result positions, such as [1] or [2].
- Do not invent URLs, rankings, sources, or statistics.
- If the snippets are not enough, say that full-page retrieval is needed.
- Keep the answer concise and practical.
"""

    response = client.chat.completions.create(
        model=LOCAL_LLM_MODEL,
        messages=[{"role": "user", "content": prompt}],
        temperature=0.2
    )

    return response.choices[0].message.content


if __name__ == "__main__":
    user_question = "What are the current top pages about SERP APIs for AI agents?"
    search_query = "SERP API for AI agents"

    serp_json = search_google_serp(search_query)
    compact_results = extract_compact_results(serp_json, max_results=5)

    answer = answer_with_search_context(user_question, compact_results)
    print(answer)

Expected Output

實際輸出取決於 SERP API 返回的 live search results。常見結果可能像這樣:

Based on the current Google results, the top pages mainly discuss SERP APIs for AI agents from three angles:

1. API comparison pages that evaluate providers by pricing, JSON output, and search coverage [1].
2. Developer tutorials that explain how to connect search APIs to AI agents or RAG systems [2].
3. Product pages focused on live search data, localization, and structured SERP results [3].

The snippets are useful for identifying current ranking pages, but full-page retrieval would be needed for a deeper comparison.

具體 wording 取決於你的 local model。

Local LLM Search Workflow 建議

保持 SERP context 小而精簡。Local model 通常比 hosted frontier model 更受 memory 和 speed 限制。

優先使用 structured data,而不是 raw HTML。Raw HTML 適合 debugging,但 compact JSON 更容易被模型使用。

保存 search metadata。如果需要 auditability,建議保存 query、location、device、title、URL、snippet、position 和 timestamp。

必要時使用多個 queries。一個 user question 可能需要兩三個 search queries,而不只是一次搜尋。

不要盲目信任 snippets。對重要回答,建議抓取 source page、提取 readable text、切分 chunk,再把選中的 chunk 傳給 local model。

FAQ

Local LLM 可以直接訪問 Google Search 嗎?

不行。Local LLM 本身需要外部 tool、API、browser 或 retrieval layer。SERP API 是提供 live Google search data 的一種實用方式。

一定要使用 LM Studio 或 Ollama 嗎?

不一定。你只需要一個 app 能調用的 local model runtime。LM Studio 和 Ollama 常見,是因為它們適合 local model workflow,並支援 OpenAI-compatible endpoints。

要把完整 SERP API response 傳給模型嗎?

通常不建議。先精簡 response。可以從 title、URL、domain、snippet 和 position 開始,需要時再增加其他欄位。

這和 RAG 一樣嗎?

它和 RAG 有關。SERP API 可以作為 RAG workflow 的 live discovery layer。若需要更深入回答,可以抓取搜尋結果中的頁面、切分 chunks、rerank,然後把最佳 chunks 傳給 local LLM。

Scale Your Data
Operations Today.

Join the world's most robust proxy network.

Start Free Trial