🙂

Vertex AI Search の運用チップス

に公開

概要

Enterprise における検索システムを簡単に構築できるという点で、Vertex AI Search は有用なサービスです。
他にも検索だけではなく Gemini における Grounding となるソースとして利用する事もできます。

しかし、単純に Vertex AI Search を構築してもあまり良い検索結果を得られることは少ないかと思います。
良い検索結果を得るためには、データの整備、運用が必要になります。

Gemini Enterprise は Vertex AI Search と同じ技術を利用しているため同じような検索結果をより良くするために同じような観点が必要になるでしょう。
そのため、今回の話としては Gemini Enterprise でもできるレベル感での話をさせていただきます。

今回話している部分は、以下のイベントでも若干話します。

https://fortee.jp/google-cloud-technical-summit-2026/proposal/bdffe3f9-bf58-45a1-859c-fb6290e4bc67

対象読者

ある程度 Vertex AI Search の前提知識を持っていることをベースとしています。

  • Google Cloud についてある程度理解している人
  • Vertex AI Search (Custom Search)を使っている人、使いこなしたい人
  • 非構造化データの検索システムを作りたい人

話さないこと

いきなりまとめ

検索精度向上

基本的に利用しているデータを見極める必要があります。
そのうえで、以下の点が Vertex AI Search の検索結果に大きく関係します。

  • データ形式
  • ドキュメントパース方法
  • メタデータ設定

データの形式

データをどの様に作成するかですが、基本的には PDF や PPTX などの画像を含めるようなパターンか、データサイズがどうかで考えます。

名前 メタデータの有無 ドキュメントパース方法の変更 チャンクサイズ変更 自然言語での問い合わせ データサイズ
構造化データ あり ☓(デジタルパーサしかないはず) 1ファイル 1MB(メタデータ含め)まで
非構造化データ なし 1ファイル 200 MBまで
メタデータ付き非構造化データ あり 1ファイル 200 MBまで

基本的には非構造化データを使うならばメタデータ付きのほうが良いです。使わないとしたら、単純に構築難易度とメタデータが要らないぐらいかと思います
以下のようにデータを使い分けるとよいかと思います。

データ形式 理由
メール文章、チャット文章 非構造化データ 長い文章や html、画像埋め込みなどの修飾があり得るため
QAなどの問い合わせ文章、単語帳 構造化データ Text だけで良いため

https://docs.cloud.google.com/generative-ai-app-builder/docs/prepare-data

ドキュメントパース方法

非構造化データについては以下のパターンでチューニングが可能です。

チャンクサイズ 料金 利用できる File Type 注意事項
Digital parser 500まで かからない html,pdf(デジタルテキスト),docx,pptx,txt,xlsx,xlsm 表、リスト、見出しなどのドキュメント要素は読まない
Layout parser 500まで Document AI の値段 html,pdf(デジタルテキスト),docx,pptx,xlsx,xlsm 段落、表、リストなどを検出する(タグを認識する感じ)
OCR parser 500まで Document AI の値段 画像を含んでいるPDFやスキャンしたPDFに利用 1ファイル500ページまで

それぞれのパーサの特徴がありますが値段を考えないパターンでは以下が一番良さそうではあります。

File Type パーサ 理由
PDF Layout Parser(Image Annotation,Table Annotation を利用) OCR の手もあるが、画像データ以外にデジタルデータが入っている場合も考えこちら *1
TXT Digital Parser 可用性がないということなので。マークダウンに関しては前もって html などの構造にしておくと良いかも
ソレ以外 Layout Parser 基本的には段落などを検出するほうが良い検索結果になりやすい

LayoutParser は取り込み時にお金がかかるため、大量のデータで構築する場合は注意が必要です。取り込み時に差分を見て、差分更新のような形で実装する必要があります。

https://docs.cloud.google.com/generative-ai-app-builder/docs/parse-chunk-documents

メタデータの設定

メタデータ付きの非構造化データ、または、構造化データについてはメタデータを設定できます。
基本的にメタデータは以下の用途で利用します。

  • 検索対象 (Searchable)
  • データの indexing(Database の index のイメージ、Filter で利用される)(Indexable)
  • 取得可能 (Retrievable)
  • オートコンプリート (Completable)
  • コンテキストに応じたフィルター (Dynamic facetable)

そもそもメタデータとしてつけられるフィールドの型は以下のとおりです

  • datetime
  • string
  • integer(整数値), number(小数値)
  • boolean
  • geolocation

これらを array, object で構造化することができます。

