G-gen の堂原です。当記事では、Google ドライブをデータソースとする Vertex AI Search が提供するウィジェットを、Cloud Run で構築したウェブサイトに埋め込む手順を紹介します。

はじめに
当記事について
当記事では、Google Cloud(旧称 GCP)が提供する検索エンジンサービスである Vertex AI Search において、Google ドライブをデータソースとする Vertex AI Search アプリが提供するウィジェットを、Cloud Run + Flask で構築したウェブサイトに埋め込む手順を紹介します。
留意点
当記事で紹介するソースコードは、ウェブサイトに上でウィジェットを表示する方法の紹介だけを目的としています。そのため、紹介するソースコードをそのまま本番環境に用いることは推奨しません。
実際に本番環境に組み込む際は、例として以下のようなことを考慮する必要があります。
- Flask の secret_key や OAuth クライント ID のクライント ID・クライアントシークレットを Secret Manager で管理する
- OAuth アクセストークンの有効期限をケアする
前提知識
Vertex AI Search は Google Cloud の生成 AI 関連サービスである Vertex AI Agent Builder の1機能です。Vertex AI Search を用いることで Google がマネージドで提供している検索エンジンを利用し、以下のようなデータソースに対して検索を行うことが出来ます。
- Cloud Storage や BigQuery といった Google Cloud サービス
- Google ドライブや Box、Slack などといった Google Cloud 外部のツール
- 特定のウェブサイト
また、Gemini を用いて検索結果を要約する機能も提供されています。
詳しくは、以下の記事をご参照ください。
権限設計
当記事で構築するウェブサイトは、OAuth 認証を用いて、サイトにアクセスしたユーザの権限で Vertex AI Search の検索を実行します。
また、サイトにアクセスするユーザは Google Cloud プロジェクト上において、「ディスカバリー エンジン閲覧者(roles/discoveryengine.viewer)」ロールを持っている必要があります。
Vertex AI Search の設定
事前準備
はじめに、Vertex AI Agent Builder の Vertex AI Search で Google ドライブをデータソースとするデータストアとアプリを作成してください。
詳細な手順については、当記事では記述しません。
作成後、Google Cloud コンソールの検索プレビューで正しく検索ができることを確認してください。
ドメインの許可設定
Vertex AI Search のウィジェットをウェブサイトに埋め込むためには、Vertex AI Search でそのサイトのドメインを許可する必要があります。
許可設定は、Google Cloud コンソールの Vertex AI Search アプリの「統合」画面の「ウィジェット」タブにて行います。

今回は Cloud Run で自動で払い出される URL を用いてアクセスするため、ドメイン「asia-northeast1.run.app」を許可しておきます。
また、ページ下部の HTML をウェブサイトに書き込むことで、ウィジェットを埋め込むことになります。
OAuth 同意画面作成
OAuth 認証に必要な OAuth 同意画面を作成します。

以下にパラメータを記載します。記載の無いパラメータについては未入力でも設定可能です。
| 設定項目 | 小項目 | 設定値 | 補足 |
|---|---|---|---|
| アプリ情報 | アプリ名 | 任意の名前 | 同意画面で表示されるアプリ名となります |
| ユーザー サポートメール | 任意のメールアドレス | ||
| デベロッパーの連絡先情報 | メールアドレス | 任意のメールアドレス | |
| ユーザーの種類 | 内部 | 組織の Google Workspace に属する Google アカウントのみがアクセス可能となります | |
| 非機密のスコープ | ・https://www.googleapis.com/auth/userinfo.email ・openid |
||
| 機密性の高いスコープ | https://www.googleapis.com/auth/cloud-platform |
OAuth 2.0 クライアント ID 作成
ウェブサイトで OAuth 認証処理を実装する際に必要となる OAuth 2.0 クライアント ID を作成します。
以下にパラメータを記載します。記載の無いパラメータについては未入力でも設定可能です。
| 設定項目 | 設定値 | 補足 |
|---|---|---|
| アプリケーションの種類 | ウェブ アプリケーション | |
| 名前 | 任意の名前 | Google Cloud コンソール上で識別するためだけに用いるため、クライアントに表示されることはありません。 |
| 承認済みのリダイレクト URI | https://test-search-widget-{プロジェクト番号}.asia-northeast1.run.app/oauth2callback | 「test-search-widget」は後に作成する Cloud Run のサービス名となります |
作成後、作成したクライアント ID のコンソール画面より、「クライアント ID」と「クライント シークレット」の値を確認しておいてください。

