Condividi tramite


Esercitazione: Progettare un indice per RAG in Azure AI Search

Un indice contiene contenuti testuali e vettoriali ricercabili, oltre a configurazioni. In uno schema RAG che usa un modello di chat per le risposte, si vuole un indice progettato intorno a blocchi di contenuto che possono essere passati a un LLM in fase di query.

In questa esercitazione:

  • Informazioni sulle caratteristiche di uno schema di indice creato per RAG
  • Creare un indice che supporta query vettoriali e ibride
  • Aggiungere profili vettoriali e configurazioni
  • Aggiungere dati strutturati
  • Aggiungere un filtro

Prerequisiti

Visual Studio Code con l'estensione Python e il pacchetto Jupyter. Per altre informazioni, vedere Python in Visual Studio Code.

Il risultato di questo esercizio è una definizione di indice in JSON. A questo punto, non viene caricato su Azure AI Search, quindi non ci sono i requisiti per i servizi cloud o le autorizzazioni in questo esercizio.

Rivedere le considerazioni sullo schema per RAG

Nella ricerca conversazionale, gli LLM compongono la risposta visualizzata dall'utente, non il motore di ricerca, quindi non è necessario pensare a quali campi mostrare nei risultati della ricerca e se le rappresentazioni dei singoli documenti di ricerca siano coerenti per l'utente. A seconda della domanda, l'LLM potrebbe restituire il contenuto testuale dell'indice o, più probabilmente, riproporre il contenuto per ottenere una risposta migliore.

Organizzato in base a blocchi

Quando i LLM generano una risposta, operano su blocchi di contenuto come input del messaggio e, anche se devono sapere da dove proviene il blocco ai fini della citazione, ciò che conta di più è la qualità degli input del messaggio e la sua rilevanza rispetto alla domanda dell'utente. Indipendentemente dal fatto che i blocchi provengano da uno o da mille documenti, l'LLM inserisce le informazioni o i dati di base e formula la risposta tramite le istruzioni fornite in un prompt del sistema.

I blocchi sono il focus dello schema e ogni blocco è l'elemento che definisce un documento di ricerca in un modello RAG. Si può pensare all'indice come a una grande raccolta di blocchi, a differenza dei documenti di ricerca tradizionali che probabilmente hanno una struttura più articolata, come a campi con contenuti uniformi per un nome, descrizioni, categorie e indirizzi.

Migliorato con i dati generati

In questa esercitazione, i dati di esempio sono costituiti da PDF e contenuti del NASA Earth Book. Il contenuto è di tipo descrittivo e informativo, con numerosi riferimenti a luoghi geografici, paesi e aree del mondo. Tutto il contenuto testuale viene acquisito in blocchi, ma le istanze ricorrenti dei nomi dei posti creano un'opportunità per aggiungere struttura all'indice. Usando le competenze, è possibile riconoscere le entità nel testo e acquisirle in un indice da usare nelle query e nei filtri. In questa esercitazione è inclusa una competenza di riconoscimento delle entità che riconosce ed estrae le entità delle località, caricandole in un campo locations in cui è possibile eseguire ricerche e applicare filtri. L'aggiunta di contenuti strutturati all'indice offre più opzioni per filtrare, migliorare la pertinenza e ottenere risposte più specifiche.

Campi padre-figlio in uno o due indici?

Il contenuto in blocchi deriva in genere da un documento più grande. Anche se lo schema è organizzato in base ai blocchi, è opportuno acquisire anche le proprietà e i contenuti a livello padre. Esempi di queste proprietà possono includere il percorso del file padre, il titolo, gli autori, la data di pubblicazione o un riepilogo.

Il punto di flessione nella progettazione dello schema è la scelta di avere due indici per il contenuto padre e figlio/in blocchi o un singolo indice che ripete gli elementi padre per ogni blocco.

In questa esercitazione, poiché tutti i blocchi di testo provengono da un singolo elemento padre (NASA Earth Book), non è necessario un indice separato dedicato al livello superiore dei campi padre. Tuttavia, se si esegue l'indicizzazione da più PDF padre, è possibile che una coppia di indici padre-figlio acquisisca campi specifici del livello e quindi invii query di ricerca all'indice padre per recuperare i campi pertinenti per ogni blocco.

Elenco di controllo delle considerazioni sullo schema

