Partager via


Tutoriel : Concevoir un index pour RAG dans Recherche Azure AI

Un index contient du texte et du contenu vectoriel, ainsi que des configurations, dans lesquels vous pouvez faire des recherches. Dans un modèle RAG qui utilise un modèle de conversation pour les réponses, vous souhaitez un index qui contient des blocs de contenu qui peuvent être passés à un LLM quand il est interrogé.

Dans ce tutoriel, vous allez :

  • Découvrir les caractéristiques d’un schéma d’index créé pour RAG
  • Créer un index qui s’adapte aux vecteurs et aux requêtes hybrides
  • Ajouter des profils et des configurations de vecteur
  • Ajouter des données structurées
  • Ajouter la fonctionnalité de filtrage

Prérequis

Visual Studio Code avec l’extension Python et le package Jupyter. Pour plus d’informations, consultez Python dans Visual Studio Code.

Le résultat de cet exercice est une définition d’index en JSON. À ce stade, il n’est pas chargé dans Recherche Azure AI, donc aucun service cloud ni aucune autorisation ne sont requis dans cet exercice.

Examiner les points à prendre en considération pour le schéma pour RAG

Dans la recherche conversationnelle, les LLM composent la réponse que l’utilisateur voit, et non le moteur de recherche, de sorte que vous n’avez pas besoin de vous soucier des champs à montrer dans vos résultats de recherche ni de la cohérence des représentations des documents de recherche individuels pour l’utilisateur. Selon la question, le LLM peut retourner un contenu détaillé de votre index, ou plus probablement, retravailler le contenu pour obtenir une meilleure réponse.

Organisé autour de blocs

Lorsque les modèles LLM génèrent une réponse, ils opèrent sur des blocs de contenu pour les entrées de message, et même s’ils doivent savoir d’où le bloc provient à des fins de citation, ce qui importe le plus est la qualité des entrées de message et leur pertinence vis-à-vis de la question de l’utilisateur. Que les blocs proviennent d’un seul document ou d’un millier, le LLM ingère les informations ou les données d’ancrage, et formule la réponse à l’aide d’instructions fournies dans un prompt système.

Les blocs sont au centre du schéma, et chaque bloc est l’élément majeur d’un document de recherche dans un modèle RAG. Vous pouvez considérer votre index comme une grande collection de blocs, par opposition aux documents de recherche traditionnels qui ont probablement une structure plus détaillée, comme les champs contenant du contenu uniforme pour un nom, des descriptions, des catégories et des adresses.

Amélioré avec les données générées

Dans ce tutoriel, des échantillons de données se composent de fichiers PDF et du contenu du livre de la NASA sur la Terre. Ce contenu est descriptif et éducatif, avec de nombreuses références aux zones géographiques, pays et régions du monde entier. Tout le contenu textuel est capturé en blocs, mais les instances récurrentes de noms de lieux créent une opportunité pour ajouter une structure à l’index. À l’aide de compétences, il est possible de reconnaître des entités dans le texte et de les capturer dans un index à utiliser dans les requêtes et les filtres. Dans ce tutoriel, nous incluons une compétence de reconnaissance d’entité qui reconnaît et extrait les entités d’emplacement, en les chargeant dans un champ locations pouvant faire l’objet d’une recherche et d’un filtre. L’ajout de contenu structuré à votre index vous offre davantage d’options pour filtrer, une pertinence améliorée, ainsi que des réponses plus précises.

Champs parent-enfant dans un ou deux index ?

Le contenu en bloc dérive généralement d’un document plus grand. Bien que le schéma soit organisé autour de blocs, vous souhaitez également capturer des propriétés et du contenu au niveau parent. Par exemple, ces propriétés peuvent inclure le chemin, le titre, les auteurs, la date de publication ou un résumé du fichier parent.

Un point d’inflexion dans la conception de schéma est d’avoir deux index pour le contenu parent et enfant/en bloc, ou un seul index qui répète les éléments parents pour chaque bloc.

Dans ce tutoriel, étant donné que tous les blocs de texte proviennent d’un parent unique (le livre de la NASA sur la Terre), vous n’avez pas besoin d’un index distinct dédié au niveau supérieur des champs parents. Toutefois, si vous effectuez une indexation à partir de plusieurs fichiers PDF parents, vous souhaiterez peut-être une paire d’index parent-enfant pour capturer les champs à des niveaux spécifiques, puis envoyer des requêtes de recherche à l’index parent pour extraire ces champs en rapport avec chaque bloc.

Check-list des points à prendre en considération pour le schéma

