🍺

実践 Vertex AI Search:「関連性」をマスターする

2025/01/27に公開

1. はじめに

株式会社 Hogetic Lab のエンジニア 古川です。

Google Cloud の Vertex AI Search は、RAG (Retrieval-Augmented Generation) を手軽に構築できるフルマネージド プラットフォームです。
Vertex AI Search では、検索結果を要約して回答を生成することができます。
簡単な設定で実現できるのはメリットですが、一見するともっともらしい回答でも、内容に誤りがある、つまり「回答精度」が低い場合があります。

この「回答精度」を改善するには、どうすれば良いでしょうか?

一般的に、RAG の「回答精度」は「検索精度」と「生成精度」の二つの要素に分解できます。
「生成精度」はプロンプトを磨きこむことで改善しますが、「検索精度」はどのように改善すれば良いでしょうか?

今回は、「検索精度」の改善を助けるために有用な Vertex AI Search の「関連性 (Relevance)」を紹介します。

2. 関連性 (Relevance) とは

Vertex AI Search では、質問に対して関連しているドキュメントの一部をチャンク (Chunk) や抽出セグメント (Extractive Segment) を検索結果として返します。
その検索結果を元にして 生成 AI が回答を作成するので、検索結果に的外れなものが多数含まれていると、プロンプトチューニングだけでは改善は見込めません。

この検索結果が、質問とどれだけ関係があるか示すのが関連性スコア (Relevance Score) です。

Vertex AI Search では、データストアの種類および設定によっては、関連性スコアが取得できなかったり、レスポンス内の含まれる位置が異なります。

例えば、BigQuery のテーブルなどを取り込んで作成した構造化データでは関連性スコアは取得できません。

また、PDFファイル等を取り込んで作成した非構造化データでも、データストア作成時に「高度なチャンク構成」の設定に応じて、リクエスト時のパラメータを適切に設定する必要があります。

以下で、具体的なリクエスト設定とレスポンスについて解説します。

なお、チャンクの具体的な設定については、公式サイトのドキュメントを解析してチャンク処理するを参照してください。

2-1. チャンクを設定しないデータストアの場合

あらかじめファイルが分割されている場合など、チャンク構成を無効にしたデータストアを作成している場合、SearchRequest() のパラメータ content_search_spec 内で return_extractive_segment_score を True に設定することで、抽出セグメント (Extractive Segment) の関連性スコア (Relevance Score) が取得できます。

設定の追加箇所
※ 全体のコードは コードサンプル を参照してください。

    content_search_spec = discoveryengine.SearchRequest.ContentSearchSpec(
        snippet_spec=discoveryengine.SearchRequest.ContentSearchSpec.SnippetSpec(
            return_snippet=True
        ),
        summary_spec=discoveryengine.SearchRequest.ContentSearchSpec.SummarySpec(
            ...
        ),
        # search_result_mode はデフォルトが DOCUMENTS であるため記載しなくても良い
        search_result_mode=discoveryengine.SearchRequest.ContentSearchSpec.SearchResultMode.DOCUMENTS,
        extractive_content_spec=discoveryengine.SearchRequest.ContentSearchSpec.ExtractiveContentSpec(
            return_extractive_segment_score=True, # 関連スコアを返す
        ),        
    )

レスポンス抜粋

レスポンスを print() し、一つの document を抜粋して紹介します。

results {
  id: "b22040049"
  document {
    name: "projects/MY_PROJECT_NUMBER/locations/global/collections/default_collection/dataStores/MY_DATASTORE/branches/0/documents/b22040049"
    id: "b22040049"
    struct_data {
      ...
    }
    derived_struct_data {
      fields {
        key: "snippets"
        value {
            ...
          }
        }
      }
      fields {
        key: "link"
        value {
          ...
        }
      }
      fields {
        key: "extractive_segments"
        value {
          list_value {
            values {
              struct_value {
                fields {
                  key: "relevanceScore"
                  value {
                    number_value: 0.83110570907592773
                  }
                }
                fields {
                  key: "content"
                  value {
                    "{{この回答の中身の関連性スコアが relevanceScore として追加される}}"
                  }
                }
              }
            }
          }
        }
      }
    }
  }
}

