Google検索によるグラウンディング実装時の Tips 8選 + UI 参照実装
はじめに
Google 検索によるグラウンディング実装時の Tips を 8 つ紹介します。
また、UI 実装時のサンプルコードもこちらに置いてあります。
※基本的に Google Cloud の Vertex AI を前提としていますが、Gemini Developer API でも共通の部分は多くあると思います
後続の説明を容易にするため、まず Google 検索によるグラウンディングについて紹介します。
簡単に説明すると、Gemini にツールとして Google 検索を持たせることができる機能です。
Python での具体例を以下に掲載します。
import vertexai
from vertexai.generative_models import (
GenerationConfig,
GenerativeModel,
Tool,
grounding,
)
PROJECT_ID = "project_id"
vertexai.init(project=PROJECT_ID, location="us-central1")
model = GenerativeModel("gemini-1.5-flash-002")
# Use Google Search for grounding
tool = Tool.from_google_search_retrieval(grounding.GoogleSearchRetrieval())
prompt = "〇〇の最新情報を教えてください"
response = model.generate_content(
prompt,
tools=[tool],
generation_config=GenerationConfig(temperature=0.0),
stream=true
)
なお、Gemini 2.0 を使用する場合はモデル名を変えるだけでは動かず、以下のようにGen AI SDK に移行する必要がありそうです。
from google import genai
from google.genai.types import (
GenerateContentConfig,
GoogleSearch,
HttpOptions,
Tool,
)
client = genai.Client(
vertexai=True,
project="project_id",
location="us-central1",
http_options=HttpOptions(api_version="v1"),
)
response = client.models.generate_content(
model="gemini-2.0-flash-001",
contents="〇〇の最新情報を教えてください",
config=GenerateContentConfig(
tools=[
# Use Google Search Tool
Tool(google_search=GoogleSearch())
],
temperature=0.0,
),
)
そしてレスポンスとしては以下のようなものが返ってきます。
※実際のレスポンスではなく、簡略化したサンプルです
※Gemini 1.5 Flash でストリーミングのパラメータを true にした場合のレスポンスです
candidates {
content {
role: "model"
parts {
text: "イ"
}
}
}
usage_metadata {
}
model_version: "gemini-1.5-flash-002"
candidates {
content {
role: "model"
parts {
text: "ーロンマスク氏は現在、ドナルド・トランプ大統領によって設立された"
}
}
}
model_version: "gemini-1.5-flash-002"
candidates {
content {
role: "model"
parts {
text: "政府効率化省(DOGE)の長官を務めています\n"
}
}
finish_reason: STOP
grounding_metadata {
web_search_queries: "イーロンマスクの政治家としての最新動向"
search_entry_point {
rendered_content: "<style>..."
}
grounding_chunks {
web {
uri: "https://vertexaisearch.cloud.google.com/grounding-api-redirect/..."
title: "morishin.club"
}
}
grounding_chunks {
web {
uri: "https://vertexaisearch.cloud.google.com/grounding-api-redirect/..."
title: "morishin.org"
}
}
grounding_supports {
segment {
end_index: 117
text: "政府効率化省(DOGE)の長官を務めています"
}
grounding_chunk_indices: 0
grounding_chunk_indices: 1
confidence_scores: 0.853702784
confidence_scores: 0.853702784
}
retrieval_metadata {
}
}
}
usage_metadata {
prompt_token_count: 17
candidates_token_count: 29
total_token_count: 46
}
model_version: "gemini-1.5-flash-002"
なお、検索が行われた場合でも、レスポンスとしてgrounding_metadata
が含まれない場合があります(公式ドキュメント)。
また、ユーザーへの回答表示時、これらのレスポンスを使用して参照元サイトの情報を UI で示すよう、Google は推奨しています。
後ほど説明します。
実装時の Tips
1. Gemini の利用とは別に料金がかかる
Gemini のインプット、アウトプットとは別にお金が掛かります。
1,000 リクエストあたり 35 ドルです。詳しくは公式ドキュメントをご覧ください。
2. Gemini 1.x シリーズと 2.0 シリーズでは仕様が異なる
※前提
Gemini 1.x シリーズは 2025 年中に順次廃止されます。
これから Google 検索によるグラウンディングの導入を検討する場合は、
Gemini 2.0 シリーズと、用意されている言語の場合は Gen AI SDK の使用がおすすめです。
Google 検索によるグラウンディングは、以下のモデルでサポートされています。
※2025/3/17 時点の情報です。最新情報は公式ドキュメントをご覧ください。
※Gemini 2.0 Pro などの Experimental なモデルは未調査です。
- Gemini 1.5 Pro(テキスト入力のみ)
- Gemini 1.5 Flash(テキスト入力のみ)
- Gemini 1.0 Pro(テキスト入力のみ)
- Gemini 2.0 Flash
Gemini 2.0 Flash は画像や動画など、
グラウンディングがオンになっている際もマルチモーダルなインプットに対応していますが、
現時点で以下で説明する Dynamic Retrieval には未対応です。
3. Dynamic Retrieval の活用有無
Google 検索の実行要否を LLM が判断する機能です。
以下のようにして使います。
- tool = Tool.from_google_search_retrieval(grounding.GoogleSearchRetrieval())
tool = Tool.from_google_search_retrieval(
+ grounding.GoogleSearchRetrieval(
+ dynamic_retrieval_config=grounding.DynamicRetrievalConfig(
+ mode="MODE_DYNAMIC",
+ dynamic_threshold=0.5,
+ )
+ )
)
- Prediction score というスコアに基づいて検索の実行有無が決まる。デフォルトでは 0.7 であり、それを超えると検索が行われる
- 前述のように、2025/3/17 時点で Gemini2.0 は Dynamic Retrieval 未対応。必ず Google 検索が実行される
なお、このトピックのテーマからは外れますが、
Gemini 2.0 は公式ドキュメントに検索と code execution を組み合わせられるということが書いてあったので以下のように試したのですが、実行できませんでした。
response = client.models.generate_content(
model="gemini-2.0-flash-001",
contents="helloと出力するPythonのコード",
config=GenerateContentConfig(
tools=[
# Use Google Search Tool
Tool(google_search=GoogleSearch()),
Tool(code_execution=ToolCodeExecution()),
],
),
)
以下のようなエラーが出ます。
{'error': {'code': 400, 'message': 'At most one tool is supported.', 'status': 'INVALID_ARGUMENT'}}
このあたりは仕様への理解が不足していることが原因だと感じているため、どなたかご存知の方いましたら教えてください。
4. Temperature は 0 が推奨
公式ドキュメントに記載があります。
5. Google の推奨 (一部は必須) UI が存在する
簡単にまとめると、以下の 3 つです。
- 参照元サイトのタイトルやファビコンなどを表示させる
- 回答文中の該当箇所に、上記サイトへの引用数字を表示させる
- 検索クエリを Suggestion として表示させる(必須)
Google 検索によるグラウンディングを使用した際の Gemini からのレスポンスを再掲します。
candidates {
content {
role: "model"
parts {
text: "イ"
}
}
}
usage_metadata {
}
model_version: "gemini-1.5-flash-002"
chunk: candidates {
content {
role: "model"
parts {
text: "ーロンマスク氏は現在、ドナルド・トランプ大統領によって設立された"
}
}
}
model_version: "gemini-1.5-flash-002"
chunk: candidates {
content {
role: "model"
parts {
text: "政府効率化省(DOGE)の長官を務めています\n"
}
}
finish_reason: STOP
grounding_metadata {
web_search_queries: "イーロンマスクの政治家としての最新動向"
search_entry_point {
rendered_content: "<style>..."
}
grounding_chunks {
web {
uri: "https://vertexaisearch.cloud.google.com/grounding-api-redirect/..."
title: "morishin.club"
}
}
grounding_chunks {
web {
uri: "https://vertexaisearch.cloud.google.com/grounding-api-redirect/..."
title: "morishin.org"
}
}
grounding_supports {
segment {
end_index: 117
text: "政府効率化省(DOGE)の長官を務めています"
}
grounding_chunk_indices: 0
grounding_chunk_indices: 1
confidence_scores: 0.853702784
confidence_scores: 0.853702784
}
retrieval_metadata {
}
}
}
usage_metadata {
prompt_token_count: 17
candidates_token_count: 29
total_token_count: 46
}
model_version: "gemini-1.5-flash-002"
参照元サイトのタイトルやファビコンなどを表示させる
以下部分のuri
を fetch する必要があります。
grounding_chunks {
web {
uri: "https://vertexaisearch.cloud.google.com/grounding-api-redirect/..."
title: "morishin.club"
}
}
この部分は Gemini が回答を作成するために使った Web サイトの URI とドメインです。
URI はhttps://vertexai...
となっていますが、記事用のダミーではなく実際にこのような形式になっています。
なおファビコンについては、取得率的には以下の API を使うのが良さそうでした。
http://www.google.com/s2/favicons?domain=
回答文中の該当箇所に、参照元サイトへの引用数字を表示させる
以下の部分を使います。
grounding_chunks {
web {
uri: "https://vertexaisearch.cloud.google.com/grounding-api-redirect/..."
title: "morishin.club"
}
}
grounding_chunks {
web {
uri: "https://vertexaisearch.cloud.google.com/grounding-api-redirect/..."
title: "morishin.org"
}
}
grounding_supports {
segment {
end_index: 117
text: "政府効率化省(DOGE)の長官を務めています"
}
grounding_chunk_indices: 0
grounding_chunk_indices: 1
confidence_scores: 0.853702784
confidence_scores: 0.853702784
}
grounding_supports.grounding_chunk_indices
がgrounding_chunks
の出現順に対応しています。
つまり上記の例で言うと「政府効率化省(DOGE)の長官を務めています」の部分は
grounding_chunks
の 1 つめと 2 つめ、両方のサイトを根拠として出力した回答ということです。
参照実装ではファビコンなどの取得を経つつ、
上記のレスポンスを整形して以下のような型のリストとしてまとめています。
export type GroundingSource = {
title: string;
domain: string;
uri: string;
faviconUrl: string;
};
export type GroundingCitation = {
referenceNumber: number[];
correspondingText: string;
};
const groundingCitations: GroundingCitation[] = [];
const groundingSources: GroundingSource[] = [];
これらを、回答文として出力されたテキストと一緒に以下のような関数に渡します。
該当部分に対して上付き文字のリンクを追加し、リプレイスしています。
export const addCitations = (
responseText: string,
sources: GroundingSource[],
citations: GroundingCitation[]
): string => {
// 文字列の複数回置換を避けるためのマップを作成
const replacementMap = new Map<string, string>();
for (const citation of citations) {
const citationText = citation.correspondingText.replace(
/(?<=[^\s\p{P}])\s+(?=[^\s\p{P}])/gu,
''
);
if (!replacementMap.has(citationText)) {
let links = '';
// 重複するインデックス番号を削除
const uniqueIndices = [...new Set(citation.referenceNumber)].sort(
(a, b) => a - b
);
for (const index of uniqueIndices) {
if (sources[index]) {
links += `<a href="${
sources[index].uri
}" target="_blank" rel="noopener noreferrer" class="align-super text-xs no-underline">${
index + 1
}</a>`;
}
}
if (links) {
replacementMap.set(citationText, `${citationText}${links}`);
}
}
}
// 一度にすべての置換を実行
let newResponseText = responseText;
for (const [original, replacement] of replacementMap.entries()) {
// 正規表現を使わず単純な文字列置換を使用
newResponseText = newResponseText.split(original).join(replacement);
}
return newResponseText;
};
これまでの例で具体的なアウトプットを示すと、まず以下のレスポンスを Gemini から受け取ったとします。
# Geminiのレスポンス(テキスト部分をすべてつなげたもの)
イーロンマスク氏は現在、ドナルド・トランプ大統領によって設立された政府効率化省(DOGE)の長官を務めています
# Geminiのレスポンス(grounding_metadataの抜粋)
grounding_chunks {
web {
uri: "https://vertexaisearch.cloud.google.com/grounding-api-redirect/response1..."
title: "morishin.club"
}
}
grounding_chunks {
web {
uri: "https://vertexaisearch.cloud.google.com/grounding-api-redirect/response2...."
title: "morishin.org"
}
}
grounding_supports {
segment {
end_index: 117
text: "政府効率化省(DOGE)の長官を務めています"
}
grounding_chunk_indices: 0
grounding_chunk_indices: 1
confidence_scores: 0.853702784
confidence_scores: 0.853702784
}
「政府効率化省(DOGE)の長官を務めています」の部分は、2 つのサイトを参照して回答されたことがわかります。
そのため、この部分のテキストにaddCitations
関数で以下のように a タグを加えます。
政府効率化省(DOGE)の長官を務めています<a href="https://vertexaisearch..../response1" target="_blank" rel="noopener noreferrer" class="align-super text-xs no-underline">1</a><a href="https://vertexaisearch..../response2" target="_blank" ...>2</a>
実例として、以下のように表示されます。
※リンクであることがかなりわかり辛かったり、リンク同士の距離が近かったりと UI/UX 的にかなりお粗末ですがご容赦ください。
検索クエリを Suggestion として表示させる
以下のrendered_content
の部分です。(本当はすごく長いのですが、省略しています)
grounding_metadata {
web_search_queries: "イーロンマスクの政治家としての最新動向"
search_entry_point {
rendered_content: "<style>..."
}
これを html として出力すると、以下画像のようになります。
ブラウザがダークモードになっていると黒に変わります。
6. グラウンディングされた回答は、保存して良い内容と期間に制限がある
Service Termsに以下のような表現があります。
- Customer may store the text of the Grounded Results (excluding Links):
(1) that were displayed by Customer for up to thirty (30) days only to evaluate and optimize the display of the Grounded Results in the Customer Application;
(2) in the chat history of an End User of the Customer Application for up to six (6) months only for the purpose of allowing that End User to view their chat history.
要約すると以下の通りです。
- グラウンディングが行われたレスポンスについては、以下条件において、その回答テキストのみ保存可能
- 評価および最適化の目的においては 30 日間
- アプリのユーザーが自身のチャット履歴を閲覧できるようにする目的においては 6 か月
つまり、前述の参照元サイトのリンクや、Suggestion などの保存は NG です。
7. ユーザーが入力したクエリはデバッグ、テストの目的で Google 社内にのみ保存される
※Google サポートに問い合わせて得た内容ですが、判断に責任は持てません。参考程度にご認識お願いします。
- 検索に利用したワードは 30 日間保存され、Google 社内でのテスト・デバッグに利用される
- 一定期間データは残るが、第三者に推測可能な形で利用されることはない
8. 検索されるサイトに制限をかけることはできない
例えば検索実行前に Web プロキシをかませるといった機能はないため、
セキュリティ的に問題がある場合は、取得した回答に対しての対応が必要です。
そもそも、よろしくないサイトが検索結果に表示されてしまうようなクエリを投げないためのリテラシー教育なども重要です。
実装例
コードはこちらに置いておきます。
(記事内ではサンプルとして Python を出してきましたが、コードは TypeScript です)
おわりに
Google 検索によるグラウンディングを使用する場合は、Gemini 1.5 シリーズから 2.0 へのマイグレーションが必要で、ちょっと大変ですね。
Web 検索関連の余談ですが、直近ではgpt-4o シリーズも検索できるようになったりしていますね。
Discussion