種類 つけられる型 制約
Indexable(インデックス) string, integer, number, datetime 50フィールドまで。種類、データが多すぎると登録や追加に時間がかかる様になる
Searchable string 50フィールドまで。検索精度に影響を与える(無関係な結果が返りやすくなる)
Retrievable すべて 50フィールドまで。多すぎるとレスポンスのサイズが多いのでレスポンスが遅くなる
Completable string 検索結果自体には影響がほとんどない(自然言語フィルタリングで影響を受けるというがよくわからない)
Dynamic facetable (Indexable にチェックがついている)string 50フィールドまで。検索結果自体には直接影響がない(Gemini Enterprise は自然言語フィルタリングを使うため影響を受けると思われる)

メタデータの更新は注意が必要です。
https://docs.cloud.google.com/generative-ai-app-builder/docs/update-schemas

  • フィールドの削除ができません
  • 型を変更することはできません
  • データの更新が走るので再度パーサの金額がかかる

メタデータについては基本以下の説明も合わせて読んでおくことをおすすめします。

https://docs.cloud.google.com/generative-ai-app-builder/docs/configure-field-settings
https://docs.cloud.google.com/generative-ai-app-builder/docs/provide-schema

自然言語での問い合わせ

構造化データに対して、自然言語でのフィルタまたは boost を行うことができます。これによって、運用者が意識せずとも高い検索結果を得られる可能性が上がります。(逆も然り)

フィルタは、検索結果から排除しますが boost は順位を上げるだけなので、基本的には boost を使って見るのが良いかと思います。

データストアに対してAPIを通して設定の更新を行うと24時間後に利用が可能になります。
https://docs.cloud.google.com/generative-ai-app-builder/docs/natural-language-queries#before-you-begin

注意点として、個人的にはまだ納得がいっていないのですが、メタデータ付き非構造化データに対しては対応されていません。
また、ブレンド検索アプリ(複数のデータソースに繋いでいるアプリ)では接続できません。

評価方法

Vertex AI Search の検索結果を評価することができます。
そもそもの評価の目的としては以下のようにドキュメントに記載されています。

  • 集計レベルで検索エンジンのパフォーマンスを測定する
  • クエリレベルでパターンを特定し、ランキングアルゴリズムの潜在的なバイアスや欠点を把握する
  • 過去の評価結果を比較して、検索構成の変更による影響を把握する

例えばチューニングとして、serving controlやメタデータの設定、boostの設定などを変更した際に有用です。
やはりデータは更新されていくものなので、データの追加はあるとしてもソレ以外の項目の更新でどう変わるのかを毎回ニンゲンの手で確認するのは骨が折れます。
実際評価を入れることで、運用面では個人的には、1つめのパターンの検証と、3つめの確認が一番大きいと思っています。
評価軸は複数あり、単純に検索結果の一致かどうかではないので利用価値はあると思います。
こちらの機能は Gemini Enterprise でも利用可能です。

https://docs.cloud.google.com/generative-ai-app-builder/docs/evaluate-search-quality

評価の構築方法

  1. テスト用のクエリ(テスト用のデータ)を作成する
  2. 作成したファイルを Vertex AI Search にインポートする
  3. Vertex AI Search に対して import の ID を指定して検索の精度評価を実行する。
  4. 結果を確認する

テストするクエリを作成する

基本的には以下の2つを1つのセットとして評価(テスト)します。

  • input の クエリ
  • クエリに対する想定している検索結果
    • ファイルの URI(web検索の場合はURL)
      • ただし pdf は page number も指定できる

このセットは最大で20000件作成できます。

sample.json
{
  "inlineSource": {
    "sampleQueries": [
      {
        "queryEntry": {
          "query": "2018 Q4 Google revenue",
          "targets": [
            {
              "uri": "gs://cloud-samples-data/gen-app-builder/search/alphabet-investor-pdfs/2018Q4_alphabet_earnings_release.pdf"
            },
            {
              "uri": "gs://cloud-samples-data/gen-app-builder/search/alphabet-investor-pdfs/201802024_alphabet_10K.pdf"
            }
          ]
        }
      },
      {
        "queryEntry": {
          "query": "2019 Q4 Google revenue",
          "targets": [
            {
              "uri": "gs://cloud-samples-data/gen-app-builder/search/alphabet-investor-pdfs/2019Q4_alphabet_earnings_release.pdf"
            }
          ]
        }
      }
    ]
  }
}

作成したファイルを Vertex AI Search にインポートする

作成したデータセットを保存する枠を作成します

