チュートリアル: Azure AI 検索で RAG のインデックスを設計する
インデックスには、検索可能なテキストとベクトルのコンテンツと構成が含まれます。 応答にチャット モデルを使用する RAG パターンでは、クエリ時に LLM に渡すことができるコンテンツのチャンクを中心に設計されたインデックスが必要です。
このチュートリアルでは、次の作業を行いました。
- RAG 用に構築されたインデックス スキーマの特性について学ぶ
- ベクトルとハイブリッド クエリに対応するインデックスを作成する
- ベクトル プロファイルと構成を追加する
- 構造化データを追加する
- フィルターの追加
前提条件
Python 拡張機能と Jupyter パッケージを持つ Visual Studio Code。 詳細については、「Visual Studio Code での Python」を参照してください。
この演習の出力は、JSON のインデックス定義です。 この時点では、Azure AI 検索にはアップロードされないため、この演習ではクラウド サービスやアクセス許可の要件はありません。
RAG のスキーマに関する考慮事項を確認する
会話検索では、LLM は検索エンジンではなく、ユーザーが表示する応答を作成するため、検索結果に表示するフィールドや、個々の検索ドキュメントの表現がユーザーにとって一貫性があるかどうかを考慮する必要はありません。 質問によっては、LLM はインデックスから逐語的なコンテンツを返す場合もあれば、より適切な回答を得るためのコンテンツを再パッケージ化する可能性もあります。
チャンクを中心に整理する
LLM は、応答を生成するときに、メッセージ入力のコンテンツのチャンクを操作します。また、引用の目的でチャンクがどこから来たのかを認識する必要がありますが、最も重要なのは、メッセージ入力の品質とユーザーの質問との関連性です。 チャンクの派生元のドキュメントが 1 つか 1,000 個かにかかわらず、LLM は情報か、"グラウンディング データ" を取り込みシステム プロンプトで指定された指示を使用して応答を作成します。
チャンクはスキーマの焦点であり、各チャンクは RAG パターンの検索ドキュメントの定義要素です。 名前、説明、カテゴリ、アドレスの統一されたコンテンツを含むフィールドなど、より多くの構造を持つ可能性がある従来の検索ドキュメントとは対照的に、インデックスはチャンクの大規模なコレクションと考えることができます。
生成されたデータによる強化
このチュートリアルのサンプル データは NASA Earth Book の PDF とコンテンツで構成されています。 このコンテンツは説明的で有益であり、世界中の地理、国、地域の数多くの参照が含まれています。 すべてのテキスト コンテンツはチャンクで取り込まれますが、繰り返しがある地名のインスタンスの場合、インデックスに構造を追加することもできます。 スキルを使用すると、テキスト内のエンティティを認識し、クエリやフィルターで使用できるようにインデックスに取り込むことができます。 このチュートリアルには、位置エンティティを認識して抽出し、検索可能でフィルター処理可能な locations
フィールドに読み込むエンティティ認識スキルが含まれています。 構造化コンテンツをインデックスに追加すると、フィルター処理、関連性の向上、より的を絞った回答のための選択肢が増えます。
インデックスの親子フィールドがあるインデックスは 1 つまたは 2 つ?
チャンク コンテンツは通常、大きなドキュメントから派生します。 また、スキーマはチャンクを中心に編成されていますが、親レベルでプロパティとコンテンツをキャプチャする必要もあります。 これらのプロパティの例としては、親ファイルのパス、タイトル、作成者、公開日、概要などがあります。
スキーマデザインの転換点は、親コンテンツと子またはチャンク コンテンツに対して 2 つのインデックスを持っているか、チャンクごとに親要素を繰り返す単一のインデックスを持っているかです。
このチュートリアルでは、テキストのすべてのチャンクが単一の親 (NASA Earth Book) から生成されるため、親フィールドのレベルを上げるための個別のインデックスは必要ありません。 ただし、複数の親 PDF からインデックスを作成する場合は、親子のインデックス ペアでレベル固有のフィールドをキャプチャし、参照クエリを親インデックスに送信して、各チャンクに関連するフィールドを取得できます。
スキーマの考慮事項のチェックリスト
Azure AI 検索では、RAG ワークロードに最適なインデックスには、次の特性があります。
クエリに関連し、LLM が読み取り可能なチャンクを返します。 LLM は、マークアップ、冗長性、不完全な文字列など、チャンク内の一定のレベルのダーティ データを処理できます。 チャンクは読みやすく、質問に関連している必要がありますが、手付かず (pristine) である必要はありません。
ドキュメントのチャンクと親ドキュメントのプロパティ (ファイル名、ファイルの種類、タイトル、作成者など) の間の親子関係を維持します。 クエリに応答するために、インデックス内のどこからでもチャンクをプルできます。 チャンクを提供する親ドキュメントとの関連付けは、コンテキスト、引用、フォローアップ クエリに役立ちます。
作成するクエリに対応します。 ベクトルおよびハイブリッド コンテンツのフィールドが必要です。これらのフィールドは、検索可能またはフィルター可能などの特定のクエリ動作をサポートするように属性付ける必要があります。 一度にクエリを実行できるのは 1 つのインデックスのみ (結合なし) であるため、フィールド コレクションは検索可能なすべてのコンテンツを定義する必要があります。
スキーマはフラット (複合型なし、構造体なし) であるか、LLM に送信する前に、複合型の出力を JSON として書式設定する必要があります。 この要件は、Azure AI 検索の RAG パターンに固有です。
Note
スキーマの設計は、ストレージとコストに影響します。 この演習では、スキーマの基礎に焦点を当てます。 「ストレージとコストを最小限に抑える」チュートリアルでは、スキーマを見直して、データ型、圧縮、ストレージの選択肢を狭めることで、ベクトルによって使用されるストレージの量を大幅に削減する方法を確認します。
RAG ワークロードのインデックスを作成する
LLM の最小インデックスは、コンテンツのチャンクを格納するように設計されています。 一般に、関連性の高い結果を類似性検索する場合は、ベクトル フィールドが含まれます。 また、会話検索用 LLM への人間が判読できる入力用の非ベクトル フィールドも含まれています。 検索結果内の非ベクトル チャンク コンテンツは、LLM に送信されるグラウンディング データになります。
Visual Studio Code を開き、新しいファイルを作成します。 この演習では、Python ファイル タイプである必要はありません。
ベクトルおよびハイブリッド検索をサポートする RAG ソリューションの最小インデックス定義を次に示します。 必要な要素 (インデックス名、フィールド、ベクトル フィールドの構成セクション) の概要を確認します。
{ "name": "example-minimal-index", "fields": [ { "name": "id", "type": "Edm.String", "key": true }, { "name": "chunked_content", "type": "Edm.String", "searchable": true, "retrievable": true }, { "name": "chunked_content_vectorized", "type": "Edm.Single", "dimensions": 1536, "vectorSearchProfile": "my-vector-profile", "searchable": true, "retrievable": false, "stored": false }, { "name": "metadata", "type": "Edm.String", "retrievable": true, "searchable": true, "filterable": true } ], "vectorSearch": { "algorithms": [ { "name": "my-algo-config", "kind": "hnsw", "hnswParameters": { } } ], "profiles": [ { "name": "my-vector-profile", "algorithm": "my-algo-config" } ] } }
フィールドにはキー フィールド (この例では
"id"
) を含める必要があり、類似性検索の場合はベクトル チャンク、LLM への入力には非ベクトル チャンクを含める必要があります。ベクトル フィールドは、クエリ時に検索パスを決定するアルゴリズムに関連付けられています。 このインデックスには、複数のアルゴリズム構成を指定するための vectorSearch セクションがあります。 また、ベクトル フィールドには、モデルのディメンションを埋め込むための固有の型と追加の属性があります。
Edm.Single
は、一般的に使用される LLM に対して機能するデータ型です。 ベクトル フィールドの詳細については、「ベクトル インデックスを作成する」を参照してください。メタデータ フィールドは、親ファイルのパス、作成日、またはコンテンツ タイプの場合があり、フィルターに役立ちます。
こちらで、チュートリアル ソース コードおよび Earth Book コンテンツのインデックス スキーマを参照できます。
基本的なスキーマと同様に、チャンクを中心に編成されています。
chunk_id
は、各チャンクを一意に識別します。text_vector
フィールドはチャンクの埋め込みです。 非ベクトルchunk
フィールドは読み取り可能な文字列です。title
は、BLOB の一意のメタデータ ストレージ パスにマップされます。parent_id
は唯一の親レベルのフィールドであり、親ファイル URI の base64 でエンコードされたバージョンです。このチュートリアル シリーズで使用されるような垂直統合ワークロードでは、ベクトル フィールドの
dimensions
プロパティと、データのベクトル化に使用される埋め込みスキルによって生成されるdimensions
の数が同一であることが必要です。 このシリーズでは、Azure OpenAI 埋め込みスキルを使用しますが、これは Azure OpenAI 上の text-embedding-3-large モデルを呼び出します。 このスキルは、次のチュートリアルで指定されます。 ここでは、ベクトル フィールドとスキル定義の両方でディメンションを 1024 に設定します。スキーマには、インデックス作成パイプラインによって作成された生成済みコンテンツを格納するための
locations
フィールドも含まれています。from azure.identity import DefaultAzureCredential from azure.identity import get_bearer_token_provider from azure.search.documents.indexes import SearchIndexClient from azure.search.documents.indexes.models import ( SearchField, SearchFieldDataType, VectorSearch, HnswAlgorithmConfiguration, VectorSearchProfile, AzureOpenAIVectorizer, AzureOpenAIVectorizerParameters, SearchIndex ) credential = DefaultAzureCredential() # Create a search index index_name = "py-rag-tutorial-idx" index_client = SearchIndexClient(endpoint=AZURE_SEARCH_SERVICE, credential=credential) fields = [ SearchField(name="parent_id", type=SearchFieldDataType.String), SearchField(name="title", type=SearchFieldDataType.String), SearchField(name="locations", type=SearchFieldDataType.Collection(SearchFieldDataType.String), filterable=True), SearchField(name="chunk_id", type=SearchFieldDataType.String, key=True, sortable=True, filterable=True, facetable=True, analyzer_name="keyword"), SearchField(name="chunk", type=SearchFieldDataType.String, sortable=False, filterable=False, facetable=False), SearchField(name="text_vector", type=SearchFieldDataType.Collection(SearchFieldDataType.Single), vector_search_dimensions=1024, vector_search_profile_name="myHnswProfile") ] # Configure the vector search configuration vector_search = VectorSearch( algorithms=[ HnswAlgorithmConfiguration(name="myHnsw"), ], profiles=[ VectorSearchProfile( name="myHnswProfile", algorithm_configuration_name="myHnsw", vectorizer_name="myOpenAI", ) ], vectorizers=[ AzureOpenAIVectorizer( vectorizer_name="myOpenAI", kind="azureOpenAI", parameters=AzureOpenAIVectorizerParameters( resource_url=AZURE_OPENAI_ACCOUNT, deployment_name="text-embedding-3-large", model_name="text-embedding-3-large" ), ), ], ) # Create the search index index = SearchIndex(name=index_name, fields=fields, vector_search=vector_search) result = index_client.create_or_update_index(index) print(f"{result.name} created")
構造化されたコンテンツをより厳密に模倣するインデックス スキーマの場合、親と子 (チャンク) フィールドに対して個別のインデックスが作成されます。 2 つのインデックスのインデックス作成を同時に調整するには、インデックス プロジェクションが必要です。 クエリは子インデックスに対して実行されます。 クエリ ロジックには、parent_idt を使用して親インデックスからコンテンツを取得する参照クエリが含まれます。
子インデックスのフィールド:
- ID
- chunk
- chunkVectcor
- parent_id
親インデックス内のフィールド ("いずれか" が必要なものすべて):
- parent_id
- 親レベルのフィールド (名前、タイトル、カテゴリ)