Partilhar via


Tutorial: Criar um índice para RAG na Pesquisa de IA do Azure

Um índice contém texto pesquisável e conteúdo vetorial, além de configurações. Em um padrão RAG que usa um modelo de bate-papo para respostas, você deseja um índice projetado em torno de partes de conteúdo que podem ser passadas para um LLM no momento da consulta.

Neste tutorial:

  • Conheça as características de um esquema de índice criado para RAG
  • Criar um índice que acomoda consultas vetoriais e híbridas
  • Adicionar perfis vetoriais e configurações
  • Adicionar dados estruturados
  • Adicionar filtragem

Pré-requisitos

Visual Studio Code com a extensão Python e o pacote Jupyter. Para obter mais informações, consulte Python no Visual Studio Code.

O resultado deste exercício é uma definição de índice em JSON. Neste momento, ele não é carregado para o Azure AI Search, portanto, não há requisitos para serviços de nuvem ou permissões neste exercício.

Revisar considerações de esquema para RAG

Na pesquisa conversacional, os LLMs compõem a resposta que o usuário vê, não o mecanismo de pesquisa, então você não precisa pensar em quais campos mostrar nos resultados da pesquisa e se as representações de documentos de pesquisa individuais são coerentes para o usuário. Dependendo da pergunta, o LLM pode retornar o conteúdo literal do seu índice ou, mais provavelmente, reempacotar o conteúdo para obter uma resposta melhor.

Organizado em torno de pedaços

Quando os LLMs geram uma resposta, eles operam em partes de conteúdo para entradas de mensagens e, embora precisem saber de onde o bloco veio para fins de citação, o que mais importa é a qualidade das entradas de mensagem e sua relevância para a pergunta do usuário. Quer as partes venham de um documento ou mil, o LLM ingere as informações ou os dados de aterramento e formula a resposta usando instruções fornecidas em um prompt do sistema.

Chunks são o foco do esquema, e cada parte é o elemento definidor de um documento de pesquisa em um padrão RAG. Você pode pensar em seu índice como uma grande coleção de partes, em oposição aos documentos de pesquisa tradicionais que provavelmente têm mais estrutura, como campos contendo conteúdo uniforme para um nome, descrições, categorias e endereços.

Melhorado com dados gerados

Neste tutorial, os dados de exemplo consistem em PDFs e conteúdo do Livro da Terra da NASA. Este conteúdo é descritivo e informativo, com inúmeras referências a geografias, países e áreas em todo o mundo. Todo o conteúdo textual é capturado em partes, mas instâncias recorrentes de nomes de lugares criam uma oportunidade para adicionar estrutura ao índice. Usando habilidades, é possível reconhecer entidades no texto e capturá-las em um índice para uso em consultas e filtros. Neste tutorial, incluímos uma habilidade de reconhecimento de entidade que reconhece e extrai entidades de localização, carregando-as em um campo pesquisável e filtrável locations . Adicionar conteúdo estruturado ao seu índice oferece mais opções de filtragem, relevância aprimorada e respostas mais focadas.

Campos pai-filho em um ou dois índices?

O conteúdo fragmentado normalmente deriva de um documento maior. E, embora o esquema esteja organizado em blocos, você também deseja capturar propriedades e conteúdo no nível pai. Exemplos dessas propriedades podem incluir o caminho do arquivo pai, título, autores, data de publicação ou um resumo.

Um ponto de inflexão no design do esquema é ter dois índices para conteúdo pai e filho/fragmentado ou um único índice que repete elementos pai para cada bloco.

Neste tutorial, como todos os pedaços de texto são originários de um único pai (NASA Earth Book), você não precisa de um índice separado dedicado a subir o nível dos campos pai. No entanto, se você estiver indexando a partir de vários PDFs pai, convém que um par de índice pai-filho capture campos específicos de nível e, em seguida, envie consultas de pesquisa para o índice pai para recuperar os campos relevantes para cada bloco.

Lista de verificação de considerações de esquema

