Vertex AI Searchで「CSV(よくある質問のデータ用)」がセマンティック検索される条件を調査してみた
背景
Vertex AI Searchの構造化データストア(CSV)にFAQデータを入れて検索してみたところ、検索精度が悪く、言い回しが異なるクエリでは適切な結果が得られませんでした。
たとえば、「複数のアカウントを作成することは問題ないか」というクエリで、「一人が複数アカウントを持つのは規約違反か」というFAQにマッチしてほしいのですが、ヒットしません。
検索APIのレスポンスを確認してみると、semanticState: DISABLEDが返ってきていました。セマンティック検索が無効になっており、キーワードマッチングしか行われていないようです。
この問題を解決するため、いくつかのアプローチを検証しました。
検証環境
- Enterprise edition: 有効(
SEARCH_TIER_ENTERPRISE,SEARCH_ADD_ON_LLM) - API: Discovery Engine v1alpha
- データ: FAQデータ(48件、title/question/answer)
検証1: 構造化データストア(CSV)
手順
コンソールのデータストア作成画面には「CSV(よくある質問のデータ用)」という選択肢があります。

これを使い、Cloud StorageからCSVを構造化データストアとしてインポートしました。CSVはtitle, question, answerの3カラム構成です。
title,answer,question
複数アカウントの利用,1人につき1アカウントの利用をお願い...,一人が複数アカウントを持つのは規約違反か
WAFによるブロック,脅威検出サービスの自動検知でIPアドレスが誤検知された...,投稿を連続で行ったため書き込み系をブロックされた...
...(全48件)
作成されたデータストアの設定は以下のとおりです。
{
"name": "projects/{PROJECT_NUMBER}/locations/global/collections/default_collection/dataStores/{DATA_STORE_ID}",
"displayName": "faq-csv-structured",
"industryVertical": "GENERIC",
"createTime": "2026-03-13T08:17:53.671221Z",
"solutionTypes": [
"SOLUTION_TYPE_SEARCH"
],
"defaultSchemaId": "default_schema",
"languageInfo": {
"languageCode": "ja",
"normalizedLanguageCode": "ja",
"language": "ja"
},
"billingEstimation": {
"structuredDataSize": "26472",
"structuredDataUpdateTime": "2026-03-16T04:56:45.813085130Z"
},
"documentProcessingConfig": {
"name": "projects/{PROJECT_NUMBER}/locations/global/collections/default_collection/dataStores/{DATA_STORE_ID}/documentProcessingConfig",
"defaultParsingConfig": {
"layoutParsingConfig": {}
}
},
"servingConfigDataStore": {},
"naturalLanguageQueryUnderstandingConfig": {
"mode": "ENABLED"
},
"federatedSearchConfig": {}
}
industryVertical: GENERIC— メディア・ヘルスケア等の特化型ではなく、汎用の業種カテゴリsolutionTypes: SOLUTION_TYPE_SEARCH— 検索用途のデータストアnaturalLanguageQueryUnderstandingConfig: ENABLED— 自然言語クエリの解釈機能が有効。(ただし、これが有効でもセマンティック検索が有効になるわけではなかった)
スキーマは、CSVのカラムに対応する3つのフィールドが自動で定義されました。keyPropertyMappingにはFAQ CSV特有のtitle, question, answerが設定されています。
{
"structSchema": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"title": {
"type": "string",
"keyPropertyMapping": "title",
"retrievable": true
},
"question": {
"type": "string",
"keyPropertyMapping": "question",
"retrievable": true
},
"answer": {
"type": "string",
"keyPropertyMapping": "answer",
"retrievable": true
}
}
},
"fieldConfigs": [
{"fieldPath": "title", "fieldType": "STRING", "keyPropertyType": "TITLE"},
{"fieldPath": "question", "fieldType": "STRING", "keyPropertyType": "QUESTION"},
{"fieldPath": "answer", "fieldType": "STRING", "keyPropertyType": "ANSWER"}
]
}
検索リクエスト
この構造化データストアを指定して通常の検索リクエストを送信しました。
$ curl -X POST \
-H "Authorization: Bearer $(gcloud auth print-access-token)" \
-H "Content-Type: application/json" \
"https://discoveryengine.googleapis.com/v1alpha/projects/{PROJECT_NUMBER}/locations/global/collections/default_collection/engines/{ENGINE_ID}/servingConfigs/default_search:search" \
-d '{
"query": "複数のアカウントを作成することは問題ないか",
"dataStoreSpecs": [{
"dataStore": "projects/{PROJECT_NUMBER}/locations/global/collections/default_collection/dataStores/{DATA_STORE_ID}"
}]
}'
{
"attributionToken": "...",
"guidedSearchResult": {},
"summary": {},
"queryExpansionInfo": {},
"semanticState": "DISABLED"
}
semanticState: DISABLEDが返ってきました。Enterprise editionを有効にしていても、セマンティック検索は有効になりませんでした。
詳しく調べていたところ、Dialogflow CXのドキュメントに興味深い記述がありました。
参考: Dialogflow CX - Data store agents
注: CSV ファイルは、非構造化コンテンツとしてインポートすることもできます。(...中略...)一致要件は FAQ CSV データストアに比べて厳密ではなく、回答はエージェントによって書き換えられる場合があり、一言一句そのまま返されるとは限りません。
つまり、同じCSVデータでもインポート方法(FAQ構造化 vs 非構造化)でマッチングの挙動が異なる場合があることが示唆されています。
これを受けて、CSVをそのまま構造化データストアに入れるのではなく、非構造化データストアとしてインポートする方向で検証を進めることにしました。
検証2: 非構造化データストア(HTML変換)
手順
今度は非構造化データの「ドキュメント」としてインポートします。

