Partager via


Tutoriel : Créer un pipeline d’indexation pour RAG sur Recherche Azure AI

Découvrez comment créer un pipeline d’indexation automatisé pour une solution RAG sur Recherche Azure AI. L’automatisation de l’indexation consiste à utiliser un indexeur qui pilote l’indexation et l’exécution de l’ensemble de compétences, fournissant une segmentation et une vectorisation des données intégrées de façon ponctuelle ou récurrente pour les mises à jour incrémentielles.

Dans ce tutoriel, vous allez :

  • Fournir le schéma d’index provenant du tutoriel précédent
  • Créer une connexion à la source de données
  • Créer un indexeur
  • Créer un ensemble de compétences qui segmente, vectorise et reconnaît les entités
  • Exécuter l’indexeur et vérifier les résultats

Si vous n’avez pas d’abonnement Azure, créez un compte gratuit avant de commencer.

Conseil

Vous pouvez utiliser l’Assistant Importation et vectorisation des données pour créer votre pipeline. Essayez des guides de démarrage rapide : Recherche d’images et Recherche vectorielle.

Prérequis

Télécharger l’exemple

Téléchargez un notebook Jupyter depuis GitHub pour envoyer les requêtes à Recherche Azure AI. Si vous souhaitez obtenir plus d’informations, consultez Téléchargement de fichiers à partir de GitHub.

Fournir le schéma d’index

Ouvrez ou créez un notebook Jupyter (.ipynb) dans Visual Studio Code, destiné à contenir les scripts qui composent le pipeline. Les étapes initiales installent les packages et collectent des variables pour les connexions. Une fois les étapes d’installation terminées, vous êtes prêt à commencer par les composants du pipeline d’indexation.

Commençons par le schéma d’index provenant du tutoriel précédent. Il est organisé autour de blocs vectorisés et non vectorisés. Il inclut un champ locations qui stocke le contenu généré par l’IA et créé par l’ensemble de compétences.

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")  

Créer une connexion à la source de données

Dans cette étape, configurez les exemples de données et une connexion à Stockage Blob Azure. L’indexeur récupère les fichiers PDF auprès d’un conteneur. Dans cette étape, vous créez le conteneur et vous chargez des fichiers.

Le livre électronique d’origine est volumineux, plus de 100 pages, et sa taille s’élève à 35 Mo. Nous l’avons divisé en fichiers PDF plus petits, un par page de texte, pour rester sous la limite de documents pour les indexeurs de 16 Mo par appel d’API, et en dessous des limites de données d’enrichissement par IA. Pour rester simples, nous omettons la vectorisation d’image pour cet exercice.

  1. Connectez-vous au portail Azure et recherchez votre compte de stockage Azure.

  2. Créez un conteneur et chargez les fichiers PDF depuis earth_book_2019_text_pages.

  3. Vérifiez que Recherche Azure AI dispose des autorisations Lecteur des données Blob du stockage sur la ressource.

  4. Ensuite, dans Visual Studio Code, définissez une source de données d’indexeur qui fournit des informations de connexion lors de l’indexation.

    from azure.search.documents.indexes import SearchIndexerClient
    from azure.search.documents.indexes.models import (
        SearchIndexerDataContainer,
        SearchIndexerDataSourceConnection
    )
    
    # Create a data source 
    indexer_client = SearchIndexerClient(endpoint=AZURE_SEARCH_SERVICE, credential=credential)
    container = SearchIndexerDataContainer(name="nasa-ebooks-pdfs-all")
    data_source_connection = SearchIndexerDataSourceConnection(
        name="py-rag-tutorial-ds",
        type="azureblob",
        connection_string=AZURE_STORAGE_CONNECTION,
        container=container
    )
    data_source = indexer_client.create_or_update_data_source_connection(data_source_connection)
    
    print(f"Data source '{data_source.name}' created or updated")
    

Si vous configurez une identité managée pour Recherche Azure AI pour la connexion, la chaîne de connexion inclut un suffixe ResourceId=. Le résultat doit ressembler à l’exemple suivant : "ResourceId=/subscriptions/FAKE-SUBCRIPTION=ID/resourceGroups/FAKE-RESOURCE-GROUP/providers/Microsoft.Storage/storageAccounts/FAKE-ACCOUNT;"

Créer un ensemble de compétences

Les compétences constituent la base de la segmentation et de la vectorisation des données intégrées. Au minimum, vous voulez qu’une compétence Fractionnement de texte segmente votre contenu, et une compétence d’incorporation qui crée des représentations vectorielles de votre contenu segmenté.

