Condividi tramite


Esercitazione: creare una pipeline di indicizzazione per RAG in Azure AI Search

Informazioni su come creare una pipeline di indicizzazione automatizzata per una soluzione RAG in Azure AI Search. L'automazione dell'indicizzazione avviene tramite un indicizzatore che supporta l'indicizzazione e l'esecuzione del set di competenze, fornendo la suddivisione in blocchi e la vettorizzazione dei dati integrati su base occasionale o ricorrente per gli aggiornamenti incrementali.

In questa esercitazione:

  • Specificare lo schema dell'indice dell'esercitazione precedente
  • Crea connessione di origine dati
  • Creare un indicizzatore
  • Creare un set di competenze che scomponga, vettorizzi e riconosca le entità
  • Eseguire l'indicizzatore e verificare i risultati

Se non si ha una sottoscrizione di Azure, creare un account gratuito prima di iniziare.

Suggerimento

È possibile usare la Procedura guidata di importazione e vettorizzazione dei dati per creare la pipeline. Per avvii rapidi, vedere Ricerca immagini e Ricerca vettoriale.

Prerequisiti

Scaricare l'esempio

Scaricare un Jupyter Notebook da GitHub per inviare le richieste ad Azure AI Search. Per altre informazioni, vedere Download di file da GitHub.

Specificare lo schema dell'indice

Aprire o creare un Jupyter Notebook (.ipynb) in Visual Studio Code per contenere gli script che costituiscono la pipeline. I passaggi iniziali consentono di installare i pacchetti e di raccogliere le variabili per le connessioni. Dopo aver completato i passaggi di installazione, è possibile iniziare con i componenti della pipeline di indicizzazione.

Si inizia con lo schema dell'indice dell'esercitazione precedente. La struttura si basa su blocchi vettorializzati e nonvettorializzati. Include un campo locations che archivia i contenuti generati dall'intelligenza artificiale creati dal set di competenze.

index_name = "py-rag-tutorial-idx"
index_client = SearchIndexClient(endpoint=AZURE_SEARCH_SERVICE, credential=AZURE_SEARCH_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=1536, 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="myOpenAI",  
        )
    ],  
    vectorizers=[  
        AzureOpenAIVectorizer(  
            name="myOpenAI",  
            kind="azureOpenAI",  
            azure_open_ai_parameters=AzureOpenAIParameters(  
                resource_uri=AZURE_OPENAI_ACCOUNT,  
                deployment_id="text-embedding-ada-002",
                model_name="text-embedding-ada-002"
            ),
        ),  
    ],  
)  
    
# Create the search index on Azure AI Search
index = SearchIndex(name=index_name, fields=fields, vector_search=vector_search)  
result = index_client.create_or_update_index(index)  
print(f"{result.name} created")  

Crea connessione di origine dati

In questo passaggio, si configurano i dati di esempio e la connessione ad Archiviazione BLOB di Azure. L'indicizzatore recupera i PDF da un contenitore. È possibile creare il contenitore e caricare i file in questo passaggio.

L'ebook originale è di grandi dimensioni, oltre 100 pagine e 35 MB. È stato suddiviso in PDF più piccoli, uno per pagina di testo, per rimanere al di sotto del limite di payload dell'API di 16 MB per ogni chiamata API e anche i limiti dei dati di arricchimento tramite intelligenza artificiale. Per semplicità, in questo esercizio viene tralasciata la vettorializzazione delle immagini.

  1. Accedere al portale di Azure e individuare l'account di archiviazione di Azure.

  2. Creare un contenitore e caricare i PDF da earth_book_2019_text_pages.

  3. Assicurarsi che Azure AI Search disponga delle autorizzazioni di lettura dei dati dei BLOB di archiviazione per la risorsa.

  4. Successivamente, in Visual Studio Code, definire un'origine dati dell'indicizzatore che fornisca informazioni di connessione durante l'indicizzazione.

    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=AZURE_SEARCH_CREDENTIAL)
    container = SearchIndexerDataContainer(name="nasa-ebook-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")
    

Creare un set di competenze

Le competenze sono la base per la suddivisione dei dati integrata e la vettorializzazione. Come minimo, si vuole che una competenza di Suddivisione testo suddivida in blocchi i contenuti e una competenza di incorporamento crei rappresentazioni vettoriali dei contenuti in blocchi.

In questo set di competenze, viene usata una competenza aggiuntiva per creare dati strutturati nell'indice. La competenza di Riconoscimento entità viene usata per identificare le posizioni, che possono variare dai nomi appropriati ai riferimenti generici, ad esempio "oceano" o "montagna". La creazione di dati strutturati offre più opzioni per la creazione di query interessanti e per aumentare la pertinenza.