CSVの各行(48件)を個別のHTMLファイルに変換しました。
<!DOCTYPE html>
<html>
<head><title>複数アカウントの利用</title></head>
<body>
<h1>複数アカウントの利用</h1>
<h2>質問</h2>
<p>一人が複数アカウントを持つのは規約違反か</p>
<h2>回答</h2>
<p>...</p>
</body>
</html>
HTMLファイルをGCSバケットにアップロードし、コンソールから非構造化データストア(Cloud Storage)として作成、エンジンに接続しました。
作成されたデータストアの設定は以下のとおりです。
{
"name": "projects/{PROJECT_NUMBER}/locations/global/collections/default_collection/dataStores/{DATA_STORE_ID}",
"displayName": "faq-html-unstructured",
"industryVertical": "GENERIC",
"createTime": "2026-03-16T01:22:14.863267Z",
"solutionTypes": [
"SOLUTION_TYPE_SEARCH"
],
"contentConfig": "CONTENT_REQUIRED",
"defaultSchemaId": "default_schema",
"billingEstimation": {
"unstructuredDataSize": "34337",
"unstructuredDataUpdateTime": "2026-03-16T02:40:36.968677615Z"
},
"documentProcessingConfig": {
"name": "projects/{PROJECT_NUMBER}/locations/global/collections/default_collection/dataStores/{DATA_STORE_ID}/documentProcessingConfig",
"chunkingConfig": {
"layoutBasedChunkingConfig": {
"chunkSize": 500
}
},
"defaultParsingConfig": {
"layoutParsingConfig": {}
}
},
"servingConfigDataStore": {}
}
contentConfig: CONTENT_REQUIRED— 非構造化データストアではコンテンツが必須。検証1の構造化データストアにはこの設定がなかったchunkingConfig— レイアウトベースのチャンキングが有効(チャンクサイズ500)。非構造化データストアではドキュメントが自動的にチャンク分割されるlayoutParsingConfig— レイアウトパーサーが有効。HTMLの構造を解析してインデックスに反映する
検索リクエスト
検証1と同じクエリで検索してみます。
$ curl -s -X POST \
-H "Authorization: Bearer $(gcloud auth print-access-token)" \
-H "Content-Type: application/json" \
"https://discoveryengine.googleapis.com/v1alpha/projects/{PROJECT_NUMBER}/locations/global/collections/default_collection/engines/{ENGINE_ID}/servingConfigs/default_search:search" \
-d '{
"query": "複数のアカウントを作成することは問題ないか",
"dataStoreSpecs": [{
"dataStore": "projects/{PROJECT_NUMBER}/locations/global/collections/default_collection/dataStores/{DATA_STORE_ID}"
}]
}'
{
"results": [
{
"id": "a72d4a6a75ef09d25ad8d011f0c9cc33",
"document": {
"name": "projects/{PROJECT_NUMBER}/locations/global/collections/default_collection/dataStores/{DATA_STORE_ID}/branches/0/documents/a72d4a6a75ef09d25ad8d011f0c9cc33",
"id": "a72d4a6a75ef09d25ad8d011f0c9cc33",
"derivedStructData": {
"link": "gs://{BUCKET}/faq_html/037.html",
"title": "複数アカウントの利用",
"snippets": [
{
"snippet_status": "SUCCESS",
"snippet": "... <b>アカウント</b>とは別に会社用<b>アカウントを作成</b>したい。一人が<b>複数アカウント</b>を持つのは規約違反か。 ## 回答 <b>複数アカウント</b> の保有は規約的に<b>問題ない</b>。ただし後から ..."
}
]
}
},
"modelScores": {
"relevance_score": { "values": [1] }
},
"rankSignals": {
"keywordSimilarityScore": 3.2140481,
"relevanceScore": 0.99168247,
"semanticSimilarityScore": 0.81432873,
"topicalityRank": 1,
"documentAge": 492680.63,
"boostingFactor": 0,
"defaultRank": 1
}
},
...
],
"totalSize": 3,
"attributionToken": "...",
"guidedSearchResult": {},
"summary": {},
"queryExpansionInfo": {},
"semanticState": "ENABLED"
}
semanticState: ENABLEDになりました。
1位の「複数アカウントの利用」はsemanticSimilarityScore: 0.814、relevance_score: 1(最高スコア)で、言い回しが異なるクエリでもしっかりマッチしています。検証1では結果が返ってこなかったクエリが、非構造化データストアに変えるだけでセマンティック検索が機能するようになりました。
これが最もシンプルな解決策ですが、もう一つのアプローチも試してみました。
検証3: カスタムエンベディング付き構造化データストア
構造化データにカスタムエンベディングを含めることで、ベクトル検索が可能になるという公式ドキュメントの記述があります。この方法でもsemanticStateがENABLEDになるのかを検証しました。
手順
gemini-embedding-001モデルで各FAQドキュメント(question + answer)のエンベディングを生成(768次元)- エンベディングを含むJSONLファイルを作成
{"id": "faq-001", "structData": {"title": "...", "question": "...", "answer": "...", "embedding_vector": [0.1, 0.2, ...]}}
- APIでデータストアを作成(
contentConfig: NO_CONTENT) - スキーマで
embedding_vectorフィールドにkeyPropertyMapping: "embedding_vector"とdimension: 768を設定 - データをインポート(48件成功)
- エンジンに接続
作成されたデータストアの設定は以下のとおりです。
{
"name": "projects/{PROJECT_NUMBER}/locations/global/collections/default_collection/dataStores/{DATA_STORE_ID}",
"displayName": "faq-custom-embeddings",
"industryVertical": "GENERIC",
"createTime": "2026-03-16T04:14:49.765248Z",
"solutionTypes": [
"SOLUTION_TYPE_SEARCH"
],
"contentConfig": "NO_CONTENT",
"defaultSchemaId": "default_schema",
"billingEstimation": {
"structuredDataSize": "829736",
"structuredDataUpdateTime": "2026-03-16T04:56:45.813085130Z"
},
"servingConfigDataStore": {},
"naturalLanguageQueryUnderstandingConfig": {
"mode": "ENABLED"
}
}
スキーマは以下のとおりです。
{
"structSchema": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"title": {
"type": "string",
"retrievable": true,
"keyPropertyMapping": "title"
},
"question": {
"type": "string",
"retrievable": true
},
"answer": {
"type": "string",
"retrievable": true
},
"embedding_vector": {
"type": "array",
"items": { "type": "number" },
"keyPropertyMapping": "embedding_vector",
"dimension": 768
}
}
},
"fieldConfigs": [
{"fieldPath": "embedding_vector", "fieldType": "NUMBER", "keyPropertyType": "EMBEDDING_VECTOR"},
{"fieldPath": "title", "fieldType": "STRING", "keyPropertyType": "TITLE"},
{"fieldPath": "question", "fieldType": "STRING"},
{"fieldPath": "answer", "fieldType": "STRING"}
]
}
contentConfig: NO_CONTENT—structDataのみのドキュメントをインポートするために必要。CONTENT_REQUIREDだとインポートエラーになるembedding_vectorフィールドにkeyPropertyMapping: "embedding_vector"とdimension: 768を設定。この設定は既存ドキュメントがある状態では追加できないため、データストア作成直後(インポート前)に設定する必要があるquestionとanswerには検証1と異なりkeyPropertyMappingが設定されていない。FAQ CSVとしてではなく、通常の構造化データストアとして作成しているため
検索リクエスト
公式ドキュメントに従い、検索リクエストにembeddingSpecでクエリのエンベディングベクトルを渡し、ranking_expressionでベクトル類似度をランキングに反映する方法を試しました。
$ curl -s -X POST \
-H "Authorization: Bearer $(gcloud auth print-access-token)" \
-H "Content-Type: application/json" \
"https://discoveryengine.googleapis.com/v1alpha/projects/{PROJECT_NUMBER}/locations/global/collections/default_collection/engines/{ENGINE_ID}/servingConfigs/default_search:search" \
-d '{
"query": "複数のアカウントを作成することは問題ないか",
"dataStoreSpecs": [{
"dataStore": "projects/{PROJECT_NUMBER}/locations/global/collections/default_collection/dataStores/{DATA_STORE_ID}"
}],
"embeddingSpec": {
"embeddingVectors": [{
"fieldPath": "embedding_vector",
"vector": [0.028, 0.003, -0.016, ...]
}]
},
"ranking_expression": "0.5 * relevance_score + 0.5 * dotProduct(embedding_vector)"
}'
{
"results": [
{
"id": "faq-037",
"document": {
"name": "projects/{PROJECT_NUMBER}/locations/global/collections/default_collection/dataStores/{DATA_STORE_ID}/branches/0/documents/faq-037",
"id": "faq-037",
"structData": {
"title": "複数アカウントの利用",
"question": "個人用アカウントとは別に会社用アカウントを作成したい。一人が複数アカウントを持つのは規約違反か。",
"answer": "複数アカウントの保有は規約的に問題ない。ただし後からアカウント間の記事移行はできない。"
},
"derivedStructData": {
"snippets": [
{
"snippet_status": "NO_SNIPPET_AVAILABLE",
"snippet": "No snippet is available for this page."
}
]
}
},
"modelScores": {
"dotProduct(embedding_vector)": { "values": [0.8300950527191162] }
},
"rankSignals": {
"keywordSimilarityScore": 2.3365948,
"topicalityRank": 1,
"defaultRank": 1
}
},
...
],
"totalSize": 48,
"attributionToken": "...",
"nextPageToken": "...",
"guidedSearchResult": {},
"summary": {},
"queryExpansionInfo": {},
"semanticState": "ENABLED"
}
semanticState: ENABLEDになりました。
- 1位: 複数アカウントの利用(dotProduct: 0.830)
- 2位: アカウント凍結・解除(dotProduct: 0.608)
- 3位: アカウント削除・退会(dotProduct: 0.605)
全48件が検索対象となり、正しくセマンティック検索が機能しています。
カスタムエンベディング利用時の注意点
検証の中で気づいた点をまとめておきます。
- 検索リクエストごとにクエリのエンベディングベクトルを生成して渡す必要がある。通常の検索リクエストだけでは有効にならない
ranking_expressionでdotProduct(フィールド名)を使ってランキングに反映する- スキーマの
keyPropertyMapping: "embedding_vector"は、既存ドキュメントがある状態では追加できない。データストア作成直後(インポート前)に設定する必要がある - エンベディングの次元数は1〜768の範囲。
gemini-embedding-001はデフォルト3072次元のため、output_dimensionality: 768を指定して制限が必要
なぜクエリのベクトルを自前で渡す必要があるのか
カスタムエンベディングの場合、データ側のベクトルはユーザーが任意のモデルで生成したものです。ベクトル類似度検索はデータ側とクエリ側が同じモデル・同じ設定で生成されていることが前提なので、Vertex AI Searchの内部モデルでクエリのベクトルを自動生成しても意味がありません。そのため、ユーザーが同じモデルでクエリのベクトルを生成してembeddingSpecで渡す必要があります。
一方、非構造化データストアの場合は、Vertex AI Searchがデータ側もクエリ側も同じ内部モデルでエンベディングを自動生成するため、ユーザーはエンベディングを意識する必要がありません。
余談: blended searchで構造化データストアもセマンティック検索が有効になるケース
ここまでの検証は単一のデータストアを接続したエンジンで行いましたが、最後に一つ気になる挙動を紹介します。
blended search(複数データストア接続)のエンジンで、dataStoreSpecsを指定せずに全データストアを対象に検索すると、semanticState: ENABLEDが返ってきて構造化データストアの内容もヒットしました。
| 条件 | semanticState |
|---|---|
blended searchエンジンでdataStoreSpecsなし(全データストア検索) |
ENABLED |
同エンジンでdataStoreSpecsにCSV構造化データストアのみ指定 |
DISABLED |
Webサイト(Advanced indexing)データストアなど、セマンティック検索に対応したデータストアと一緒に検索されることで、構造化データストアにもセマンティック検索が波及しているように見えます。ただし、ドキュメント0件のWebサイトデータストアを追加しただけでは有効にならなかったため、インデックス済みのドキュメントを持つデータストアの存在が必要なようです。
この挙動が仕様なのか、あるいはエンジン全体の検索モードが切り替わっているだけなのか、詳細はよくわかっていません。dataStoreSpecsで構造化データストアだけに絞るとDISABLEDに戻ることから、構造化データストア自体のセマンティック検索が有効化されているわけではなさそうです。実用上は、下の表にまとめた方法で対応するのが確実だと思います。
まとめ
検証結果をまとめます。
| データストアの種類 | semanticState |
|---|---|
| 構造化データストア(CSV / keyPropertyMappingあり) | DISABLED |
| 構造化データストア(CSV / keyPropertyMappingなし) | ENABLED |
| 構造化 + カスタムエンベディング | ENABLED |
| 非構造化データストア(HTML) | ENABLED |
blended search(dataStoreSpecsなし) |
ENABLED |
構造化データストアでは特定のkeyPropertyMappingでセマンティック検索が無効化されることが分かりました。
セマンティック検索を実現するには、非構造化データストア(HTML変換)も有効です。CSVをHTMLに変換する手間はありますが、一度変換してしまえば通常の検索リクエストでセマンティック検索が有効になります。
一方、カスタムエンベディングを使う方法は、エンベディングモデルを自由に選べるという利点がありますが、検索のたびにクエリのベクトルを生成・送信する必要があり、アプリケーション側の実装が複雑になります。
blended searchでdataStoreSpecsを指定しない方法は手軽ですが、構造化データストア単体を指定するとDISABLEDに戻るため、検索対象のデータストアを絞りたい場合には使えません。