2-2. チャンクを設定したデータストアの場合

複数ページの PDF ファイルなどのドキュメントを使い、チャンク構成を有効にしたデータストアを作成している場合、SearchRequest() のパラメータ content_search_spec 内で search_result_mode を CHUNKS に設定してリクエストします。(なお、DOCUMENTS で設定するとチャンクを設定しないデータストアの場合と同じく抽出セグメントの関連性スコアを取得することもできます。)
また、関連性の高いチャンクを使用して要約を生成する設定もあるので、その設定も追加します。

設定の追加箇所

    content_search_spec = discoveryengine.SearchRequest.ContentSearchSpec(
        snippet_spec=discoveryengine.SearchRequest.ContentSearchSpec.SnippetSpec(
            return_snippet=True
        ),
        summary_spec=discoveryengine.SearchRequest.ContentSearchSpec.SummarySpec(
            ...
            use_semantic_chunks=True, # チャンクを使用して要約を生成する
        ),
        # CHUNKS を設定する
        search_result_mode=discoveryengine.SearchRequest.ContentSearchSpec.SearchResultMode.CHUNKS,
    )

レスポンス抜粋
レスポンスの一つの chunk を抜粋して紹介します。

results {
  chunk {
    name: "projects/MY_PROJECT_NUMBER/locations/global/collections/default_collection/dataStores/MY_DATASTORE/branches/0/documents/b22040114/chunks/c1"
    id: "c1"
    content: "{{この回答の中身の関連性スコアが relevance_score }}"
    document_metadata {
      ...
    }
    relevance_score: 0.759391188621521
  }
}

3. 関連性フィルタを設定する

AIによる要約生成時に、関連性が低い検索結果を除くフィルタ設定もあります。

この設定は関連性スコアの値での指定ではなく、 HIGH(関連性の高い検索結果に限定)、 MEDIUMLOW (関連性の低い検索結果も含まれる)のように指定します。
関連性スコアの閾値を決めるのは難しいため、その判定も Vertex AI Search にお任せすることができます。

    request = discoveryengine.SearchRequest(
        serving_config=serving_config,
        query=search_query,
        page_size=10,
        content_search_spec=content_search_spec,
        query_expansion_spec=discoveryengine.SearchRequest.QueryExpansionSpec(
            condition=discoveryengine.SearchRequest.QueryExpansionSpec.Condition.AUTO,
        ),
        spell_correction_spec=discoveryengine.SearchRequest.SpellCorrectionSpec(
            mode=discoveryengine.SearchRequest.SpellCorrectionSpec.Mode.AUTO
        ),
        # "LOW", "MEDIUM", "HIGH"から選択
        relevance_threshold=discoveryengine.SearchRequest.RelevanceThreshold.MEDIUM,
    )

※ 記事作成時点でプレビューサービスです。Python のモジュールとしては discoveryengine_v1beta を使う必要があります。

この設定は非構造化データだけでなく、構造化データでも使用できます。

公式ドキュメントにも記載がありますが、HIGH で条件が厳しい場合は MEDIUM にしてみてください。(データストアや質問に依存するとは思いますが、MEDIUM だと設定しなくてもあまり大きな違いがない印象でした。)

また、筆者が試してみたところ、チャンク設定なしの非構造化データと構造化データでは動作しましたが、チャンクを設定した非構造化データではエラーになりました。

適用例

これは個人的な趣味の話になりますが、私はクラフトビールが大好きで、飲んだビールを記録しています。今回は、それらのデータを使い、ビアスタイル(ビールの種類のこと)の判定を試します。
まず、クラフトビア・アソシエーション 日本地ビール協会が公開している ビアスタイルガイドライン2204 を表形式にまとめた構造化データを使った結果を紹介します。

