こんにちは!ダンです!
皆さん、Google Cloud の Vertex AI Search を使ったことはありますか?最近RAGを開発した際に、ベクトルDBをChromaDBからVertex AI Searchのデータストアに移行してみました。作業中にスムーズに設定できず困ったところがあったため、対処法を共有したいと思っています。
まず、概念から行きましょう!
Vertex AI Searchとは
自社データ(ウェブサイト、PDF、データベースなど)を使って、Google検索品質の高度な検索機能やAIチャットボットを簡単に構築できるフルマネージドサービスです。
デベロッパーは、ウェブサイト、イントラネット、RAGシステム向けの生成AIエージェントとアプリで、安全かつ高品質なGoogleの検索体験を構築できます。参考
フルマネージドサービスのため、RAG構築時のナレッジ管理やデータ読み込みの仕組みも簡単になります。
データストアとは
企業が持つ文書、ウェブサイト、データベースなどの多様な情報をVertex AI Searchに取り込み、検索や質問応答、要約などのAIアプリの基盤として利用するためのデータのリポジトリ(保管場所)です。Googleドライブ、Cloud Storage、SharePoint、Salesforce、ウェブサイト、データベースなど、様々なソースからデータを統合できます。これにより、ドキュメント管理の負荷をある程度軽減できます。参考
RAGシステムにおけるナレッジの保管場所として機能すると理解して良いでしょう。
背景
現在、設計書の中からQ&A形式内容を取得できるRAGを構築しています。生成AIによるドキュメント検索アプリを構築しており、ベクトルDB作成にはChromaDBを利用しています。
ChromaDBは柔軟性が高い一方で、チャンキング戦略の手動調整やインデックスの運用など、開発者が管理すべき範囲が広く、管理負荷が高いという課題がありました。そこで、これらの運用負担を大幅に軽減できるフルマネージドプラットフォームであるVertex AI Searchへの移行を検討しました。
まず、今回はCloud Storageのバケットに格納されたデータをデータストアにインポートしたいです。また、ドキュメントを検索する際に、ドキュメントの中身だけでなく、メタデータもフィルターとして検索対象を絞り込んでいきたいと思っています。
現状
Vertex AI Search上にデータストアを作成する際、メタデータを含まない非構造化データの場合は、ドキュメントを格納するバケットをフォルダとして指定するだけで作成できます。以下のイメージです。

しかし、メタデータを含む非構造化データの場合は、そう単純にはいきません。
この場合、非構造化データへのリンクを含むJSONLファイルを事前に用意する必要があります。詳細はこちらを参考にしてください。

