Tutorial: Criar um índice para o RAG na Pesquisa de IA do Azure
Um índice contém conteúdo de vetor e texto pesquisável, além de configurações. Em um padrão RAG que usa um modelo de chat para respostas, você deseja um índice que contenha partes de conteúdo que possam ser passadas para uma LLM no momento da consulta.
Neste tutorial, você:
- Conhecer as características de um esquema de índice criado para RAG
- Criar um índice que acomode vetores e consultas híbridas
- Adicionar perfis e configurações de vetor
- Adicionar dados estruturados
- Adicionar filtragem
Pré-requisitos
Visual Studio Code com a extensão do Python e o pacote do Jupyter. Para mais informações, Python no Visual Studio Code.
A saída deste exercício é uma definição de índice no JSON. Neste ponto, ele não é carregado na Pesquisa de IA do Azure, portanto, não há requisitos para serviços de nuvem ou permissões neste exercício.
Examinar as considerações de esquema para o RAG
Na pesquisa de conversação, as LLMs compõem a resposta que o usuário vê, não o mecanismo de pesquisa, portanto, 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 com o usuário. Dependendo da pergunta, a LLM pode retornar conteúdo verbatim do índice ou, provavelmente, recriar pacote do conteúdo para obter uma resposta melhor.
Organizados em partes
Quando as LLMs geram uma resposta, elas operam em partes de conteúdo para entradas de mensagens e, embora precisem saber de onde a parte veio para fins de citação, o que mais importa é a qualidade das entradas de mensagens e sua relevância para a pergunta do usuário. Se as partes são provenientes de um documento ou de mil, a LLM ingere as informações ou dados de aterramentoe formula a resposta usando instruções fornecidas em um prompt do sistema.
Partes 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, ao contrário dos documentos de pesquisa tradicionais que provavelmente têm mais estrutura, como campos que contêm conteúdo uniforme para um nome, descrições, categorias e endereços.
Avançado com dados gerados
Neste tutorial, os dados de exemplo consistem em PDFs e conteúdos do Livro da Terra da NASA. Esse 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 blocos, 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 entidades que reconhece e extrai entidades de localização, carregando-as em um campo locations
pesquisável e filtrável. Adicionar conteúdo estruturado ao seu índice oferece mais opções de filtragem, melhora a relevância e proporciona respostas mais focadas.
Campos pai-filho em um ou dois índices?
Normalmente, o conteúdo em partes deriva de um documento maior. E embora o esquema seja organizado em partes, você também deseja capturar propriedades e conteúdo no nível pai. Exemplos dessas propriedades podem incluir o caminho do arquivo pai, o título, os autores, a data da publicação ou um resumo.
Um ponto de inflexão no design do esquema é ter dois índices para conteúdo pai e filho/em partes ou um único índice que repita os elementos pai para cada parte.
Neste tutorial, como todas as partes do texto se originam de um único pai (livro terrestre da NASA), você não precisa de um índice separado dedicado ao nível superior dos campos pai. No entanto, se você estiver indexando de vários PDFs pai, talvez queira que um par de índice pai-filho capture campos específicos ao nível e envie consultas de pesquisa para o índice pai para recuperar esses campos relevantes para cada parte.
Lista de verificação de considerações de esquema
Na Pesquisa de IA do Azure, um índice que funciona melhor para cargas de trabalho RAG tem estas qualidades:
Retorna partes relevantes para a consulta e legíveis para a LLM. As LLMs podem lidar com um determinado nível de dados sujos em partes, como marcação, redundância e cadeias de caracteres incompletas. Embora as partes precisem ser legíveis e relevantes para a questão, elas não precisam ser primitivas.
Mantém uma relação pai-filho entre partes de um documento e as propriedades do documento pai, como o nome do arquivo, o tipo de arquivo, o título, o autor e assim por diante. Para responder a uma consulta, as partes podem ser extraídas de qualquer lugar no índice. A associação com o documento pai que fornece a parte é útil para contexto, 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 por vez (sem junções) para que sua coleção de campos defina todo o conteúdo pesquisável.
Seu esquema deve ser simples (sem tipos ou estruturas complexas), ou você deve formatar a saída do tipo complexo como JSON antes de enviá-la para o LLM. Esse requisito é específico para o padrão RAG na Pesquisa de IA do Azure.
Criar um índice para cargas de trabalho RAG
Um índice mínimo para LLM foi projetado para armazenar partes de conteúdo. Normalmente, ele inclui campos de vetor se você quiser a pesquisa de similaridade para resultados altamente relevantes. Ele também inclui campos não vetoriais para entradas legíveis por humanos no LLM para pesquisa de conversação. O conteúdo em partes não vetorial nos resultados da pesquisa torna-se os dados de aterramento enviados para a LLM.
Abra o Visual Studio Code e crie um novo arquivo. Ele não precisa ser um tipo de arquivo Python para este exercício.
Aqui está uma definição mínima de índice para soluções RAG que dão suporte à busca híbrida e em vetores. Examine-o para obter uma introdução aos elementos necessários: nome do índice, campos e uma seção de configuração para campos de vetor.
{ "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 principal (
"id"
neste exemplo) e devem incluir blocos vetoriais para pesquisa de similaridade e partes não vetoriais para entradas no LLM.Campos vetoriais estão associados a algoritmos que determinam os caminhos de busca no momento da consulta. O índice possui uma seção vectorSearch para especificar várias configurações de algoritmos. Campos vetoriais também têm tipos específicos e atributos extras para inserir as dimensões do modelo.
Edm.Single
é um tipo de dados que funciona para LLMs comumente usadas. Para obter mais informações sobre campos de vetor, consulte Criar um índice de vetor.Campos de metadados podem ser o caminho do arquivo pai, data de criação ou tipo de conteúdo, e são úteis para filtros.
Aqui está o esquema de índice para o código-fonte do tutorial e o conteúdo do livro terrestre.
Como o esquema básico, ele é organizado em partes.
chunk_id
identifica cada parte com exclusividade. O campotext_vector
é uma inserção da parte. O campochunk
não vetorial é uma cadeia de caracteres legível.title
é mapeado para um caminho de armazenamento de metadados exclusivo para os blobs.parent_id
é o único campo de nível pai e é uma versão codificada em base64 do URI do arquivo pai.O esquema também inclui um campo
locations
para armazenar conteúdo gerado 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")
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 no índice filho. A lógica de consulta inclui uma consulta de pesquisa, usando o parent_idt para recuperar conteúdos do índice pai.
Campos no índice filho:
- ID
- chunk
- chunkVectcor
- parent_id
Campos no índice pai (tudo o que você deseja "um"):
- parent_id
- campos de nível pai (nome, título, categoria)