In Azure AI Search, l'indice ottimale per i carichi di lavoro RAG ha queste qualità:

  • Restituisce blocchi pertinenti per la query e leggibili dall'LLM. Gli LLM possono gestire un certo livello di dati sporchi in blocchi, come mark up, ridondanza e stringhe incomplete. Anche se i blocchi devono essere leggibili e pertinenti per la domanda, non è necessario che siano intatti.

  • Mantiene una relazione padre-figlio tra blocchi di un documento e le proprietà del documento padre, ad esempio il nome file, il tipo di file, il titolo, l'autore e così via. Per rispondere a una query, i blocchi possono essere estratti da qualsiasi punto dell'indice. L'associazione con il documento padre che fornisce il blocco è utile per il contesto, le citazioni e le query di completamento.

  • Supporta le query da creare. È necessario avere campi per il contenuto vettoriale e ibrido e questi campi devono essere attribuiti per supportare comportamenti di query specifici, ad esempio ricercabile o filtrabile. È possibile eseguire una query su un solo indice alla volta (senza join) in modo che la raccolta di campi definisca tutto il contenuto ricercabile.

  • Lo schema deve essere flat (nessun tipo o strutture complesse) oppure formattare l'output del tipo complesso come JSON prima di inviarlo all'LLM. Questo requisito è specifico del modello RAG in Azure AI Search.

Nota

La progettazione dello schema influisce sull'archiviazione e sui costi. Questo esercizio è incentrato sui concetti fondamentali dello schema. Nell'esercitazione Ridurre al minimo lo spazio di archiviazione e i costi è possibile rivedere gli schemi per informazioni su come limitare significativamente i tipi di dati, la compressione e le opzioni di archiviazione riducono significativamente la quantità di spazio di archiviazione usata dai vettori.

Creare un indice per i carichi di lavoro RAG

Un indice minimo per LLM è progettato per archiviare blocchi di contenuto. In genere include campi vettoriali se si vuole effettuare una ricerca di somiglianza con risultati altamente pertinenti. Include anche campi non vettoriali per gli input leggibili dall'uomo all'LLM per la ricerca conversazionale. I contenuti non vettoriali in blocchi dei risultati della ricerca diventano i dati di base inviati all'LLM.

  1. Aprire Visual Studio Code e creare un nuovo file. Per questo esercizio non è necessario che il file sia di tipo Python.

  2. Ecco una definizione di indice minimo per le soluzioni RAG che supportano la ricerca vettoriale e ibrida. Esaminare l'argomento per un'introduzione agli elementi obbligatori: nome dell'indice, campi e sezione di configurazione per i campi vettoriali.

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

    I campi devono includere il campo chiave ("id" in questo esempio) e devono includere blocchi vettoriali per la ricerca di somiglianza e blocchi non vettoriali per gli input per l'LLM.

    I campi vettoriali sono associati ad algoritmi che determinano i percorsi di ricerca in fase di query. L'indice include una sezione vectorSearch per specificare più configurazioni dell'algoritmo. I campi vettoriali hanno anche tipi specifici e attributi aggiuntivi per incorporare le dimensioni del modello. Edm.Single è un tipo di dati che funziona per gli LLM di uso comune. Per altre informazioni sui campi vettoriali, vedere Creare un indice vettoriale.

    I campi dei metadati possono essere il percorso del file padre, la data di creazione o il tipo di contenuto e sono utili per i filtri.

  3. Ecco lo schema dell'indice per il codice sorgente dell'esercitazione e il contenuto dell'Earth Book.

    Come lo schema di base, è organizzato in blocchi. chunk_id identifica in modo univoco ogni blocco. Il campo text_vector è un incorporamento del blocco. Il campo non vettoriale chunk è una stringa leggibile. title esegue il mapping a un percorso di archiviazione dei metadati univoco per i BLOB. parent_id è l'unico campo a livello padre ed è una versione base64-encoded dell'URI del file padre.

    Nei carichi di lavoro di vettorizzazione integrata come quello usato in questa serie di esercitazioni, la dimensions proprietà nei campi vettoriali deve essere identica al numero di generati dalla competenza di dimensions incorporamento usata per vettorizzare i dati. In questa serie viene usata la competenza di incorporamento OpenAI di Azure, che chiama il modello text-embedding-3-large in Azure OpenAI. La competenza viene specificata nell'esercitazione successiva. Le dimensioni vengono impostate su 1024 sia nel campo vettoriale che nella definizione della competenza.

    Lo schema include anche un campo locations per l'archiviazione del contenuto generato creato dalla pipeline di indicizzazione.

     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. Per uno schema di indice che imita più fedelmente il contenuto strutturato, si dovrebbero avere indici separati per i campi padre e figlio (in blocchi). Sono necessarie proiezioni di indice per coordinare l'indicizzazione dei due indici contemporaneamente. Le query eseguite sull'indice figlio. La logica di query include una query di ricerca, usando il parent_idt recuperare il contenuto dall'indice padre.

    Campi nell'indice figlio:

    • ID
    • blocco
    • chunkVectcor
    • parent_id

    Campi nell'indice padre (tutto ciò che si vuole “uno di”):

    • parent_id
    • campi a livello padre (nome, titolo, categoria)

Passaggio successivo