公式ドキュメントを参考にしたところ、JSONファイルには基本的にidとcontentのフィールドが必須です。
- id:識別子として使用。自由な文字列で作成可能
- content:ドキュメントコンテンツの概要
カスタマイズメタデータはjsonDataまたはstructDataのフィールドに記載できます。
Vertex AI Searchは、ドキュメントの基本情報と検索・フィルタリングに特化したメタデータを明確に分離して扱います。idとcontentでドキュメントの基本情報や内容を定義し、jsonDataまたはstructDataで柔軟なフィルタリングや検索ロジックを実現します。
{"id":"doc-0","jsonData":"{\\"title\\":\\"test_doc_0\\",\\"description\\":\\"This document uses a blue color theme\\",\\"color_theme\\":\\"blue\\"}","content":{"mimeType":"application/pdf","uri":"gs://test-bucket-12345678/test_doc_0.pdf"}}
{"id":"doc-1","jsonData":"{\\"title\\":\\"test_doc_1\\",\\"description\\":\\"This document uses a green color theme\\",\\"color_theme\\":\\"green\\"}","content":{"mimeType":"application/pdf","uri":"gs://test-bucket-12345678/test_doc_1.pdf"}}
{"id":"doc-2","structData":{"title":"test_doc_2","description":"This document uses a red color theme","color_theme":"red"},"content":{"mimeType":"application/pdf","uri":"gs://test-bucket-12345678/test_doc_3.pdf"}}
{"id":"doc-3","structData":{"title":"test_doc_3","description":"This is document uses a yellow color theme","color_theme":"yellow"},"content":{"mimeType":"application/pdf","uri":"gs://test-bucket-12345678/test_doc_4.pdf"}}
メタデータ含む非構造化のデータストア作成のポイント
データストア作成の詳細な手順は公式ドキュメントに記載されているため、ここでは注意点・ポイントのみを挙げます。
メタデータファイル作成
手動でメタデータのJSONファイルを作成するのは複雑なため、Pythonコードで自動生成しました。バケットに格納されたドキュメントのデータを抽出し、メタデータとして作成しています。
注意: contentフィールド内のuriはドキュメントの物理的な場所を示すものですが、検索時のフィルターとしては直接使用できません。
Vertex AI Searchの設計上、contentフィールドはドキュメントの内容表示や関連付けのために利用され、フィルタリングのロジックはstructDataやjsonDataで定義されたカスタムフィールドにのみ適用されます。そのため、もしURIでフィルタリングしたい場合は、下記のサンプルコードのようにfile_pathなどのカスタムフィールドとしてstructData内にURI情報を重複して記述する必要があります。
今回はファイルパス(uri)をフィルタリングが目的のため、シンプルなメタデータを作成しました。他の情報も取り込みたい場合は自由に修正してください。また、作成後に自動的にバケットにアップロードする仕組みも加工可能かと思います。
以下のサンプルコードを参考にしてください!
import json
import os
from google.cloud import storage
def generate_gcs_manifest(bucket_name, output_filename="metadata.jsonl"):
"""
GCS バケット内のファイルのメタデータを含む NDJSON (JSON Lines) ファイルを生成します。
各行のNDJSON形式:
{"id":"doc-xxx","structData":{"title":"...","file_path":"gs://...","description":"..."},"content":{"mimeType":"...","uri":"gs://..."}}
Args:
bucket_name (str): スキャンするGCSバケットの名前
output_filename (str): 生成するNDJSONファイルの名前
"""
client = storage.Client()
bucket = client.bucket(bucket_name)
manifest_data = []
doc_counter = 1
print(f"バケット内のファイルをスキャン中: {bucket_name}...")
# バケット内の全ファイル(blob)を一覧表示
blobs = bucket.list_blobs()
for blob in blobs:
# ディレクトリ(通常は'/'で終わる)またはサイズが0バイトのファイルをスキップ
if blob.name.endswith('/') or blob.size == 0:
continue
file_extension = os.path.splitext(blob.name)[1].lower()
# PDFではないファイルをスキップ
if file_extension not in [".pdf"]:
print(f" - スキップ")
continue
file_title = os.path.splitext(os.path.basename(blob.name))[0] # 拡張子なしのファイル名を取得
# ファイルタイプに基づいてdescriptionとmime_typeを設定
if file_extension == ".pdf":
description = "設計書"
mime_type = "application/pdf"
# GCS URIを生成
gcs_uri = f"gs://{bucket_name}/{blob.name}"
# IDを自動生成 (doc-1, doc-2, ...)
doc_id = f"doc-{doc_counter}"
doc_counter += 1
entry = {
"id": doc_id,
"structData": {
"title": file_title,
"file_path": gcs_uri,
"description": description
},
"content": {
"mimeType": mime_type,
"uri": gcs_uri
}
}
manifest_data.append(entry)
print(f" - 追加: {blob.name} (説明: {description}, MimeType: {mime_type})")
# NDJSONファイルにデータを書き込み
try:
with open(output_filename, 'w', encoding='utf-8') as f:
for entry in manifest_data:
f.write(json.dumps(entry, ensure_ascii=False) + '\\n')
print(f"\\nNDJSONファイルの生成完了: {output_filename}")
print(f"マニフェストに追加された項目数: {len(manifest_data)}")
except IOError as e:
print(f"ファイル書き込みエラー {output_filename}: {e}")
上記のコードを実行すると、以下のようなJSONファイルが生成されます。
{"id": "doc-1", "structData": {"title": "01_システム構成仕様書", "file_path": "gs://test_bucket/設計書/01_システム構成仕様書", "description": "設計書"}, "content": {"mimeType": "application/pdf", "uri": "gs://test_bucket/設計書/01_システム構成仕様書"}}
{"id": "doc-2", "structData": {"title": "02_データベース設計書", "file_path": "gs://test_bucket/設計書/02_データベース設計書", "description": "設計書"}, "content": {"mimeType": "application/pdf", "uri": "gs://test_bucket//設計書/02_データベース設計書"}}
メタデータ(JSONファイル)を作成したら、任意のバケットにそのファイルを格納してください。
※ ドキュメントとメタデータは同じバケットに格納する必要はありません。
完了したら、GUIでデータストアを作成しましょう!
インポート時の注意点
メタデータを含む非構造化データストアを作成する際は、フォルダではなく、JSONファイルを直接指定する必要があります。この点が公式ドキュメントに記載がなかったため最初は気づかず、フォルダを指定してしまい、何度もエラーが発生しました。
システムは指定されたJSONファイルを読み込み、対象ドキュメントと格納場所を認識します。この仕組みを理解しておきましょう。
イメージは以下通りです。

結果
メタデータをインポートしてデータストアを作成すると、定義したドキュメントが自動的に読み込まれます。
読み込まれたドキュメントは「ドキュメント」タブで、メタデータは「スキーマ」タブで確認できます。
- ドキュメント一覧


description、file_path、titleの3つのフィールドがメタデータとして読み込まれました。作成後、GUIでスキーマを確認しましょう。
「検索可能」は、検索時にフィルターとして使用できるかどうかを示す項目です。データストア作成後は、この設定が有効になっているか必ず確認してください。
デフォルトでは全フィールドがオンになっていますが、オフになっているとそのフィールドでフィルターできなくなるため注意が必要です。
オフになっている場合は、「スキーマ」から直接修正できます。
設定値の詳細についてはこちらを参考にしてください。
REST APIで確認
REST APIを使用して、データストア内の検索時にuriでフィルタリングできるか確認しました。

以下の画像の通り、フィルタリングに成功しました!

注意点
作成直後に試してみましたが、フィルターが効かない場合がありました。原因は不明ですが、初期構築時にローディング時間がかかる可能性があるため、慌てず5〜10分ほど待ちましょう。
その後、正常に動作するはずです。これも重要なポイントです。
まとめ
今回は、メタデータを含む非構造化データストアをVertex AI Search上で作成しました。メタデータの用意とGUIでの操作は、メタデータを含まないデータストアに比べて少し複雑です。
本記事で解説した点をおさえておけばスムーズすすめられると思います。
一緒に生成AIを楽しく活用しましょう!