Udostępnij za pośrednictwem


Samouczek: projektowanie indeksu rag w usłudze Azure AI Search

Indeks zawiera zawartość tekstową i wektorową z możliwością wyszukiwania oraz konfiguracje. We wzorcu RAG, który używa modelu czatu na potrzeby odpowiedzi, chcesz, aby indeks został zaprojektowany wokół fragmentów zawartości, które można przekazać do modułu LLM w czasie wykonywania zapytania.

W tym samouczku zostały wykonane następujące czynności:

  • Poznaj charakterystykę schematu indeksu utworzonego dla programu RAG
  • Tworzenie indeksu, który uwzględnia wektor i zapytania hybrydowe
  • Dodawanie profilów i konfiguracji wektorów
  • Dodawanie danych ustrukturyzowanych
  • Dodawanie filtrowania

Wymagania wstępne

Program Visual Studio Code z rozszerzeniem języka Python i pakietem Jupyter. Aby uzyskać więcej informacji, zobacz Python in Visual Studio Code (Język Python w programie Visual Studio Code).

Dane wyjściowe tego ćwiczenia to definicja indeksu w formacie JSON. Na tym etapie nie jest on przekazywany do usługi Azure AI Search, dlatego w tym ćwiczeniu nie ma żadnych wymagań dotyczących usług w chmurze ani uprawnień.

Zapoznaj się z zagadnieniami dotyczącymi schematu dla programu RAG

W wyszukiwaniu konwersacyjnym usługa LLMs tworzy odpowiedź, którą widzi użytkownik, a nie wyszukiwarkę, więc nie musisz myśleć o polach, które mają być wyświetlane w wynikach wyszukiwania i czy reprezentacje poszczególnych dokumentów wyszukiwania są spójne dla użytkownika. W zależności od pytania usługa LLM może zwrócić dosłowną zawartość z indeksu lub prawdopodobnie ponownie spakować zawartość, aby uzyskać lepszą odpowiedź.

Zorganizowane wokół fragmentów

Gdy moduły LLM generują odpowiedź, działają na fragmentach zawartości dla danych wejściowych komunikatów i podczas gdy muszą wiedzieć, skąd pochodzi fragment do celów cytatu, co ma największe znaczenie, to jakość danych wejściowych komunikatów i jego znaczenie dla pytania użytkownika. Niezależnie od tego, czy fragmenty pochodzą z jednego dokumentu, czy tysięcy, LLM pozyskuje informacje lub dane uziemienia i formułuje odpowiedź przy użyciu instrukcji podanych w wierszu polecenia systemu.

Fragmenty są fokusem schematu, a każdy fragment jest elementem definiującym dokument wyszukiwania we wzorcu RAG. Indeks można traktować jako dużą kolekcję fragmentów, w przeciwieństwie do tradycyjnych dokumentów wyszukiwania, które prawdopodobnie mają większą strukturę, na przykład pola zawierające jednolitą zawartość dla nazwy, opisów, kategorii i adresów.

Ulepszone przy użyciu wygenerowanych danych

W tym samouczku przykładowe dane składają się z plików PDF i zawartości z książki NASA Earth Book. Ta zawartość jest opisowa i informacyjna, z wieloma odwołaniami do lokalizacji geograficznych, krajów i obszarów na całym świecie. Cała zawartość tekstowa jest przechwytywana we fragmentach, ale cykliczne wystąpienia nazw miejsc tworzą możliwość dodawania struktury do indeksu. Korzystając z umiejętności, można rozpoznawać jednostki w tekście i przechwytywać je w indeksie do użycia w zapytaniach i filtrach. W tym samouczku uwzględniliśmy umiejętność rozpoznawania jednostek, która rozpoznaje i wyodrębnia jednostki lokalizacji, ładuje je do pola z możliwością wyszukiwania i filtrowania locations . Dodanie zawartości ustrukturyzowanej do indeksu zapewnia więcej opcji filtrowania, poprawy istotności i bardziej ukierunkowanych odpowiedzi.

Pola nadrzędno-podrzędne w jednym lub dwóch indeksach?

Fragmentowana zawartość zwykle pochodzi z większego dokumentu. Mimo że schemat jest zorganizowany wokół fragmentów, chcesz również przechwytywać właściwości i zawartość na poziomie nadrzędnym. Przykłady tych właściwości mogą obejmować nadrzędną ścieżkę pliku, tytuł, autorów, datę publikacji lub podsumowanie.

Punktem przegięcia w projekcie schematu jest to, czy mają dwa indeksy dla zawartości nadrzędnej i podrzędnej/fragmentowanej, czy pojedynczy indeks, który powtarza elementy nadrzędne dla każdego fragmentu.

W tym samouczku, ponieważ wszystkie fragmenty tekstu pochodzą z pojedynczego elementu nadrzędnego (KSIĄŻKA NASA Earth Book), nie potrzebujesz oddzielnego indeksu dedykowanego do poziomu pól nadrzędnych. Jeśli jednak indeksujesz z wielu nadrzędnych plików PDF, możesz chcieć, aby para indeksów nadrzędny-podrzędny przechwyciła pola specyficzne dla poziomu, a następnie wysyłała zapytania odnośników do indeksu nadrzędnego, aby pobrać te pola istotne dla każdego fragmentu.

Lista kontrolna zagadnień dotyczących schematu

