ローカルLLMのライブウェブ検索をOllamaとScrapelessで実現する
Senior Web Scraping Engineer
独自のマシン上で実行されるモデルは、そのトレーニングのカットオフ以降の情報を何も知りません。今週のヘッドラインについてローカルのLlama、Qwen、またはMistralのチェックポイントに尋ねると、拒否されるか、より悪いことに、根拠のある間違った情報を述べます。NISTの生成AIリスクプロファイルは、2番目の失敗モードを<あ href="https://nvlpubs.nist.gov/nistpubs/ai/NIST.AI.600-1.pdf" rel="nofollow">虚構錯覚</あ>と呼び、その定義は「システムがプロンプトに応じて誤ったり虚偽のコンテンツを生成し、自信を持って提示する」ことです。現在のデータへのアクセス経路がないモデルは、質問が今日のものである場合、これを避けることができません。
修正方法は、より大きなモデルではありません。それは、モデルが呼び出せる検索ツールであり、実際の結果を返す検索APIに接続されており、結果はモデルが最終的な回答を記述する前に同じ会話にフィードバックされます。このガイドは、そのパターンをエンドツーエンドで接続します:<あ href="https://ollama.com" rel="nofollow">Ollama</あ>が完全にあなた自身のハードウェア上でツール呼び出しモデルを実行し、<あ href="https://www.scrapeless.com/ja/product/deep-serp-api?utm_source=website&utm_medium=blog&utm_campaign=deepserpapi&utm_term=connect-local-llm-web-search">Scrapeless Deep SerpApi</あ>がモデルが頼る検索バックエンドです。以下に示すすべてのリクエストと応答は、実際のキャプチャされた実行であり、モックトランスクリプトではありません。
この統合が可能にすること
検索ツールが接続されたローカルモデルは、その重みだけでは答えられない質問に答えることができます:
- 最新の出来事と価格。 数ヶ月前にトレーニングされたモデルは、この四半期の数字を知ることができませんが、ライブ検索呼び出しが可能です。
- 自身のリコールのファクトチェック。 モデルは回答を述べ、次に検索呼び出しがそれを確認または修正してから最終応答が出されます。
- 1つの狭いネットワーク依存性を持つオフラインファーストエージェント。 すべて - 重み、推論、ツール選択ロジック - はローカルマシン上で実行されます。唯一の外向きの呼び出しは検索リクエスト自体であり、モデルが選んで尋ねるクエリに正確にスコープされています。
- トレーニングデータを超えてパンチを打つ小さなモデル。 このガイドのモデルは5億パラメータです。火星探査に関する情報を知る必要はありません。質問が検索を必要とすることを認識し、合理的なクエリを作成する必要があります。
検索ツールとしてのDeep SerpApiの理由
Ollamaは独自のホストされた検索機能(ollama.com/api/web_search)を出荷しており、これは迅速なプロトタイプのための合理的なデフォルトです。また、Ollamaアカウント、OLLAMA_API_KEYを必要とし、すべてのクエリをOllama自身のクラウドサービスを通じてルーティングします - モデルはローカルに留まりますが、検索ステップは他のホストされた検索ベンダーと同様にOllamaのインフラストラクチャを離れません。その文書化されたデフォルトの制限は、呼び出しごとに最大5件の結果で、最高で10件です。
Deep SerpApiは専用の構造化検索エンドポイントです:1つの認証されたPOSTがGoogleのオーガニック結果、関連検索、ページネーション、および(クエリに応じて)ビデオとナレッジパネルデータを解析されたJSONとして返します - 短縮された概要ではありません。Scrapelessの製品ページには、「20以上のGoogle SERPシナリオおよび主流検索エンジン」(検索、ニュース、地図、ショッピング、トレンドなど)を対象にしたカバレッジ、1〜2秒の応答時間、カード不要で「2,000件の無料API呼び出し」の無料プランがリストされています。有料使用は「1,000クエリあたり1.05ドルから」です。プロジェクトが他のデータ収集作業のためにすでにScrapelessに依存している場合、または短い回答の概要ではなくより完全なオーガニック結果スキーマが必要な場合、同じアカウントのDeep SerpApiキーをツール呼び出しループに接続すると、2つのプロバイダーと2つの請求書ではなく、1つのプロバイダーと1つの請求書に保つことができます。
無料のAPIキーをサインアップして取得できます - カード不要:<あ href="https://app.scrapeless.com/passport/login/?utm_source=website&utm_medium=blog&utm_campaign=deepserpapi&utm_term=connect-local-llm-web-search">app.scrapeless.com</あ>。
前提条件
- 最低2GBの空きRAMを持つLinux、macOS、またはWSL2マシン(このガイドのモデルは、読み込まれた後、1GB未満を必要とします; GPUはオプションで、推論を高速化します)。
curlおよびPython 3.9以上。- ダッシュボードのAPIキー管理ページからのScrapelessアカウントとAPIキー。
- Ollamaアカウントと
OLLAMA_API_KEYは不要 - このパスはOllamaのホストサービスを呼び出すことはありません。
ツール呼び出しモデルをローカルにインストールして実行する
Ollamaをインストールします:
bash
curl -fsSL https://raw.githubusercontent.com/ollama/ollama/main/scripts/install.sh | sh
systemdで管理されるLinuxホスト(systemdが有効なWSL2を含む)では、インストーラーがollamaサービスを自動的に登録し、127.0.0.1:11434でリッスンします。バイナリとサービスを確認します:
bash
ollama --version
# ollamaのバージョンは0.31.2です
小さなツール呼び出し可能モデルを取得します。Qwen2.5の指示にチューニングされたチェックポイントは、0.5億パラメータサイズまでの関数呼び出しをサポートし、ダウンロードとメモリフットプリントを小さく保ちます:
bash
ollama pull qwen2.5:0.5b
その後、ollama listでモデルがローカルであることが確認できます:397MBのエントリ名qwen2.5:0.5bが、さらなるネットワークアクセスなしでサービスを提供する準備が整いました。
すべてのローカルで実行可能なモデルがツール呼び出しをサポートしているわけではありません。エージェントループを構築する前に、Ollamaのライブラリでモデルのページに「ツール」タグがあるか確認してください。大きなQwen2.5、Llama 3.1、およびMistralチェックポイントも、特定のタスクに0.5Bが小さすぎる場合は同じタグを持っています。
モデルのダウンロード中に無料のScrapeless APIキーを取得できます — クレジットカードは不要です:app.scrapeless.com。
Deep SerpApiエンドポイントの確認
Deep SerpApiはすべてのシナリオに対して1つの形を持ちます:actor名とinputオブジェクトで、docs.scrapeless.comで文書化されています。Google検索シナリオではアクター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": "最新の火星サンプルリターンミッションに関するヘッドライン", "gl": "us", "hl": "en"}
}'
そのクエリの生の呼び出しはHTTP 200を返し、JSON本文にはこれらのトップレベルフィールドが含まれます:organic_results、pagination、related_searches、search_information、inline_videos、video_results、およびmetadata。organic_resultsの各エントリーはposition、title、link、redirect_link、favicon、snippet、snippet_highlighted_words、およびsourceを持っています。レスポンスはプレーンJSONであり、ストリーミングはなく、オープンに保持するセッションもなく、1つのリクエストで1つのドキュメントが返されます。サーバー側で完了していないリクエストは、代わりにHTTP 201とtaskIdを返します。Google検索クエリの通常のケースは、上記の同期的な200です。
検索ツールの定義と接続
Ollamaの/api/chatエンドポイントは、JSON Schema関数定義として形状を持つtools配列を受け入れます。1つのツールweb_searchを定義し、会話と共に渡します:
python
TOOLS = [
{
"type": "function",
"function": {
"name": "web_search",
"description": "ライブウェブで最新情報を検索し、タイトル、リンク、スニペットを含むトップオーガニック結果を返します。",
"parameters": {
"type": "object",
"properties": {
"query": {"type": "string", "description": "検索クエリ"}
},
"required": ["query"],
},
},
}
]
TOOLSをすべての/api/chatリクエストに添付して、モデルが常にツールの存在を知るようにします:
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)
モデルはDeep SerpApiを直接呼び出すことはありません — それは常にweb_searchを名前付けたtool_callsエントリーを出力するだけです。薄いPython関数が実際のHTTP作業を行い、整形された結果を返します:
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)
ツール出力を返す前に上位3つの結果に絞ることで、2回目の/api/chat呼び出しを小さく抑えます — 0.5億パラメータのモデルは制限されたコンテキストウィンドウを持ち、完全なレスポンスは、モデルが質問に答えるために必要ないページネーションリンク、ファビコン、および関連検索ブロックを含んでいます。
プロンプト駆動型の使用:モデルの決定を見る
すべての要素をまとめてください:現在の情報が必要なプロンプトを送信し、モデルにツールをリクエストさせ、そのリクエストをDeep SerpApiに対して実行し、結果を返して最終的な根拠のある答えを得ます。
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": "リアルタイムのウェブで現在の情報を検索し、タイトル、リンク、およびスニペットを含むトップのオーガニック結果を返します。",
"parameters": {
"type": "object",
"properties": {
"query": {"type": "string", "description": "検索クエリ"}
},
"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":
"今日の火星サンプルリターンミッションに関する最新の見出しは何ですか? "
"現在の情報が必要な場合は検索ツールを使用してください。"}]
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": "火星サンプルリターンミッション 最新の見出し"
}
}
}
]
}
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="火星サンプルリターンミッション 最新の見出し") 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": "火星サンプルリターンミッションに関する最新の見出しは以下の通りです:\n\n- [Mars Sample Return - Mars Missions](https://www.jpl.nasa.gov/missions/mars-sample-return-msr/)\n - このリンクは、火星サンプルリターンプログラムに関する情報を含むNASAのウェブサイトに案内します。\n- [NASA-ESA Mars Sample Return](https://en.wikipedia.org/wiki/NASA-ESA_Mars_Sample_Return)\n - プロジェクトに関する詳細情報を提供するWikipediaページもあります。\n\n火星サンプルリターンミッションに関連する記事へのリンクは以下の通りです:\n\n1. [NASAの火星サンプルリターンは死んだ、 中国への道を開く](https://www.universetoday.com/articles/nasas-mars-sample-return-is-dead-paving-the-way-for-china/)\n - このアーティクルは、火星サンプルリターンミッションの現在の状況に関する詳細情報を提供します。\n\nこれが役立つことを願っています!他に必要な記事や更新があれば教えてください。"
}
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億パラメーターのモデルは、ツール結果に存在しない具体的な詳細を生成する可能性があるが、実際の検索結果がそのコンテキストにあるにもかかわらず、基礎データによって生成される可能性が低くなる。基盤はそれが起こる可能性を低くするが、それを排除することはなく、モデルの応答からリンクを引き出す生産システムは、それを事実として扱う前に各リンクをツールの出力と照合する必要がある。同じリクエストと応答のパターンは、クエリのトピックに関係なく当てはまる:モデルは検索するタイミングを決定し、ツールコールはモデルの重みが所有していない唯一のネットワークトリップであり、2回目の完了はツール結果が会話に含まれるまで実行されない。
結論
ローカルモデルをDeep SerpApiに接続するために必要なものは、1つのツール定義、1つのHTTP関数、2つの/api/chat呼び出しです。モデルは、どのタイミングで検索を行い、何を尋ねるべきかの推論を扱います。これはホステッドモデルエージェントフレームワークと同じですが、生成されるトークンはすべて、それを実行する機械に留まります。このパターンは、この1つの例を超えてスケーラブルです:プロンプトを入れ替え、モデルを入れ替え、あるいはTOOLSにさらに関数を追加することで、同じリクエスト-ツール-呼び出し-レスポンスループが他の部分もカバーします。
まずは無料で始めましょう - カード不要です:app.scrapeless.com。完全なパラメーターリファレンスはdocs.scrapeless.comで、現在のクエリごとの料金はscrapeless.com/en/pricingをご覧ください。このようなSERPエンドポイントと、ホステッドAIプラットフォームの回答をキャプチャするScrapelessのアクターとの違いについては、SERP-API対LLMスクレイパー比較をご覧ください。
よくある質問
Q: ツール呼び出しをサポートするローカルモデルはどれですか?
Ollamaのモデルライブラリで「Tools」とタグ付けされたモデルは、このパターンで動作します。Qwen2.5(0.5Bまで)、Llama 3.1および3.2、Mistral、IBM Graniteは、すべてツール呼び出しに対応したチェックポイントを提供しています。そのタグがないモデルは、標準のテキストとしてtool_calls形式のJSONを発行する場合がありますが、その場合、呼び出しコードは構造化されたフィールドを読むのではなく、手動で解析する必要があります。特定のモデルに基づいて構築する前に、タグを確認してください。
Q: これらのいずれかがモデル自体にインターネット接続を必要としますか?
いいえ。モデル、プロンプト、推論はすべてローカルマシンで実行されます。唯一の送信リクエストは、モデルが生成した正確なクエリにスコープ化されたDeep SerpApiへのweb_searchツール呼び出しです。他にネットワークに触れるものはありません。
Q: 別のAPIキーではなく、Ollamaの組み込みウェブ検索を使用しない理由は何ですか?
Ollamaのホステッド検索(ollama.com/api/web_search)は、迅速なプロトタイプのための正当なオプションであり、Ollama自身の別のベンダーアカウントは必要ありません。ただし、無料のOllamaアカウントに紐づくOLLAMA_API_KEYが必要で、結果は1回の呼び出しあたり10件に制限され、Googleのフルオーガニック結果スキーマ(位置、関連検索、ページネーション、縦の結果)ではなく、一般的な結果リストが返されます。プロジェクトがその完全なスキーマや非Google検索シナリオ、またはすでにScrapelessアカウントを通じて他の作業を実行している場合、Deep SerpApiがより適しています。
Q: 検索呼び出しが失敗した場合はどうなりますか?
不正なリクエストはHTTP 400を返し、無効または欠落したAPIキーは認証エラーを返します。サーバーサイドでまだ完了していないクエリは、結果のボディではなくtaskIdとともにHTTP 201を返します。organic_resultsがレスポンスに存在する前に、ステータスコードを確認してください。これは、どのHTTPクライアントもレスポンスを解析する前に確認する方法と同じです。
Q: 英語以外の国や言語をターゲットにできますか?
はい — glはGoogleの国コードを設定し、hlはscraper.google.searchリクエストのインターフェイス言語を設定します。両方ともinputオブジェクトの通常の文字列フィールドであり、呼び出しごとに設定されます。
Q: モデルはDeep SerpApiを直接呼び出すことはありますか?
いいえ。モデルは、どの関数を実行し、どの引数とともに実行するかを説明するtool_callsエントリをのみ発行します。モデル自身にはネットワークアクセスはありません。実際のHTTPリクエストは呼び出し元のPythonコードが所有しており、これによりAPIキーがモデルのコンテキストから完全に外れています。
Q: これをAPIではなく検索結果のスクレイピングに向けるのは安全ですか?
Deep SerpApiは認証されたエンドポイントを介して解析済みのGoogleデータを返すため、呼び出し元側にはrobots.txtやレート制限の懸念はありません。これらのインフラストラクチャの作業はScrapelessの側で行われます。Googleの結果ページを直接スクレイピングして同等のものを構築する人は、まずロボット排除プロトコルや対象の利用規約を読むべきです。管理された検索エンドポイントは、そのクラスの問題を避けるために具体的に存在しています。
Q: モデルが「検索することを決定する」時に実際に何が起こっていますか?
これは、元のリトリーバル強化生成研究で説明されている取得後生成パターンです:モデルは、推論時に取得されたドキュメントに最終出力を条件づけるのではなく、モデルの重みの中に組み込まれているものだけに依存します。ツールの呼び出しは、現代のチャット調整モデルが会話の途中で自らその取得をトリガーするために使用するメカニズムであり、各プロンプトの前に固定されたリトリーバルステップを実行するのではありません。
Q: グラウンディングはモデルが創作するのを完全に止めるのか?
いいえ。グラウンディングは、検索結果が実際にカバーする特定の事実に関する虚構を減少させますが、小さなモデルは依然として誤って帰属させたり、過度に要約したり、ツール出力には含まれていない詳細を追加する可能性があります。2回目のターンは、権威ある引用ではなく、実際のデータに基づいたドラフトとして扱いましょう — 何か重要な内容については、モデルの主張を実際に与えられたorganic_resultsペイロードと比較してから信頼してください。
Scrapelessでは、適用される法律、規制、およびWebサイトのプライバシーポリシーを厳密に遵守しながら、公開されているデータのみにアクセスします。 このブログのコンテンツは、デモンストレーションのみを目的としており、違法または侵害の活動は含まれません。 このブログまたはサードパーティのリンクからの情報の使用に対するすべての責任を保証せず、放棄します。 スクレイピング活動に従事する前に、法律顧問に相談し、ターゲットウェブサイトの利用規約を確認するか、必要な許可を取得してください。