質問

インペリアルスタウトについて説明してください

検索結果(件数)

  • 関連性フィルタなし:検索結果は 181 件
  • 関連性フィルタ HIGH:検索結果は 4 件

回答

インペリアルスタウトは、高アルコール度数のスタウトビールです。 [1, 2] ブリティッシュスタイルとアメリカンスタイルの2種類があり、それぞれ特徴が異なります。
ブリティッシュスタイル・インペリアルスタウトは、色は濃い銅色から非常に濃い色まで幅があります。 [1]濃い色のものは不透明で、透明度がある場合は冷温白濁があっても問題ありません。 [1]フルーティーなエステルは中程度から高い場合がありますが、なくても構いません。 [1]ホップのアロマとフレーバーは非常に低めから中程度で、フローラル、シトラス、またはハーブのような香りがします。 [1]ホップの苦味は中程度で、全体のバランスを崩すほど強くはありません。 [1]濃い色のものほど苦味が強い場合がありますが、モルトの甘味とバランスが取れている必要があります。 [1]モルトのフレーバーは非常に濃厚で、タフィーキャンデーやカラメルのような風味があります。 [1]ローストモルト由来の渋味は非常に少ないです。 [1]アルコール度数の高さは口の中で感じられます。 [1]ボディはフルボディです。 [1]かつては「ロシアン・インペリアルスタウト」と呼ばれていました。 [1]アルコール度数は6.9~11.9%、苦味(IBU)は45~65、色はEBCで40~70以上、SRMで20~35以上です。 [1]
アメリカンスタイル・インペリアルスタウトは、不透明で真っ黒な色をしています。 [2]フルーティーなエステルは高いです。 [2]モルトのアロマとフレーバーは非常に濃厚で、強い甘味があります。 [2]ローストモルト由来の渋味と苦味は中程度ですが、全体のキャラクターを支配するほど強くはありません。 [2]ホップのアロマとフレーバーは中高程度から高いです。 [2]ホップの苦味は中高程度から非常に高く、濃厚なモルトのキャラクターとバランスが取れています。 [2]アルコール度数は6.9~11.9%、苦味(IBU)は50~80、色はEBCで80以上、SRMで40以上です。 [2]
どちらのスタイルも、ダイアセチルは非常に少ない方が好ましいです。 [1, 2]ボディはフルボディです。 [1, 2]
このように、インペリアルスタウトは、その濃厚なモルトの風味と高アルコール度数、そしてブリティッシュスタイルとアメリカンスタイルによる異なる特徴が魅力のビールです。

上の回答は「関連性フィルタなし」のものですが、どちらも要約に含まれた引用は 2 件で(要約件数: summary_result_count=5 で設定)、内容はほぼ同じでした。
要約時に LLM にて質問への回答に近いものが選択されたものと思われます。
この質問に関しては関連性フィルタ有無の影響はありませんでしたが、プロンプトや質問によっては回答に違いが出てくるかと思います。

4. 応用 - ビアスタイルの判定 -

クラフトビールには多様なビアスタイルがあり、さらにブルワーが自分のビールの特徴を表現するため自由にビアスタイル名をつけるため、分析するためには標準化が必要になります。
以下のビールメモを元に、 ビアスタイルガイドライン2204 のビアスタイルを判定してみます。

場所: サンクトガーレンタップルーム
醸造所(記録): サンクトガーレン
ビール名: インペリアルチョコレートスタウト2023
パッケージ: tap
アルコール度数: 9.0
フレーバーレベル: 3.5
苦味レベル: 4.0
甘味レベル: 3.0
ボディレベル: 3.0
ボディ色コード: 24
ボディの濁り: unsure
原材料: 麦芽(チョコレート麦芽使用)、ホップ 
フレーバー備考: ダークビターなチョコレート
第一印象: マイルドでクリーミーな口当たり。とにかくダークビター!!!
炭酸の印象: 弱め
特徴的な印象: 口当たりがマイルドで飲みやすいが9%という超危険なヤツ