Cloud Run サービス作成
処理の流れ
Cloud Run で実行するプログラムの大まかな処理の流れは、以下のとおりです。
- OAuth アクセストークンを取得
- Python ライブラリである「google_auth_oauthlib」を用いて OAuth 認可リクエスト用のパラメータを作成する
- 認可エンドポイントへリダイレクト
- ユーザに同意処理を行わせ、認可レスポンスをクライント ID で設定した URL へリダイレクトさせる
- 認可コードを用いてアクセストークンを取得する
- アクセストークン含めた認証情報をセッションに保管する
- アクセストークンを埋め込んだ index.html を返却する
ソースコードは、主に以下の公式ドキュメントを参考としています。
ソースコード
ディレクトリ構造
Cloud Run サービスにデプロイする各種ファイルのディレクトリ構造は以下のとおりです。
/ ├ main.py ├ requirements.txt └ templates/ └ index.html
Cloud Run サービスのデプロイコマンド
Cloud Run サービスのデプロイには以下の gcloud コマンドを用います。このコマンドを main.py と同じディレクトリ上で実行します。
gcloud run deploy test-search-widget \ --project {プロジェクト ID} \ --region asia-northeast1 \ --service-account {Cloud Run サービスにアタッチするサービスアカウント} \ --source . \ --allow-unauthenticated \ --session-affinity \ --set-env-vars PROJECT_ID={プロジェクト ID} \ --set-env-vars OAUTH_CLIENT_ID={OAuth 2.0 クライント ID のクライント ID} \ --set-env-vars OAUTH_CLIENT_SECRET={OAuth 2.0 クライント ID のクライアントシークレット} \ --set-env-vars OAUTH_REDIRECT_URI=https://test-search-widget-{プロジェクト番号}.asia-northeast1.run.app
なお、当記事で構築するウェブサイトにおいては、Cloud Run サービスにアタッチするサービスアカウントに権限を付与する必要はありません。
index.html
Vertex AI Search のウィジェット画面に記載されていた HTML を埋め込みます。
<!doctype html> <html> <head> <meta charset="utf-8"> <title>Test Page</title> </head> <body> <!-- Widget JavaScript bundle --> <script src="https://cloud.google.com/ai/gen-app-builder/client?hl=ja"></script> <!-- Search widget element is not visible by default --> <gen-search-widget configId="xxxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" triggerId="searchWidgetTrigger"> </gen-search-widget> <!-- Element that opens the widget on click. It does not have to be an input --> <input placeholder="ここで検索" id="searchWidgetTrigger" /> <script> // Set authorization token. const searchWidget = document.querySelector('gen-search-widget'); searchWidget.authToken = "{{ token }}"; </script> </body> </html>
searchWidget.authToken は Flask の render_template で値を挿入するため、 "{{ token }}" としておきます。
main.py
「処理の流れ」に記載したとおり、ユーザからのリクエストを処理します。
import flask import google.auth import google_auth_oauthlib.flow import google.oauth2.credentials import os app = flask.Flask(__name__) app.secret_key = "xxxxx" CLIENT_CONFIG = {"web": { "client_id": os.environ.get("OAUTH_CLIENT_ID"), "project_id": os.environ.get("PROJECT_ID"), "auth_uri": "https://accounts.google.com/o/oauth2/auth", "token_uri": "https://oauth2.googleapis.com/token", "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs", "client_secret": os.environ.get("OAUTH_CLIENT_SECRET"), "redirect_uris": [ os.environ.get("OAUTH_REDIRECT_URI") ] }} SCOPES = [ "https://www.googleapis.com/auth/userinfo.email", "openid", "https://www.googleapis.com/auth/cloud-platform" ] def credentials_to_dict(credentials) -> dict: """google.oauth2.credentials.Credentialsをdict型に変換する""" return { "token": credentials.token, "refresh_token": credentials.refresh_token, "token_uri": credentials.token_uri, "client_id": credentials.client_id, "client_secret": credentials.client_secret, "granted_scopes": credentials.granted_scopes } @app.route("/oauth2callback") def oauth2callback(): """認可レスポンスのリダイレクト先となる関数""" # stateを用いて整合性を検証しつつ、Flowオブジェクトを再生成 state = flask.session["state"] flow = google_auth_oauthlib.flow.Flow.from_client_config( CLIENT_CONFIG, scopes=SCOPES, state=state ) flow.redirect_uri = flask.url_for("oauth2callback", _external=True, _scheme="https") # 認可レスポンスに含まれる認可コード取得 authorization_response = flask.request.url.replace("http", "https", 1) # アクセストークン含む認証情報を取得 flow.fetch_token(authorization_response=authorization_response) credentials = flow.credentials credentials = credentials_to_dict(credentials) flask.session["credentials"] = credentials return flask.redirect("/") @app.route("/", methods=["GET"]) def webapp(): if "credentials" in flask.session: credentials = google.oauth2.credentials.Credentials( **flask.session["credentials"]) # アクセストークンを埋め込んだindex.htmlを返す return flask.render_template("index.html", token=credentials.token) else: # OAuth 2.0の認証プロセスを管理するためのFlowオブジェクトを作成 flow = google_auth_oauthlib.flow.Flow.from_client_config( CLIENT_CONFIG, scopes=SCOPES ) flow.redirect_uri = flask.url_for("oauth2callback", _external=True, _scheme="https") # 認可エンドポイントのURL取得 authorization_url, state = flow.authorization_url( access_type="offline", include_granted_scopes="true", prompt="consent" ) flask.session["state"] = state return flask.redirect(authorization_url) if __name__ == "__main__": app.run(debug=True, host="0.0.0.0", port=8080)
動作確認
Cloud Run サービスのデプロイ後、Cloud Run サービスに付与される URL にアクセスし動作確認を行います。
アクセスするとまず Google へのログインが求められ、ログイン後に OAuth 同意画面が表示されます。 同意画面にてスコープの許可を行うことで作成したウェブサイトにリダイレクトされます。
すると、ウィジェットが埋め込まれたページが現れ、Google ドライブを対象とした検索を行うことが可能となります。

堂原 竜希(記事一覧)
クラウドソリューション部クラウドエクスプローラ課。2023年4月より、G-genにジョイン。
Google Cloud Partner Top Engineer 2023, 2024, 2025に選出 (2024年はRookie of the year、2025年はFellowにも選出)。休みの日はだいたいゲームをしているか、時々自転車で遠出をしています。
Follow @ryu_dohara