【実装】Azure AI Searchでベクトル検索対応インデックスを作成・移行する方法

この記事でわかること
- Azure AI Search で Vectorizer 付きインデックスを作る Python の実装例
- キーワード検索だけのインデックスからハイブリッド検索に乗り換える手順
- VectorizedQuery と VectorizableTextQuery の違い(ベクトル化をどちらがやるか)
- ドキュメントをレイヤー別インデックスに分ける設計
Azure AI Search リソースと、Azure OpenAI で embeddings モデル(text-embedding-3-small 等)がデプロイ済みであること。Python 3.10 以上と azure-search-documents を使います。
はじめに
RAGシステムの検索精度を向上させるため、Azure AI Searchのインデックスをキーワード検索のみ → ハイブリッド検索(キーワード + ベクトル)に移行しました。本記事ではその手順と注意点をまとめます。
背景: なぜVectorizer対応が必要だったか
当初はキーワード検索のみのインデックスでRAGを構築していましたが、以下の問題がありました:
- 専門用語や表記揺れに弱い — 「エラーハンドリング」で検索しても「例外処理」のドキュメントがヒットしない
- 意味的に関連するドキュメントが取得できない — キーワードが一致しないと検索結果に含まれない
- ハイブリッド検索を行うにはVectorizedQueryを送る必要があるが、インデックス側にVectorizerが設定されていないとベクトルフィールドが正しく機能しない
移行手順
Step 1: 旧インデックスの削除
Vectorizerなしで作成した旧インデックスを削除します。他のインデックスを誤って消さないよう、削除対象を明示的に指定します。
from azure.search.documents.indexes import SearchIndexClient
from azure.core.credentials import AzureKeyCredential
client = SearchIndexClient(
endpoint="[ENDPOINT]",
credential=AzureKeyCredential("[API_KEY]")
)
# 削除対象のインデックス名を明示的にリストアップ
target_indexes = [
"myapp_basic_knowledge_20260131",
"myapp_requirements_definition_20260131",
"myapp_specification_20260131",
"myapp_detailed_design_20260131",
]
for name in target_indexes:
client.delete_index(name)
print(f"Deleted: {name}")list_indexes() で全インデックスを列挙できます。Step 2: Vectorizer付きインデックスの作成
新しいインデックスにはAzureOpenAIVectorizerを設定します。これにより、検索時にクエリを自動的にベクトル化できます。
from azure.search.documents.indexes.models import (
SearchIndex,
SearchField,
SearchFieldDataType,
VectorSearch,
HnswAlgorithmConfiguration,
VectorSearchProfile,
AzureOpenAIVectorizer,
AzureOpenAIVectorizerParameters,
)
def create_index_with_vectorizer(client, index_name: str):
fields = [
SearchField(name="id", type=SearchFieldDataType.String, key=True),
SearchField(name="content", type=SearchFieldDataType.String, searchable=True),
SearchField(name="file_name", type=SearchFieldDataType.String, filterable=True),
SearchField(name="page", type=SearchFieldDataType.String),
SearchField(name="url", type=SearchFieldDataType.String),
SearchField(
name="content_vector",
type=SearchFieldDataType.Collection(SearchFieldDataType.Single),
searchable=True,
vector_search_dimensions=1536,
vector_search_profile_name="my-vector-profile",
),
]
vector_search = VectorSearch(
algorithms=[HnswAlgorithmConfiguration(name="my-hnsw")],
profiles=[
VectorSearchProfile(
name="my-vector-profile",
algorithm_configuration_name="my-hnsw",
vectorizer_name="my-vectorizer",
)
],
vectorizers=[
AzureOpenAIVectorizer(
vectorizer_name="my-vectorizer",
parameters=AzureOpenAIVectorizerParameters(
resource_url="[AOAI_ENDPOINT]",
deployment_name="text-embedding-3-small",
model_name="text-embedding-3-small",
api_key="[AOAI_API_KEY]",
),
)
],
)
index = SearchIndex(name=index_name, fields=fields, vector_search=vector_search)
client.create_or_update_index(index)
print(f"Created: {index_name}")Step 3: レイヤー別にドキュメントを登録
ドキュメントをカテゴリ(要件定義、仕様、詳細設計、基礎知識)ごとに別インデックスに振り分けて登録します。これにより、検索時にカテゴリ別のコンテキストを構造的にLLMに渡せます。
LAYER_TO_FOLDER = {
"requirements": "_requirements_definition_",
"specifications": "_specification_",
"detailed_design": "_detailed_design_",
"explanation": "_basic_knowledge_",
}
for layer, folder in LAYER_TO_FOLDER.items():
index_name = f"{prefix}{folder}{date_suffix}"
create_index_with_vectorizer(client, index_name)
# レイヤーに属するチャンクを抽出して登録
chunks = [c for c in all_chunks if c["layer"] == layer]
search_client = client.get_search_client(index_name)
search_client.upload_documents(chunks)検索側の実装: ハイブリッド検索
インデックスにベクトルフィールドを用意したら、キーワード検索とベクトル検索を同時に投げるようになります。
from azure.search.documents.models import VectorizedQuery
def search_with_hybrid(index_name: str, query: str, top: int = 5):
vector_query = VectorizedQuery(
vector=generate_embeddings(query),
k_nearest_neighbors=top,
fields="content_vector",
)
search_client = client.get_search_client(index_name)
results = search_client.search(
search_text=query, # キーワード検索
vector_queries=[vector_query], # ベクトル検索
select=["content", "file_name", "page", "url"],
top=top,
)
return resultsVectorizedQuery と VectorizableTextQuery は役割が違います。上のコードは VectorizedQuery なので、クエリのベクトル化を自分でやっています(generate_embeddings(query) の部分)。この形だけなら、実はインデックス側の Vectorizer は使われません。
設定した Vectorizer を実際に活かしてベクトル化を Azure 側に任せるなら、VectorizableTextQuery(text=query, k_nearest_neighbors=top, fields="content_vector") のように文字列のまま渡します。こちらならアプリ側に embedding 呼び出しのコードとキーが不要になります。
Tips
参考リンク
- Azure AI Search ベクトル検索の概要
- 統合ベクトル化(Vectorizer)
- azure-search-documents Python SDK
- RAGのtop-k設定とトークン上限のトレードオフ(別記事)
まとめ
- Vectorizerなしのインデックスではハイブリッド検索が正しく機能しないため、Vectorizer付きで再作成が必要
- AzureOpenAIVectorizerを使うことで、検索時のクエリベクトル化をAzure側に任せられる
- ドキュメントをレイヤー別インデックスに分割することで、RAGのコンテキスト構造が明確になる
更新履歴
- 記事中の「クエリのベクトル化を Azure 側に任せられる」という説明と、実際のコード(自前で embedding を作って VectorizedQuery に渡している)が食い違っていたので、両者の使い分けを補足。Tips の「Vectorizer は後から追加できない」を、実態に即した表現に修正。callout の色指定を他記事と揃え、メソッド名をインラインコードに統一。参考リンクと関連記事へのリンクを追加。「この記事でわかること」「対象読者」「前提条件」を概要プロパティへ移動。