枠の作成
PROJECT_ID="" # Google Cloud のプロジェクトのID
SAMPLE_QUERY_SET_ID="test-satohjohn-dataset-001" # テストデータセットの枠のID
SAMPLE_QUERY_SET_DISPLAY_NAME="Test SatohJohn Dataset 001" # テストデータセットの表示名

curl -X POST \
    -H "Authorization: Bearer $(gcloud auth print-access-token)" \
    -H "Content-Type: application/json" \
    -H "X-Goog-User-Project: $PROJECT_ID" \
    "https://discoveryengine.googleapis.com/v1beta/projects/$PROJECT_ID/locations/global/sampleQuerySets?sampleQuerySetId=$SAMPLE_QUERY_SET_ID" \
    -d '{
  "displayName": "$SAMPLE_QUERY_SET_DISPLAY_NAME"
}'

次に、作成した枠に対して、実データを作成したデータセットを import します。
データセットの置き場は、GCSかBigQuery、Localが設定できます。

localの場合
PROJECT_ID="" # Google Cloud のプロジェクトのID
SAMPLE_QUERY_SET_ID="test-satohjohn-dataset-001" # 先程設定した枠
INPUT_FILE_PATH="PATH/TO/FILE.json" # json

curl -X POST \
-H "Authorization: Bearer $(gcloud auth print-access-token)" \
-H "Content-Type: application/json" \
-H "X-Goog-User-Project: $PROJECT_ID" \
"https://discoveryengine.googleapis.com/v1beta/projects/$PROJECT_ID/locations/global/sampleQuerySets/$SAMPLE_QUERY_SET_ID/sampleQueries:import" \
--data @$INPUT_FILE_PATH

ここまでで設定は終わりです。
一応 import が終わったかどうかは、上記の API のresponse の name フィールドを使い以下のようにリクエストを送り確認できます。

処理の確認
OPERATION_NAME="" # 上記 API の response のnameフィールドの値

curl -X GET \
-H "Authorization: Bearer $(gcloud auth print-access-token)" \
"https://discoveryengine.googleapis.com/v1beta/$OPERATION_NAME"

Vertex AI Search に対して import の ID を指定して検索の精度評価を実行する

実際にクエリは以下のようになります。これは基本1日5回/1プロジェクト実施できます。

PROJECT_ID=""
APP_ID="" # Vertex AI Search の検索アプリケーションのID
SAMPLE_QUERY_SET_ID="test-satohjohn-dataset-001" # すでに設定されているクエリセットのID

curl -X POST \
-H "Authorization: Bearer $(gcloud auth print-access-token)" \
-H "Content-Type: application/json" \
-H "X-Goog-User-Project: $PROJECT_ID" \
"https://discoveryengine.googleapis.com/v1beta/projects/$PROJECT_ID/locations/global/evaluations" \
-d '{
 "evaluationSpec": {
   "querySetSpec": {
     "sampleQuerySet": "projects/'$PROJECT_ID'/locations/global/sampleQuerySets/'$SAMPLE_QUERY_SET_ID'"
   },
   "searchRequest": {
     "servingConfig": "projects/'$PROJECT_ID'/locations/global/collections/default_collection/engines/'$APP_ID'/servingConfigs/default_search"
   }
 }
}'

ここの結果で返って来る response の response.name を使い評価の進捗状況や結果を確認します。まぁまぁ時間かかります。

EVALUATION_NAME="" # 上記 API の response の response.name フィールドの値

curl -X GET \
-H "Authorization: Bearer $(gcloud auth print-access-token)" \
-H "Content-Type: application/json" \
-H "X-Goog-User-Project: $PROJECT_ID" \
"https://discoveryengine.googleapis.com/v1beta/$EVALUATION_NAME"

結果を確認する

先程の結果が SUCCESS になるとレスポンスの中に以下のように結果が入ってきます

"qualityMetrics": {
       "docRecall": {
         "top1": DOC_RECALL_TOP_1,
         "top3": DOC_RECALL_TOP_3,
         "top5": DOC_RECALL_TOP_5,
         "top10": DOC_RECALL_TOP_10
       },
       "docPrecision": {
         "top1": DOC_PRECISION_TOP_1,
         "top3": DOC_PRECISION_TOP_3,
         "top5": DOC_PRECISION_TOP_5,
         "top10": DOC_PRECISION_TOP_10
       },
       "docNdcg": {
         "top1": DOC_NDCG_TOP_1,
         "top3": DOC_NDCG_TOP_3,
         "top5": DOC_NDCG_TOP_5,
         "top10": DOC_NDCG_TOP_10
       },
       "pageRecall": {
         "top1": PAGE_RECALL_TOP_1,
         "top3": PAGE_RECALL_TOP_3,
         "top5": PAGE_RECALL_TOP_5,
         "top10": PAGE_RECALL_TOP_10
       },
       "pageNdcg": {
         "top1": PAGE_NDCG_TOP_1,
         "top3": PAGE_NDCG_TOP_3,
         "top5": PAGE_NDCG_TOP_5,
         "top10": PAGE_NDCG_TOP_10
        }
      }

