Tutorial: Entwerfen eines Index für RAG-Muster in der Azure KI-Suche

Ein Index enthält durchsuchbare Text- und Vektorinhalte sowie Konfigurationen. In einem RAG-Muster, das ein Chatmodell für Antworten verwendet, benötigen Sie einen Index, der Blöcke von Inhalten enthält, die zur Abfragezeit an ein LLM übergeben werden können.

In diesem Tutorial:

  • Übersicht über die Merkmale eines Indexschemas, das für RAG erstellt wurde
  • Erstellen eines Indexes für Vektoren und Hybridabfragen
  • Hinzufügen von Vektorprofilen und Konfigurationen
  • Hinzufügen strukturierter Daten
  • Hinzufügen von Filterung

Voraussetzungen

Visual Studio Code mit der Python-Erweiterung und dem Jupyter-Paket Weitere Informationen finden Sie unter Python in Visual Studio Code.

Die Ausgabe dieser Übung ist eine Indexdefinition in JSON. An diesem Punkt wird sie nicht in die Azure KI-Suche hochgeladen, daher gibt es keine Anforderungen für Clouddienste oder Berechtigungen in dieser Übung.

Wiederholung der Überlegungen zu RAG-Schemata

In der unterhaltungsbasierten Suche verfassen LLMs die Antwort, die der Benutzer sieht, nicht die Suchmaschine. Sie müssen daher nicht überlegen, welche Felder in Ihren Suchergebnissen angezeigt werden sollen, und ob die Darstellungen einzelner Suchdokumente für den Benutzer kohärent sind. Je nach Frage gibt das LLM möglicherweise unveränderte Inhalte aus Ihrem Index zurück. Es ist jedoch wahrscheinlicher, dass der Inhalt neu gepackt wird, um eine bessere Antwort zu erhalten.

Organisiert um Blöcke

Wenn LLMs eine Antwort generieren, arbeiten sie mit Blöcken von Inhalten für Nachrichteneingaben. Sie müssen dabei für Zitatzwecke wissen, woher der Block stammt, die Qualität der Nachrichteneingaben und ihre Relevanz für die Frage des Benutzers sind aber am wichtigsten. Unabhängig davon, ob die Blöcke aus einem Dokument oder tausend stammen, erfasst das LLM die Informationen oder Groundingdaten und formuliert die Antwort mithilfe von Anweisungen in einem Systemprompt.

Blöcke sind der Fokus des Schemas, und jeder Block ist das definierende Element eines Suchdokuments in einem RAG-Muster. Sie können sich Ihren Index als eine große Sammlung von Blöcken vorstellen, im Gegensatz zu herkömmlichen Suchdokumenten, die wahrscheinlich mehr Struktur aufweisen, z. B. Felder mit einheitlichen Inhalten für einen Namen, Beschreibungen, Kategorien und Adressen.

Verbessert mit generierten Daten

In diesem Tutorial bestehen die Beispieldaten aus PDFs und Inhalten aus dem NASA Earth Book. Dieser Inhalt ist beschreibend und informativ, mit zahlreichen Verweisen auf Geografie, Länder und Gebiete auf der ganzen Welt. Der gesamte Textinhalt wird in Blöcken erfasst, aber wiederkehrende Instanzen von Ortsnamen schaffen eine Möglichkeit zum Hinzufügen von Struktur zum Index. Mithilfe von Fähigkeiten ist es möglich, Entitäten im Text zu erkennen und in einem Index für die Verwendung in Abfragen und Filtern zu erfassen. In diesem Lernprogramm fügen wir eine Entitätserkennungsfertigkeit hinzu, die Standortentitäten erkennt und extrahiert und in ein durchsuchbares und filterbares locations-Feld lädt. Durch das Hinzufügen von strukturierten Inhalten zu Ihrem Index erhalten Sie mehr Optionen zum Filtern, eine verbesserte Relevanz sowie fokussiertere Antworten.

Felder mit untergeordnetem und übergeordnetem Element in einem oder zwei Indizes?

In Blöcke unterteilte Inhalte werden in der Regel von einem größeren Dokument abgeleitet. Und obwohl das Schema um Blöcke organisiert ist, sollten Sie auch Eigenschaften und Inhalte auf der übergeordneten Ebene erfassen. Beispiele für diese Eigenschaften sind der übergeordnete Dateipfad, Titel, Autoren, Veröffentlichungsdatum oder eine Zusammenfassung.

Ein entscheidender Punkt beim Schemaentwurf ist die Frage, ob zwei Indizes für übergeordnete und untergeordnete/in Blöcken unterteilte Inhalte oder ein einzelner Index vorhanden sein soll, der übergeordnete Elemente für jeden Block wiederholt.

Da in diesem Tutorial alle Textblöcke von einem einzigen übergeordneten Element (NASA Earth Book) stammen, benötigen Sie keinen separaten Index, der den übergeordneten Feldern auf oberster Ebene zugeordnet ist. Wenn Sie jedoch aus mehreren übergeordneten PDF-Dateien indizieren, sollten Sie ein Indexpaar für über- und untergeordnete Elemente erstellen, um ebenenspezifische Felder zu erfassen und dann Lookup-Abfragen an den übergeordneten Index zu senden, um diese Felder für jeden Block abzurufen.

Prüfliste zu Schemaüberlegungen