Dans cet ensemble de compétences, une compétence supplémentaire est utilisée pour créer des données structurées dans l’index. La compétence Reconnaissance d’entités est utilisée pour identifier les emplacements, qui peuvent aller des noms propres à des références génériques, comme « océan » ou « montagne ». Le fait d’avoir des données structurées vous offre davantage d’options pour créer des requêtes intéressantes et améliorer la pertinence.

AZURE_AI_MULTISERVICE_KEY est nécessaire même si vous utilisez le contrôle d’accès en fonction du rôle. Recherche Azure AI utilise la clé à des fins de facturation et elle est requise, sauf si vos charges de travail restent sous la limite gratuite. Vous pouvez également établir une connexion sans clé si vous utilisez l’API ou les packages bêta les plus récents. Pour plus d’informations, consultez Attacher une ressource multiservices Azure AI à un ensemble de compétences.

from azure.search.documents.indexes.models import (
    SplitSkill,
    InputFieldMappingEntry,
    OutputFieldMappingEntry,
    AzureOpenAIEmbeddingSkill,
    EntityRecognitionSkill,
    SearchIndexerIndexProjection,
    SearchIndexerIndexProjectionSelector,
    SearchIndexerIndexProjectionsParameters,
    IndexProjectionMode,
    SearchIndexerSkillset,
    CognitiveServicesAccountKey
)

# Create a skillset  
skillset_name = "py-rag-tutorial-ss"

split_skill = SplitSkill(  
    description="Split skill to chunk documents",  
    text_split_mode="pages",  
    context="/document",  
    maximum_page_length=2000,  
    page_overlap_length=500,  
    inputs=[  
        InputFieldMappingEntry(name="text", source="/document/content"),  
    ],  
    outputs=[  
        OutputFieldMappingEntry(name="textItems", target_name="pages")  
    ],  
)  
  
embedding_skill = AzureOpenAIEmbeddingSkill(  
    description="Skill to generate embeddings via Azure OpenAI",  
    context="/document/pages/*",  
    resource_url=AZURE_OPENAI_ACCOUNT,  
    deployment_name="text-embedding-3-large",  
    model_name="text-embedding-3-large",
    dimensions=1536,
    inputs=[  
        InputFieldMappingEntry(name="text", source="/document/pages/*"),  
    ],  
    outputs=[  
        OutputFieldMappingEntry(name="embedding", target_name="text_vector")  
    ],  
)

entity_skill = EntityRecognitionSkill(
    description="Skill to recognize entities in text",
    context="/document/pages/*",
    categories=["Location"],
    default_language_code="en",
    inputs=[
        InputFieldMappingEntry(name="text", source="/document/pages/*")
    ],
    outputs=[
        OutputFieldMappingEntry(name="locations", target_name="locations")
    ]
)
  
index_projections = SearchIndexerIndexProjection(  
    selectors=[  
        SearchIndexerIndexProjectionSelector(  
            target_index_name=index_name,  
            parent_key_field_name="parent_id",  
            source_context="/document/pages/*",  
            mappings=[  
                InputFieldMappingEntry(name="chunk", source="/document/pages/*"),  
                InputFieldMappingEntry(name="text_vector", source="/document/pages/*/text_vector"),
                InputFieldMappingEntry(name="locations", source="/document/pages/*/locations"),  
                InputFieldMappingEntry(name="title", source="/document/metadata_storage_name"),  
            ],  
        ),  
    ],  
    parameters=SearchIndexerIndexProjectionsParameters(  
        projection_mode=IndexProjectionMode.SKIP_INDEXING_PARENT_DOCUMENTS  
    ),  
) 

cognitive_services_account = CognitiveServicesAccountKey(key=AZURE_AI_MULTISERVICE_KEY)

skills = [split_skill, embedding_skill, entity_skill]

skillset = SearchIndexerSkillset(  
    name=skillset_name,  
    description="Skillset to chunk documents and generating embeddings",  
    skills=skills,  
    index_projection=index_projections,
    cognitive_services_account=cognitive_services_account
)
  
client = SearchIndexerClient(endpoint=AZURE_SEARCH_SERVICE, credential=credential)  
client.create_or_update_skillset(skillset)  
print(f"{skillset.name} created")

Créer et exécuter l’indexeur

Les indexeurs sont les composants qui définissent tous les processus en mouvement. Vous pouvez créer un indexeur dans un état désactivé, mais l’action par défaut est de l’exécuter immédiatement. Dans ce tutoriel, vous créez et vous exécutez l’indexeur pour récupérer les données auprès de Stockage Blob, vous exécutez les compétences, notamment la segmentation et la vectorisation, et vous chargez l’index.