from azure.search.documents.indexes.models import (
    SplitSkill,
    InputFieldMappingEntry,
    OutputFieldMappingEntry,
    AzureOpenAIEmbeddingSkill,
    EntityRecognitionSkill,
    SearchIndexerIndexProjections,
    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_uri=AZURE_OPENAI_ACCOUNT,  
    deployment_id="text-embedding-ada-002",  
    model_name="text-embedding-ada-002",
    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 = SearchIndexerIndexProjections(  
    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_projections=index_projections,
    cognitive_services_account=cognitive_services_account
)
  
client = SearchIndexerClient(endpoint=AZURE_SEARCH_SERVICE, credential=AZURE_SEARCH_CREDENTIAL)  
client.create_or_update_skillset(skillset)  
print(f"{skillset.name} created")  

Creare ed eseguire l'indicizzatore

Gli indicizzatori sono il componente che imposta tutti i processi in movimento. È possibile creare un indicizzatore in uno stato disabilitato, ma l'impostazione predefinita consiste nell'eseguirlo immediatamente. In questa esercitazione si crea e si esegue l'indicizzatore per recuperare i dati dall'archivio BLOB, si eseguono le competenze, tra cui la suddivisione in blocchi e vettorizzazione, e si carica l'indice.

L'esecuzione dell'indicizzatore richiede alcuni minuti. Al termine, è possibile andare al passaggio finale: eseguire query sull'indice.

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=AZURE_SEARCH_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.')  

Eseguire una query per verificare i risultati

Inviare una query per verificare che l'indice sia operativo. Questa richiesta converte la stringa di testo "where are the nasa headquarters located?" in un vettore per la ricerca vettoriale. I risultati sono costituiti dai campi nell'istruzione di selezione, alcuni dei quali vengono stampati come output.

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

# Hybrid Search
query = "where are the nasa headquarters located?"  

search_client = SearchClient(endpoint=AZURE_SEARCH_SERVICE, credential=AZURE_SEARCH_CREDENTIAL, index_name=index_name)
vector_query = VectorizableTextQuery(text=query, k_nearest_neighbors=1, fields="text_vector", exhaustive=True)
  
results = search_client.search(  
    search_text=query,  
    vector_queries= [vector_query],
    select=["parent_id", "chunk_id", "title", "chunk", "locations"],
    top=1
)  
  
for result in results:  
    print(f"Score: {result['@search.score']}")
    print(f"Title: {result['title']}")
    print(f"Locations: {result['locations']}")
    print(f"Content: {result['chunk']}") 

Questa query restituisce una singola corrispondenza (top=1) costituita da un blocco determinato dal motore di ricerca come il più rilevante. I risultati della query devono essere simili all'esempio seguente:

Score: 0.03306011110544205
Title: page-178.pdf
Locations: ['Headquarters', 'Washington']
Content: national Aeronautics and Space Administration

earth Science

NASA Headquarters 

300 E Street SW 

Washington, DC 20546

www.nasa.gov

np-2018-05-2546-hQ

Provare altre query per ottenere un'idea di ciò che il motore di ricerca restituisce direttamente, in modo da poterlo confrontare con una risposta abilitata per l'LLM. Eseguire di nuovo lo script precedente con la query seguente: "how much of the earth is covered in water"?

I risultati di questa seconda query devono essere simili ai risultati seguenti, che vengono modificati leggermente per brevità.

Score: 0.03333333507180214
Content:

Land of Lakes
Canada

During the last Ice Age, nearly all of Canada was covered by a massive ice sheet. Thousands of years later, the landscape still shows 

the scars of that icy earth-mover. Surfaces that were scoured by retreating ice and flooded by Arctic seas are now dotted with 

millions of lakes, ponds, and streams. In this false-color view from the Terra satellite, water is various shades of blue, green, tan, and 

black, depending on the amount of suspended sediment and phytoplankton; vegetation is red.

The region of Nunavut Territory is sometimes referred to as the “Barren Grounds,” as it is nearly treeless and largely unsuitable for 

agriculture. The ground is snow-covered for much of the year, and the soil typically remains frozen (permafrost) even during the 

summer thaw. Nonetheless, this July 2001 image shows plenty of surface vegetation in midsummer, including lichens, mosses, 

shrubs, and grasses. The abundant fresh water also means the area is teeming with flies and mosquitoes.

Con questo esempio, è più facile individuare il modo in cui i blocchi vengono restituiti in modo dettagliato e come la parola chiave e la ricerca di somiglianza identificano le corrispondenze principali. Questo blocco specifico contiene sicuramente informazioni sull'acqua e sulla copertura sulla terra, ma non è esattamente rilevante per la query. Il classificatore semantico troverebbe una risposta migliore, ma nel passaggio successivo mostrerà come connettere Azure AI Search a un modello linguistico di grandi dimensioni (LLM) per la ricerca conversazionale.

Passaggio successivo