Condividi tramite


Esercitazione: Progettare un indice per RAG in Azure AI Search

Un indice contiene contenuti testuali e vettoriali ricercabili, oltre a configurazioni. In un modello RAG che usa un modello di chat per le risposte, l'indice deve contenere blocchi di contenuto che possano essere trasferiti 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 supporti vettori e query 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 queste istanze ricorrenti dei nomi dei luoghi creano un'opportunità per aggiungere una 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 disporre di campi per i contenuti vettoriali e ibridi e questi campi devono essere attribuiti per supportare comportamenti di query specifici. È 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 semplice (senza tipi o strutture complesse). Questo requisito è specifico del modello RAG in Azure AI Search.

Anche se Azure AI Search non può unire gli indici, è possibile creare indici che mantengano la relazione padre-figlio e usare query sequenziali o parallele nella logica di ricerca per eseguire il pull da entrambi. Questo esercizio include modelli per gli elementi padre-figlio nello stesso indice e in indici separati, in cui le informazioni dall'indice padre vengono recuperate usando una query di ricerca.

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.

    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=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_name="myOpenAI",  
             )
         ],  
         vectorizers=[  
             AzureOpenAIVectorizer(  
                 vectorizer_name="myOpenAI",  
                 kind="azureOpenAI",  
                 parameters=AzureOpenAIVectorizerParameters(  
                     resource_url=AZURE_OPENAI_ACCOUNT,  
                     deployment_name="text-embedding-ada-002",
                     model_name="text-embedding-ada-002"
                 ),
             ),  
         ], 
     )  
    
     # 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). Per coordinare l'indicizzazione dei due indici contemporaneamente sono necessarie le proiezioni dell'indice. Le query eseguite sull'indice figlio. La logica di query include una query di ricerca, che usa parent_idto per 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