L’exécution de l’indexeur prend plusieurs minutes. Quand elle est terminée, vous pouvez passer à l’étape finale : interroger votre index.

from azure.search.documents.indexes.models import (
    SearchIndexer,
    FieldMapping
)

# Create an indexer  
indexer_name = "py-rag-tutorial-idxr" 

indexer_parameters = None

indexer = SearchIndexer(  
    name=indexer_name,  
    description="Indexer to index documents and generate embeddings",  
    skillset_name=skillset_name,  
    target_index_name=index_name,  
    data_source_name=data_source.name,
    # Map the metadata_storage_name field to the title field in the index to display the PDF title in the search results  
    field_mappings=[FieldMapping(source_field_name="metadata_storage_name", target_field_name="title")],
    parameters=indexer_parameters
)  

# Create and run the indexer  
indexer_client = SearchIndexerClient(endpoint=AZURE_SEARCH_SERVICE, credential=credential)  
indexer_result = indexer_client.create_or_update_indexer(indexer)  

print(f' {indexer_name} is created and running. Give the indexer a few minutes before running a query.')    

Exécuter une requête pour vérifier les résultats

Envoyez une requête pour vérifier que votre index est opérationnel. Cette requête convertit la chaîne de texte « what's NASA's website? » en vecteur pour une recherche vectorielle. Les résultats sont constitués des champs de l’instruction select, certains étant affichés en sortie.

Il n’y a pas de conversation ni d’IA générative à ce stade. Les résultats sont du contenu détaillé de votre index de recherche.

from azure.search.documents import SearchClient
from azure.search.documents.models import VectorizableTextQuery

# Vector Search using text-to-vector conversion of the querystring
query = "what's NASA's website?"  

search_client = SearchClient(endpoint=AZURE_SEARCH_SERVICE, credential=credential, index_name=index_name)
vector_query = VectorizableTextQuery(text=query, k_nearest_neighbors=50, fields="text_vector")
  
results = search_client.search(  
    search_text=query,  
    vector_queries= [vector_query],
    select=["chunk"],
    top=1
)  
  
for result in results:  
    print(f"Score: {result['@search.score']}")
    print(f"Chunk: {result['chunk']}")

Cette requête retourne une seule correspondance (top=1) composée d’un bloc déterminé par le moteur de recherche comme étant le plus pertinent. Les résultats de la première requête doivent être similaires à l’exemple suivant :

Score: 0.01666666753590107
Chunk: national Aeronautics and Space Administration

earth Science

NASA Headquarters 

300 E Street SW 

Washington, DC 20546

www.nasa.gov

np-2018-05-2546-hQ

Essayez quelques autres requêtes pour avoir une idée de ce que le moteur de recherche retourne directement et pouvoir le comparer à une réponse produite par un LLM. Réexécutez le script précédent avec cette requête : "patagonia geography" et définissez top sur 3 pour renvoyer plusieurs réponses.

Les résultats de cette deuxième requête doivent être similaires aux résultats suivants, qui sont légèrement modifiés pour des raisons de concision. La sortie est copiée à partir du notebook, qui tronque la réponse à ce que vous voyez dans cet exemple. Vous pouvez développer la sortie de la cellule pour passer en revue la réponse complète.

Score: 0.03306011110544205
Chunk: 

Swirling Bloom off Patagonia
Argentina

Interesting art often springs out of the convergence of different ideas and influences. 
And so it is with nature. 

Off the coast of Argentina, two strong ocean currents converge and often stir up a colorful 
brew, as shown in this Aqua image from 

December 2010. 

This milky green and blue bloom formed on the continental shelf off of Patagonia, where warmer, 
saltier waters from the subtropics 

meet colder, fresher waters flowing from the south. Where these currents collide, turbulent 
eddies and swirls form, pulling nutrients 

up from the deep ocean. The nearby Rio de la Plata also deposits nitrogen- and iron-laden 
sediment into the sea. Add in some 
...

while others terminate in water. The San Rafael and San Quintín glaciers (shown at the right) 
are the icefield’s largest. Both have 

been receding rapidly in the past 30 years.

Avec cet exemple, il est plus facile de repérer la façon dont les blocs sont retournés mot pour mot, et comment la recherche de mots clés et de similarité identifient les correspondances principales. Ce bloc spécifique a certainement des informations sur la Patagonie et la géographie, mais il n’est pas exactement pertinent pour la requête. Le classeur sémantique favoriserait des segments plus pertinents pour une réponse de meilleure qualité, mais nous allons voir dans l’étape suivante comment connecter Recherche Azure AI à un LLM pour la recherche conversationnelle.

Étape suivante