イメージは top1, top3 が各個数を取ったときに、ページ単位かドキュメント単位でどれぐらい一致しているかを表していると思っていただくのが良いかと思います
各項目は以下の通りの意味を持ちます。

  • docRecall

    世の中にある正解のうち、どれだけ見つけ出せたか。例えば、図書館に探している本が全部で10冊あるとします。検索して出てきた結果の中に、その10冊のうち何冊が含まれていたか、というスコアです。

  • pageRecall

    docRecall の page版です。そのため pdf のときにしか使われません。
    当たり前ですが、テストでpageを指定していない場合はここの値はでてきません

  • docNdcg

    満点(理想的な並び順)に対して、今回の結果は何点か。例えば、答えに「超正解(3点)」「まあまあ正解(1点)」「ハズレ(0点)」というふうに、点数(評価値)をつけます。

  • pageNdcg

    docNdcg の page版です。そのため pdf のときにしか使われません。
    当たり前ですが、テストでpageを指定していない場合はここの値はでてきません

  • docPrecision

    検索結果の中に、どれだけ正解(ノイズではないもの)が含まれているか。例えば、あなたが「おすすめのマンガ」を10個(k=10)教えてもらったとき、そのうち本当に面白い(自分が探していた)マンガが何個あったか、というスコアです。

ちなみに、うまくこれについて自身で評価できない場合は、Gemini に投げて解説してもらうことをおすすめします。
元のGoogle Cloudのドキュメントと一緒に結果を貼り付けると以下のように分析をしてくれます。

結果説明
プロンプト例
https://docs.cloud.google.com/generative-ai-app-builder/docs/evaluate-search-quality?hl=ja#interpret-results

ここに書いてある指標を元に評価を行いました。

以下の結果を中学生でもわかりやすく説明をしてください。

-----
    "docRecall": {
      "top1": 0.5,
      "top3": 0.5,
      "top5": 0.5,
      "top10": 0.5
    },
    "docPrecision": {
      "top1": 0.5,
      "top3": 0.5,
      "top5": 0.5,
      "top10": 0.5
    },
    "docNdcg": {
      "top1": 0.5,
      "top3": 0.5,
      "top5": 0.5,
      "top10": 0.5
    }
-----
結果
提示された数値は、すべての指標(Recall, Precision, NDCG)が top1 から top10 まで一貫して 「0.5(50%)」 で固定されているという、非常に特徴的な状態です。

専門的な視点から、この結果を「AI検索くんの成績表」として中学生にもわかるように詳しく分析・診断します。

1. 全体的な評価: 「安定しているが、大きな壁にぶつかっている」
すべての数値が 0.5 で横ばいということは、一言で言うと**「最初の1件目で半分正解を見つける実力はあるが、その後どれだけ探させても残りの半分が見つけられない」**という状態です。

普通、優秀な検索エンジンは、探す範囲(top3, top5...)を広げれば広げるほど、正解を見つける確率(Recall)は上がり、逆に関係ないものが混ざる確率(Precision)は下がっていく傾向があります。しかし、このデータはそうなっていません。

2. 各指標の詳しい診断
① docPrecision(適合率)がすべて 0.5
分析: 1番目を見ても、10番目まで見ても、**「出された結果のうち半分が正解で、半分がハズレ」**という状態です。

中学生向けの解説: 10問のクイズを出して、1問目も正解。でも2問目はハズレ、3問目は正解、4問目はハズレ……という風に、常に「2回に1回は間違える」という、精度のムラがない(ある意味安定した)状態です。

② docRecall(再現率)がすべて 0.5
分析: ここが最も重要なポイントです。top1 で 0.5 なのに、top10 に増やしても 0.5 のままです。

中学生向けの解説: 「世の中に正解が2つある」としたとき、AIくんは最初の1回目で1つ目を見つけましたが、その後の10回目まで探しても2つ目の正解をどうしても見つけられなかった、ということです。 つまり、「あと一歩の頑張り」が効かない、深刻な**「見落とし(検索漏れ)」**が発生しています。

③ docNdcg(ランキングの質)がすべて 0.5
分析: 理想的な並び順(一番いい答えが一番上)に対して、常に50点満点という評価です。