In der Azure KI-Suche hat ein Index, der für RAG-Workloads am besten geeignet ist, folgende Merkmale:

  • Gibt Blöcke zurück, die für die Abfrage relevant und für das LLM lesbar sind. LLMs können ein bestimmtes Maß von „unsauberen“ Daten in Datenblöcken verarbeiten, z. B. Markup, Redundanz und unvollständige Zeichenfolgen. Auch wenn Blöcke lesbar und relevant für die Frage sein müssen, müssen sie nicht makellos sein.

  • Verwaltet eine Beziehung der über- und untergeordneten Elemente zwischen Blöcken eines Dokuments und den Eigenschaften des übergeordneten Dokuments, z. B. Dateiname, Dateityp, Titel, Autor usw. Um eine Abfrage zu beantworten, können Blöcke von einer beliebigen Stelle im Index abgerufen werden. Die Zuordnung zum übergeordneten Dokument, das den Block bereitstellt, ist nützlich für Kontext, Zitate und Nachfolgefragen.

  • Ist auf die Abfragen ausgerichtet, die Sie erstellen möchten. Sie sollten Felder für Vektor- und Hybridinhalte haben, und diese Felder sollten zugeordnet werden, um bestimmtes Abfrageverhalten zu unterstützen, z. B. durchsuchbar oder filterbar, zugeordnet werden. Sie können jeweils nur einen Index abfragen (keine Verknüpfungen), sodass ihre Feldsammlung alle durchsuchbaren Inhalte definieren sollte.

  • Ihr Schema sollte entweder flach sein (keine komplexen Typen oder Strukturen haben), oder Sie sollten die Ausgabe des komplexen Typs als JSON formatieren, bevor Sie es an die LLM senden. Diese Anforderung ist spezifisch für das RAG-Muster in der Azure KI-Suche.

Erstellen eines Index für RAG-Workloads

Ein minimaler Index für das LLM dient zum Speichern von Inhaltsblöcken. Er enthält in der Regel Vektorfelder, wenn Sie eine Ähnlichkeitssuche nach hoch relevanten Ergebnissen wünschen. Er enthält außerdem Nichtvektorfelder für von Menschen lesbare Eingaben für das LLM zur unterhaltungsbasierten Suche. Nicht vektorierte, in Blöcke unterteilte Inhalte in den Suchergebnissen werden zu den Groundingdaten, die an das LLM gesendet werden.

  1. Öffnen Sie Visual Studio Code, und erstellen Sie eine neue Datei. Dies muss für diese Übung kein Python-Dateityp sein.

  2. Dies ist eine minimale Indexdefinition für RAG-Lösungen, die die Vektor- und Hybridsuche unterstützen. Betrachten Sie es als Einführung in die erforderlichen Elemente: Indexname, Felder und ein Konfigurationsabschnitt für Vektorfelder.

    {
      "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" }
          ]
      }
    }
    

    Felder müssen das Schlüsselfeld ("id" in diesem Beispiel) enthalten und sollten Vektorblöcke für die Ähnlichkeitssuche und Nicht-Vektorblöcke für Eingaben in das LLM enthalten.

    Vektorfelder sind mit Algorithmen verknüpft, die die Suchpfade zum Abfragezeitpunkt bestimmen. Der Index verfügt über einen vectorSearch-Abschnitt zum Angeben mehrerer Algorithmuskonfigurationen. Vektorfelder weisen auch spezifische Typen und zusätzliche Attribute für das Einbetten von Modellabmessungen auf. Edm.Single ist ein Datentyp, der für häufig verwendete LLMs funktioniert. Weitere Informationen zu Vektorfeldern finden Sie unter Erstellen eines Vektorindexes.

    Metadatenfelder können der übergeordnete Dateipfad, Erstellungsdatum oder Inhaltstyp sein und sind für Filter nützlich.

  3. Hier sehen Sie das Indexschema für den Tutorial-Quellcode und den Earth Book-Inhalt.

    Wie das grundlegende Schema ist es um Blöcke organisiert. chunk_id identifiziert jeden Block eindeutig. Das Feld text_vector ist eine Einbettung des Blocks. Das Nicht-Vektorfeld chunk ist eine lesbare Zeichenfolge. title ist einem eindeutigen Metadatenspeicherpfad für die Blobs zugeordnet. parent_id ist das einzige Feld auf übergeordneter Ebene und eine base64-codierte Version des URI der übergeordneten Datei.

    Das Schema enthält auch ein locations-Feld zum Speichern generierter Inhalte, die von der Indizierungspipeline erstellt werden.

     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")  
    
  4. Bei einem Indexschema, das strukturierte Inhalte genauer nachahmt, hätten Sie separate Indizes für übergeordnete und untergeordnete (in Blöcke unterteilte) Felder. Sie würden Indexprojektionen benötigen, um die Indizierung der beiden Indizes gleichzeitig zu koordinieren. Abfragen werden für den untergeordneten Index ausgeführt. Die Abfragelogik enthält eine Lookup-Abfrage, wobei „parent_idt“ verwendet wird, um Inhalte aus dem übergeordneten Index abzurufen.

    Felder im untergeordneten Index:

    • Kennung
    • Block
    • chunkVectcor
    • parent_id

    Felder im übergeordneten Index (alles, von dem eines benötigt wird):

    • parent_id
    • Felder auf übergeordneter Ebene (Name, Titel, Kategorie)

Nächster Schritt