W usłudze Azure AI Search indeks, który najlepiej sprawdza się w przypadku obciążeń RAG, ma następujące cechy:

  • Zwraca fragmenty istotne dla zapytania i czytelne dla usługi LLM. Moduły LLM mogą obsługiwać określony poziom zanieczyszczonych danych we fragmentach, takich jak oznaczanie, nadmiarowość i niekompletne ciągi. Chociaż fragmenty muszą być czytelne i istotne dla pytania, nie muszą być nieskazitelne.

  • Utrzymuje relację nadrzędny-podrzędny między fragmentami dokumentu a właściwościami dokumentu nadrzędnego, takimi jak nazwa pliku, typ pliku, tytuł, autor itd. Aby odpowiedzieć na zapytanie, fragmenty można ściągnąć z dowolnego miejsca w indeksie. Skojarzenie z dokumentem nadrzędnym dostarczającym fragment jest przydatne w kontekście, cytatach i zapytaniach.

  • Uwzględnia zapytania, które chcesz utworzyć. Należy mieć pola dla zawartości wektorowej i hybrydowej, a pola te powinny być przypisywane do obsługi określonych zachowań zapytań, takich jak wyszukiwanie lub filtrowanie. Jednocześnie można wykonywać zapytania dotyczące tylko jednego indeksu (bez sprzężeń), aby kolekcja pól mogła definiować całą zawartość z możliwością wyszukiwania.

  • Schemat powinien być płaski (bez złożonych typów lub struktur) lub należy sformatować dane wyjściowe typu złożonego jako dane JSON przed wysłaniem go do usługi LLM. To wymaganie jest specyficzne dla wzorca RAG w usłudze Azure AI Search.

Uwaga

Projekt schematu wpływa na magazyn i koszty. To ćwiczenie koncentruje się na podstawach schematu. W samouczku Minimalizuj magazyn i koszty zapoznasz się ze schematami, aby dowiedzieć się, jak wąskie typy danych, kompresja i opcje magazynu znacznie zmniejszają ilość miejsca używanego przez wektory.

Tworzenie indeksu dla obciążeń RAG

Minimalny indeks dla usługi LLM jest przeznaczony do przechowywania fragmentów zawartości. Zazwyczaj zawiera pola wektorów, jeśli chcesz wyszukać wysoce istotne wyniki w poszukiwaniu podobieństwa. Zawiera również pola niewektorowe dla danych wejściowych czytelnych dla człowieka w usłudze LLM na potrzeby wyszukiwania konwersacyjnego. Zawartość fragmentowana w wynikach wyszukiwania staje się danymi uziemienia wysyłanymi do modułu LLM.

  1. Otwórz program Visual Studio Code i utwórz nowy plik. W tym ćwiczeniu nie musi być to typ pliku w języku Python.

  2. Oto minimalna definicja indeksu dla rozwiązań RAG, które obsługują wektor i wyszukiwanie hybrydowe. Zapoznaj się z nim, aby zapoznać się z wprowadzeniem do wymaganych elementów: nazwa indeksu, pola i sekcja konfiguracji dla pól wektorów.

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

    Pola muszą zawierać pole klucza ("id" w tym przykładzie) i powinny zawierać fragmenty wektorów do wyszukiwania podobieństwa oraz fragmenty niewektorów dla danych wejściowych do modułu LLM.

    Pola wektorowe są skojarzone z algorytmami określającymi ścieżki wyszukiwania w czasie wykonywania zapytania. Indeks zawiera sekcję vectorSearch określającą wiele konfiguracji algorytmów. Pola wektorowe mają również określone typy i dodatkowe atrybuty do osadzania wymiarów modelu. Edm.Single to typ danych, który działa w przypadku powszechnie używanych maszyn LLM. Aby uzyskać więcej informacji na temat pól wektorów, zobacz Tworzenie indeksu wektorów.

    Pola metadanych mogą być ścieżką pliku nadrzędnego, datą utworzenia lub typem zawartości i są przydatne w przypadku filtrów.

  3. Oto schemat indeksu dla kodu źródłowego samouczka i zawartości Książki Ziemi.

    Podobnie jak w przypadku podstawowego schematu, jest on zorganizowany wokół fragmentów. Element chunk_id jednoznacznie identyfikuje każdy fragment. Pole text_vector jest osadzaniem fragmentu. Pole niewektorowe chunk jest ciągiem czytelnym. Mapuje title na unikatową ścieżkę magazynu metadanych dla obiektów blob. Jest parent_id to jedyne pole na poziomie nadrzędnym i jest to zakodowana w formacie base64 wersja identyfikatora URI pliku nadrzędnego.

    Schemat zawiera locations również pole do przechowywania wygenerowanej zawartości utworzonej przez potok indeksowania.

     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. W przypadku schematu indeksu, który ściślej naśladuje zawartość ustrukturyzowaną, należy mieć oddzielne indeksy dla pól nadrzędnych i podrzędnych (fragmentowanych). Aby jednocześnie koordynować indeksowanie dwóch indeksów, potrzebne są projekcje indeksów. Zapytania są wykonywane względem indeksu podrzędnego. Logika zapytań zawiera zapytanie odnośnika przy użyciu parent_idt pobierania zawartości z indeksu nadrzędnego.

    Pola w indeksie podrzędnym:

    • ID
    • chunk
    • fragmentVectcor
    • parent_id

    Pola w indeksie nadrzędnym (wszystko, co ma być "jednym z"):

    • parent_id
    • pola na poziomie nadrzędnym (nazwa, tytuł, kategoria)

Następny krok