中学生向けの解説: 「一番大事なことを最初に言う」というルールは守れているけれど、情報の質が全体的に「最高」の半分くらいで止まってしまっています。

3. AI検索くんに何が起きているのか?(原因の推測)
このスコアの推移から、AIの設定(Vertex AI Searchの内部)で以下のことが起きている可能性が高いです。

「正解の一部」しか学習・把握できていない

質問に対して、特定のパターンの答えは見つけられる(だから top1 で 0.5 出る)けれど、別のパターンの答え(残り 0.5 分)は、AIの検索範囲外(インデックスされていない、またはベクトル計算で遠すぎる)にあります。

検索の「広がり」が足りない

top1 から top10 に範囲を広げても Recall が増えないのは、AIが「似たような答え」ばかりを上位に並べてしまい、新しい正解を掘り起こせていないことを示唆しています。

4. どうやって改善すべきか?(アドバイス)
もしあなたがこのAIの先生(開発者)なら、以下の対策を提案します。

「もっと広く探すように言う」 (ハイパーパラメータの調整): 検索の際、もっと多様な候補を拾ってくるように、検索アルゴリズム(ベクトル検索のしきい値など)を調整する必要があります。

「教科書(データ)を増やす」: 見つけられていない残りの 50% の正解データが、そもそもAIが読める場所に正しく入っているか確認します。

「質問の理解力を上げる」: ユーザーが言いたいことをAIが半分しか理解できていない可能性があるため、プロンプト(指示文)の書き方や、検索モデル(Embedding)をより高性能なものに変更することを検討します。

結論
この結果は、**「瞬発力はある(top1の成績は悪くない)が、粘り強さがない(範囲を広げても成果が増えない)」**という評価になります。 「とりあえず半分は確実に答えてくれる」という安心感はありますが、ビジネスで使うには「見逃している残り半分」をどう捕まえるかが次の課題です。

認証認可方法

Vertex AI Search に対するアクセス自体は IAM で制御します。
ただし、Vertex AI Search のドキュメントに対してはドキュメントごとに閲覧権限を設定する必要があります。これは非構造化データだけでは設定できず、メタデータと同じようにパラメータを設定することで対応ができます。
例えば以下のように acl_info として作成します。

{
   "id": "<your-id>",
   "jsonData": "<JSON string>",
   "content": {
     "mimeType": "<application/pdf or text/html>",
     "uri": "gs://<your-gcs-bucket>/directory/filename.pdf"
   },
   "acl_info": {
     "readers": [
       {
         "principals": [
           { "group_id": "group_1" },
           { "user_id": "user_1" }
         ]
       }
     ]
   }
 }

認証元のデータソースとしては Google Identity や Workforce Identity を使うことができます。そのため Google Identity では上記の user, group には email を入れる形になります。
ここは利用している IdP に合わせて実装してください。 Workforce Identity での設定については以下も参考にしてください。
https://zenn.dev/satohjohn/articles/a422ee68dd3485

https://docs.cloud.google.com/generative-ai-app-builder/docs/data-source-access-control?hl=ja

運用していて思ったこと

  • 検索するうえで、メタデータが大事。どういうメタデータを作るかを戦略立てる。
  • Layout parser が有用だがコストがかかるので、ディレクトリを分ける、特定のファイル拡張子だけ Digital parser にするなどで調整を行う。
  • ACL + 3rd Party の IdP を使っている場合、Workforce Identity を覚える必要があり難易度がもう一段上がる。Google Identity ならそんなに難しくはない。

良い点

  • 慣れれば設定自体は難しくなく構築が可能(データの前処理は別)
    • スタートのベクトル検索での PoC ではこれを試すで全く問題ないと思う
  • コストが低い(小規模なデータレベル 数万件レベル)であれば月1万円行くかどうか
    • 下手に Cloud SQL を建てて Vector DB 組み立てるより安い
    • 1ファイルの大きさによるが 1万件ぐらいのファイルであれば、利用回数(クエリ数)次第ではお金がかからず行ける
  • UI も用意してくれる
    • 基本はこれで十分

悪い点

  • ブラックボックスで項目が多すぎる
    • カスタムのチャンク周りが、public ででてくればイメージ付きやすい
  • 速度のチューニングなどができない。
  • ドキュメントを削除する方法が全削除しかないように見える
    • 実際は API があるように見えるがドキュメントとして掲載していないのを見るとあまり推奨していないのか?

ソレ以外

  • 名前がコロコロ変わる。少なくとも私が使っていて3回変わった。
  • Vertex AI の中に入っているのがよくわからない。

Discussion