No Azure AI Search, um índice que funciona melhor para cargas de trabalho RAG tem estas qualidades:

  • Retorna partes que são relevantes para a consulta e legíveis para o LLM. Os LLMs podem lidar com um certo nível de dados sujos em partes, como marcação, redundância e cadeias de caracteres incompletas. Embora os pedaços precisem ser legíveis e relevantes para a pergunta, eles não precisam ser imaculados.

  • Mantém uma relação pai-filho entre partes de um documento e as propriedades do documento pai, como o nome do arquivo, tipo de arquivo, título, autor e assim por diante. Para responder a uma consulta, partes podem ser extraídas de qualquer lugar do índice. A associação com o documento pai que fornece o bloco é útil para contextualização, citações e consultas de acompanhamento.

  • Acomoda as consultas que você deseja criar. Você deve ter campos para conteúdo vetorial e híbrido, e esses campos devem ser atribuídos para dar suporte a comportamentos de consulta específicos, como pesquisável ou filtrável. Você só pode consultar um índice de cada vez (sem junções), portanto, sua coleção de campos deve definir todo o seu conteúdo pesquisável.

  • Seu esquema deve ser plano (sem tipos ou estruturas complexas) ou você deve formatar a saída de tipo complexa como JSON antes de enviá-la para o LLM. Este requisito é específico para o padrão RAG no Azure AI Search.

Nota

O design do esquema afeta o armazenamento e os custos. Este exercício é focado nos fundamentos do esquema. No tutorial Minimizar armazenamento e custos, você revisita esquemas para saber como tipos de dados estreitos, compactação e opções de armazenamento reduzem significativamente a quantidade de armazenamento usada por vetores.

Criar um índice para cargas de trabalho RAG

Um índice mínimo para LLM foi projetado para armazenar partes de conteúdo. Normalmente, inclui campos vetoriais se você quiser pesquisa de semelhança para resultados altamente relevantes. Ele também inclui campos não vetoriais para entradas legíveis por humanos para o LLM para pesquisa conversacional. O conteúdo fragmentado não vetorial nos resultados da pesquisa torna-se os dados de aterramento enviados para o LLM.

  1. Abra o Visual Studio Code e crie um novo arquivo. Não precisa ser um tipo de arquivo Python para este exercício.

  2. Aqui está uma definição de índice mínima para soluções RAG que suportam pesquisa vetorial e híbrida. Revise-o para obter uma introdução aos elementos necessários: nome do índice, campos e uma seção de configuração para campos vetoriais.

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

    Os campos devem incluir o campo chave ("id" neste exemplo) e devem incluir blocos vetoriais para pesquisa de semelhança e blocos não vetoriais para entradas para o LLM.

    Os campos vetoriais são associados a algoritmos que determinam os caminhos de pesquisa no momento da consulta. O índice tem uma seção vectorSearch para especificar várias configurações de algoritmo. Os campos vetoriais também têm tipos específicos e atributos extras para incorporar dimensões do modelo. Edm.Single é um tipo de dados que funciona para LLMs comumente usados. Para obter mais informações sobre campos vetoriais, consulte Criar um índice vetorial.

    Os campos de metadados podem ser o caminho do arquivo pai, a data de criação ou o tipo de conteúdo e são úteis para filtros.

  3. Aqui está o esquema de índice para o código-fonte do tutorial e o conteúdo do Livro do Earth.

    Como o esquema básico, ele é organizado em blocos. O chunk_id identifica exclusivamente cada pedaço. O text_vector campo é uma incorporação do pedaço. O campo não vetorial chunk é uma cadeia de caracteres legível. O title mapeia para um caminho de armazenamento de metadados exclusivo para os blobs. O parent_id é o único campo de nível pai e é uma versão codificada em base64 do URI do arquivo pai.

    Em cargas de trabalho de vetorização integradas, como a usada nesta série de tutoriais, a dimensions propriedade em seus campos vetoriais deve ser idêntica ao número de gerados pela habilidade de dimensions incorporação usada para vetorizar seus dados. Nesta série, usamos a habilidade de incorporação do Azure OpenAI, que chama o modelo de incorporação de texto 3-grande no Azure OpenAI. A habilidade é especificada no próximo tutorial. Definimos dimensões para 1024 tanto no campo vetorial quanto na definição de habilidade.

    O esquema também inclui um locations campo para armazenar o conteúdo gerado que é criado pelo pipeline de indexação.

     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. Para um esquema de índice que imita mais de perto o conteúdo estruturado, você teria índices separados para campos pai e filho (em partes). Você precisaria de projeções de índice para coordenar a indexação dos dois índices simultaneamente. As consultas são executadas em relação ao índice filho. A lógica de consulta inclui uma consulta de pesquisa, usando o parent_idt recuperar conteúdo do índice pai.

    Campos no índice filho:

    • ID
    • chunk
    • chunkVectcor
    • parent_id

    Campos no índice pai (tudo o que você deseja "um de"):

    • parent_id
    • campos de nível pai (nome, título, categoria)

Próximo passo