Dans Recherche Azure AI, un index qui fonctionne le mieux pour les charges de travail RAG présente ces qualités :

  • Retourne des blocs pertinents par rapport à la requête et lisibles par le LLM. Les LLM peuvent gérer un certain niveau de données inutiles dans les blocs, telles que le balisage, la redondance et les chaînes incomplètes. Même si les blocs doivent être lisibles et en rapport avec la question, ils n’ont pas besoin d’être impeccables.

  • Gère une relation parent-enfant entre les blocs d’un document et les propriétés du document parent, telles que le nom de fichier, le type de fichier, le titre, l’auteur, etc. Pour répondre à une requête, les blocs peuvent être extraits de n’importe où dans l’index. L’association avec le document parent fournissant le bloc est utile pour le contexte, les citations et les requêtes de suivi.

  • S’adapte aux requêtes que vous souhaitez créer. Vous devez disposer de champs pour les contenus vectoriels et hybrides, et ces champs doivent être attribués de manière à prendre en charge des comportements de requête spécifiques, tels que la possibilité d’effectuer des recherches ou de filtrer. Vous ne pouvez interroger qu’un seul index à la fois (aucune jointure), de sorte que votre collection de champs doit définir l’intégralité de votre contenu dans lequel rechercher.

  • Votre schéma doit être plat (aucun type ou structure complexe), ou vous devez mettre en forme la sortie des types complexes sous forme de JSON avant de l’envoyer au LLM. Cette condition requise est spécifique au modèle RAG dans Recherche Azure AI.

Créer un index pour les charges de travail RAG

Un index minimal pour LLM est conçu pour stocker des blocs de contenu. Il inclut généralement des champs vectoriels si vous souhaitez rechercher des similarités pour obtenir des résultats hautement pertinents. Il inclut aussi des champs non vectoriels pour les entrées lisibles par les humains dans le LLM pour la recherche conversationnelle. Le contenu en bloc non vectoriel dans les résultats de la recherche devient les données d’ancrage envoyées au LLM.

  1. Ouvrez Visual Studio Code et créez un fichier. Il n’est pas obligé d’être un type de fichier Python pour cet exercice.

  2. Voici une définition d’index minimal pour les solutions RAG qui prennent en charge la recherche vectorielle et hybride. Passez-la en revue pour avoir une introduction aux éléments requis : nom d’index, champs et section de configuration pour les champs vectoriels.

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

    Les champs doivent inclure un champ clé ("id" dans cet exemple), ainsi que des blocs vectoriels pour la recherche de similarités et des blocs non vectoriels pour les entrées dans le LLM.

    Les champs vectoriels sont associés à des algorithmes qui déterminent les chemins de recherche au moment de la requête. L’index a une section vectorSearch pour spécifier plusieurs configurations d’algorithme. Les champs vectoriels ont également des types spécifiques et des attributs supplémentaires pour incorporer les dimensions du modèle. Edm.Single est un type de données qui fonctionne pour les LLM couramment utilisés. Pour plus d’informations sur les champs vectoriels, consultez Créer un index vectoriel.

    Les champs de métadonnées peuvent être le chemin, la date de création ou le type de contenu du fichier parent et sont utiles pour les filtres.

  3. Voici le schéma d’index pour le code source du tutoriel et le contenu du livre sur la Terre.

    Comme le schéma de base, il est organisé autour de blocs. Le chunk_id identifie de manière unique chaque bloc. Le champ text_vector est une incorporation du bloc. Le champ chunk non vectoriel est une chaîne lisible. Le title est mappé à un chemin de stockage de métadonnées unique pour les blobs. Le parent_id est le seul champ de niveau parent et il s’agit d’une version encodée en base64 de l’URI du fichier parent.

    Le schéma inclut également un champ locations pour stocker le contenu généré qui est créé par le pipeline d’indexation.

     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. Pour un schéma d’index qui se rapproche plus d’un contenu structuré, vous avez des index distincts pour les champs parents et enfants (en bloc). Vous aurez besoin de projections d’index pour coordonner simultanément l’indexation des deux index. Les requêtes s’exécutent sur l’index enfant. La logique de requête inclut une requête de recherche, utilisant l’index parent_idt pour extraire le contenu de l’index parent.

    Champs de l’index enfant :

    • id
    • segment
    • chunkVectcor
    • parent_id

    Champs de l’index parent (tout ce que vous voulez de type « l’un de ») :

    • parent_id
    • champs de niveau parent (nom, titre, catégorie)

Étape suivante