インペリアルチョコレートスタウト2023

回答

記述されているビールの特徴から、最も近いビアスタイルは「ブリティッシュスタイル・インペリアル・スタウト」です。 [4] アルコール度数9.0%は、ブリティッシュスタイル・インペリアル・スタウトのアルコール度数範囲(5.5%-9.5%)に含まれます。 [4] また、「ダークビターなチョコレート」というフレーバー備考[ユーザー入力]と、「きわめて濃厚で、タフィーキャンデーやカラメルを思わせるアロマが強調されているものが多い」という記述は一致しています。 [4]さらに、「ボディはフル」という記述も、「ボディレベル: 3.0」[ユーザー入力](3.0はミディアム〜フルの範囲と解釈できる)と概ね合致しています。 [4]苦味レベル4.0[ユーザー入力]も、ブリティッシュスタイル・インペリアル・スタウトの苦味(45-65 IBU)と比較して妥当な範囲です。 [4] 色度数も、ボディ色コード24[ユーザー入力]は、ブリティッシュスタイル・インペリアル・スタウトの色度数範囲(20-35+ SRM)と一致する可能性が高いです。 [4] 「マイルドでクリーミーな口当たり」[ユーザー入力]という記述は、スイートスタウトやクリームスタウトの特徴にも合致する可能性がありますが、アルコール度数9.0%という点で、ブリティッシュスタイル・インペリアル・スタウトの方がより適切です。 [1]スイートスタウトやクリームスタウトはアルコール度数が低く(2.5%-5.0%)設定されているためです。 [1] チョコレートビールの可能性も考えられますが、ベースとなるビールスタイルが不明なため、判断が困難です。 [5] また、他のスタイル(イングリッシュスタイル・マイルドエールなど)はアルコール度数やボディ、色合いが記述と一致しません。 [2]

名前からだけでなく、特徴も加味してビアスタイルが選択されました。
検索結果のドキュメントの関連性スコアは以下の通りです。

ドキュメントタイトル 関連性スコア
86. British-Style Imperial Stout; 86.ブリティッシュスタイル・インペリアル・スタウト 0.8167822957038879
84. Sweet Stout or Cream Stout; 84.スイート・スタウトまたはクリーム・スタウト 0.8055486083030701
1. American Wheat Beer; 1.アメリカンスタイル・ウィートビール 0.7760947942733765
12. Chocolate Beer; 12.チョコレートビール 0.7776080965995789
75. English-Style Mild Ale; 75.イングリッシュスタイル・マイルドエール 0.7829424142837524

(参考)設定メモ
構造化データ・非構造化データ、チャンク有無などパラメータを変更して試し、最終的に以下の設定で確定しました。

  • 非構造データ (元データ:HTMLファイル)
  • チャンク設定: あり (500トークン)
  • サマリーに使用する件数: 5件
  • 関連性の高いチャンクを使用して要約を生成する: あり
  • 関連性フィルタ: なし(デフォルト)

5. まとめ

今回は Vertex AI Search の検索結果の関連性 (Relevance) について紹介しました。

回答結果で一喜一憂するのではなく、関連性スコアを把握して検索精度を意識することにより、回答精度の改善策に繋げられますのでご活用ください。

Vertex AI Search は高度な RAG 機能を手軽に実装できるのが魅力ですが、多機能かつ設定の組み合わせが多くて複雑なため、自分のシステムにあった機能と設定を見つけるのには試行錯誤を伴います。

引き続き、実際のデータを使って機能と設定を検証し、その結果を共有いたしますので、皆様の Vertex AI Search 活用にお役に立てば幸いです。

※ データ分析や AI 活用に関するご相談は、以下よりお気軽にお問い合わせください。

お問い合わせフォーム

6. 参考情報

Hogetic Lab

Discussion