「URL を読ませているのに、応答が公式サイトのトップ説明だけで止まっている」「urlContextMetadata が空で返ってくる」— Gemini API の URL Context ツールを本格的に使い始めると、こうした症状に一度はぶつかるのではないでしょうか。私も社内ダッシュボード向けの自動要約パイプラインで同じ壁にぶつかり、原因を一つずつ潰していった経験があります。
ここではURL Context が「動いていないように見える」典型的な3パターンと、それぞれを切り分けて修正するための確認手順を、実際に現場で詰まったポイントを交えて整理します。導入そのものの解説はGemini API の URL Context 入門ガイドにまとめてありますので、まずは動かし方を確認したい方はそちらを先にお読みください。
まず最初に確認する3つの基本
不具合の8割はここで切り分けがつきます。順番に確認するのがおすすめです。
1. ツール宣言が tools 配列に正しく入っているか
tools=[{ "url_context": {} }] の形になっているかを確認します。Function Calling と混在させる場合、ツール配列の構造が崩れていて URL Context だけ無視されているケースが多いです。
2. プロンプトに URL がそのまま記述されているか
URL Context は、プロンプト本文に含まれた URL を自動検出してフェッチします。<url>...</url> のような独自タグで囲んだり、JSON にエンコードしてしまうと検出されません。素のテキストとして https://example.com/article をそのまま書いてください。
3. urlContextMetadata を毎回ログに出しているか
応答の質を疑う前に、メタデータ側で「実際にフェッチできた URL は何か」「ステータスは何か」を確認します。ここが空なら、そもそもツールが起動していないか URL を検出できていません。
from google import genai
from google.genai import types
client = genai.Client()
response = client.models.generate_content(
model="gemini-2.5-pro",
contents="次の記事の要点を3つに整理してください: https://ai.google.dev/gemini-api/docs/url-context",
config=types.GenerateContentConfig(
tools=[types.Tool(url_context=types.UrlContext)],
),
)
# 必ずメタデータを確認する
meta = response.candidates[0].url_context_metadata
for entry in meta.url_metadata:
print(entry.retrieved_url, entry.url_retrieval_status)
print("---")
print(response.text)期待する出力例: 各 URL について URL_RETRIEVAL_STATUS_SUCCESS が並びます。URL_RETRIEVAL_STATUS_ERROR や URL_RETRIEVAL_STATUS_PAYWALL が出ている場合は、後述のサイト側要因に該当します。
症状別の原因切り分け
症状A: 応答に URL の内容が反映されていない/一般論しか返ってこない
urlContextMetadata を見て、url_metadata が空であればツール自体が呼ばれていません。原因の多くは以下のどれかです。
- プロンプトに「これを参考に」「このリンクを開いて」といった指示が弱く、モデルがフェッチを必要と判断しなかった
- URL が短縮 URL(bit.ly 系)で、リダイレクト先まで追ってもらえない
system_instruction側で「外部リソースを参照しない」と書いてしまっている
最初の対策として「次の URL の内容を必ずフェッチしてから回答してください」と明示するだけで、検出率が大きく上がります。短縮 URL は事前に展開しておくのが確実です。
症状B: メタデータに URL は出るが status が _ERROR
これは URL は検出されたものの、フェッチが失敗したパターンです。代表的な原因はこちらです。
- 対象サイトの
robots.txtで Google のクローラーをブロックしている - 認証必須のページ(ログイン後ダッシュボード、社内 Wiki など)を渡している
- Cloudflare などの WAF が Bot トラフィックとして弾いている
- HTML が JavaScript で後から描画される SPA で、初期 HTML が空
社内ツールや Notion の限定公開ページは原則として読めません。これは仕様上の制約なので、対象データを公開ページにエクスポートするか、別途 Files API で渡す方針に切り替える必要があります。
症状C: タイムアウトする/レスポンスが極端に遅い
URL Context は内部的に複数 URL を並列にフェッチします。プロンプトに 10 件以上の URL を入れると、1〜2件のフェッチ失敗を待つ間に全体がタイムアウトしやすくなります。私の経験では、本番処理で同時に渡す URL は 3〜5 件までに抑えると安定しました。バッチ処理にしたい場合は、リクエストを分割するのが現実的です。
タイムアウト時のリトライ実装はGemini API のリトライ設計パターンで詳しく扱っているので、そちらと組み合わせてください。
取得できないサイトの典型と回避策
実際に運用していると、「公式仕様としては取得できるはずなのに、特定のドメインだけ確実に失敗する」という現象に出会います。私が遭遇した代表例と回避策を共有します。
- 大手ニュースサイトの記事ページ: 多くがアクセスポリシー上、AI 系クローラーに 401/403 を返します。RSS フィード経由か、提携 API を使うのが筋です
- Twitter/X、LinkedIn のタイムライン: ログイン必須のため取得不可
- Notion 公開ページ: URL は公開でも、JS レンダリングで本文が初期 HTML に存在しないことがあります
- GitHub の README: 通常はフェッチ可能ですが、private リポジトリは当然不可
JS レンダリング系のサイトについては、自前でヘッドレスブラウザでレンダリング済み HTML を生成し、それを Files API でアップロードしてから Gemini に渡す構成が、現状もっとも安定します。
URL Context を使うべきでない場面
ツールが動かない時、無理に動かす前に「そもそもこのタスクに URL Context は適切か」を見直す価値があります。私の判断基準は次の通りです。
- 同じ URL を毎回フェッチする運用 → 自前でキャッシュ層を持つほうが速い・安い
- 法的に公開取得が制限される情報 → そもそも使わない
- フェッチ結果の構造化が必要 → URL Context は要約には強いが、構造化抽出はばらつきが出やすい。Function Calling や
responseSchemaと組み合わせる前提で設計してください
検索を起点にしたい場合は、URL Context ではなく Grounding(Google Search との連携)のほうが適しています。Grounding 側のトラブルシューティングはGrounding が機能しない時の対処に整理してあります。
本番運用で押さえておきたい実装パターン
最後に、URL Context を本番に乗せる時に必ず入れておきたい防御的な実装をまとめておきます。
def fetch_with_url_context(client, model, prompt: str, urls: list[str]) -> dict:
"""URL Context を使って要約し、フェッチ結果も返す。"""
full_prompt = f"{prompt}\n\n参考 URL:\n" + "\n".join(urls)
resp = client.models.generate_content(
model=model,
contents=full_prompt,
config=types.GenerateContentConfig(
tools=[types.Tool(url_context=types.UrlContext)],
temperature=0.2,
),
)
meta = resp.candidates[0].url_context_metadata
fetched = [
{"url": m.retrieved_url, "status": str(m.url_retrieval_status)}
for m in meta.url_metadata
]
failed = [f for f in fetched if "SUCCESS" not in f["status"]]
if failed:
# 失敗 URL を呼び出し側が判断できるように返す
return {"text": resp.text, "fetched": fetched, "failed": failed}
return {"text": resp.text, "fetched": fetched, "failed": []}このように呼び出し側に「どの URL が成功し、どれが失敗したか」を必ず返す形にしておくと、本番でユーザーへ「このソースは取得できなかったのでスキップしました」と説明できます。サイレントに 5 件中 2 件落ちている状態が一番怖いので、ここはぜひ習慣化してください。
URL Context は当たり前にハマる箇所がはっきりしているツールです。まずは urlContextMetadata をログに出すところから始めて、対象サイトの素性とプロンプトの書き方を一つずつ見直していけば、たいていの不具合は今日のうちに解決できるはずです。
本番運用で持っておきたい観測手段
URL Context のトラブルは、開発中はほとんど顔を出さず、トラフィックが増え始めて初めて顕在化する性質があります。「1時間以内に気づけた」状態と「ユーザーから指摘されて初めて気づいた」状態の差は、ちょっとした観測の習慣で決まります。
私が必ず入れている習慣は3つです。1つ目は、url_retrieval_status を構造化フィールドとしてログに乗せること。フリーテキストのログ行に混ぜずに、ドメイン別の _ERROR 率を出すパネルを1枚作っておくと、デグレが起きた瞬間にはほぼ気づけます。
2つ目は、本番では URL Context の対象ドメインを軽くホワイトリスト化することです。何でも読みに行ける状態だと、ユーザー入力経由で予期しないドメインを参照してしまうリスクが残ります。「自社ドキュメント・提携サイト・主要リファレンスのみ」と限定するだけで、想定外の応答をかなり抑えられます。
3つ目は、url_metadata が空のまま返ってきたケースを通知対象にすることです。プロンプトに3つの URL を書いたのにメタデータが0件なら、それはツールが起動していない証拠で、ソフトエラーではなく確実な異常です。私はここで一度ヒヤッとした経験があるので、迷わずアラート対象にしています。
チェックリスト:原因切り分けの手順
トラブル時に毎回ゼロから考えなくて済むよう、私が現場で使っているチェックリストを置いておきます。上から順に確認してください。
urlContextMetadata.url_metadataをログに出しているtools配列が[Tool(url_context=UrlContext)]の形になっている- プロンプトに URL が素のテキストで書かれている(タグ・JSON 化していない)
- 渡している URL が短縮 URL ではない(
bit.ly,t.co等) - 同時に渡している URL が 5 件以下
system_instructionで外部参照を禁止していない- 対象サイトが認証必須でない
- 対象サイトが JavaScript で本文をレンダリングしていない
- 失敗 URL の
url_retrieval_statusを呼び出し側に必ず返している
このうち1つでも該当しないものがあれば、まずそこを直してから再現確認するのが最短ルートです。応答品質の調整(プロンプトの書き換えやモデル変更)に着手するのは、上記が全てクリアになってからにしてください。
次の一手
トラブルシューティングが落ち着いたら、URL Context を活かした実用例として競合ページの自動分析パイプラインで使い方の応用を見ておくと、設計の幅が広がります。私自身、要約タスクで安定運用できるようになってから、競合監視やニュース記事の差分追跡など、URL Context が力を発揮する場面が一気に増えました。「動かない」をきちんと潰すことが、結果的に応用範囲を広げる一番の近道です。