kt-tech.blog

画像

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

Share
💡
Azure AI Searchのインデックスをベクトル検索(Vectorizer)対応に移行し、ハイブリッド検索を実現した手順を解説します。

はじめに

RAGシステムの検索精度を向上させるため、Azure AI Searchのインデックスをキーワード検索のみ → ハイブリッド検索(キーワード + ベクトル)に移行しました。本記事ではその手順と注意点をまとめます。

この記事でわかること

  • Azure AI SearchでVectorizer付きインデックスを作成する方法

  • 既存インデックスからVectorizer対応インデックスへの移行手順

  • レイヤー別(要件定義/仕様/設計/説明)にインデックスを分割管理する設計

  • Pythonでのインデックス作成・ドキュメント登録の実装例

対象読者

  • Azure AI Searchを使ったRAGシステムを構築・運用している方

  • キーワード検索からハイブリッド検索への移行を検討している方

前提条件

  • Azure AI Searchリソースが作成済み

  • Azure OpenAI Serviceでembeddingsモデル(text-embedding-3-small等)がデプロイ済み

  • Python 3.10+、azure-search-documents ライブラリ

背景: なぜVectorizer対応が必要だったか

当初はキーワード検索のみのインデックスでRAGを構築していましたが、以下の問題がありました:

  1. 専門用語や表記揺れに弱い — 「エラーハンドリング」で検索しても「例外処理」のドキュメントがヒットしない

  2. 意味的に関連するドキュメントが取得できない — キーワードが一致しないと検索結果に含まれない

  3. ハイブリッド検索を行うには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)

検索側の実装: ハイブリッド検索

インデックスにVectorizerを設定したら、検索側でVectorizedQueryを使ったハイブリッド検索が可能になります。

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 results

Tips

1️⃣
インデックス名に日付サフィックス(YYYYMMDD)を付けると、正規表現で最新版を自動取得できて便利です
2️⃣
Vectorizerを後から追加することはできません。インデックスの再作成が必要です。最初からVectorizerを設定しておくことを推奨します
3️⃣
削除→再作成時は、他のインデックスを巻き込まないよう削除対象を明示的にリストアップしましょう

参考リンク

まとめ

  • Vectorizerなしのインデックスではハイブリッド検索が正しく機能しないため、Vectorizer付きで再作成が必要

  • AzureOpenAIVectorizerを使うことで、検索時のクエリベクトル化をAzure側に任せられる

  • ドキュメントをレイヤー別インデックスに分割することで、RAGのコンテキスト構造が明確になる