Compartilhar via


Definir uma projeção de índice para indexação pai-filho

Para índices que contêm documentos em partes, uma projeção de índice especifica como o conteúdo pai-filho é mapeado para campos em um índice de pesquisa para indexação um para muitos. Por meio de uma projeção de índice, você pode enviar conteúdo para:

  • Um único índice, em que os campos pai se repetem para cada parte, mas o grão do índice está no nível da parte. O tutorial do RAG é um exemplo dessa abordagem.

  • Dois ou mais índices, em que o índice pai tem campos relacionados ao documento pai e o índice filho é organizado em torno de partes. O índice filho é o corpus de pesquisa primário, mas o índice pai pode ser usado para consulta de pesquisa quando você deseja recuperar os campos pai de uma determinada parte ou para consultas independentes.

A maioria das implementações é um único índice organizado em torno de partes com campos pai, como o nome do arquivo do documento, repetindo para cada parte. No entanto, o sistema foi projetado para dar suporte a vários índices filho e separados, se esse for o seu requisito. A Pesquisa de IA do Azure não dá suporte a junções de índice, portanto, o código do aplicativo deve lidar com qual índice usar.

Uma projeção de índice é definida em um conjunto de habilidades. Ele é responsável por coordenar o processo de indexação que envia partes de conteúdo para um índice de pesquisa, juntamente com o conteúdo pai associado a cada parte. Ele melhora o funcionamento do agrupamento de dados nativos, dando mais opções para controlar como o conteúdo pai-filho é indexado.

Este artigo explica como criar o esquema de índice e os padrões de projeção do indexador para indexação um para muitos.

Pré-requisitos

O conjunto de habilidades contém a projeção do indexador que molda os dados para indexação um para muitos. Um conjunto de habilidades também pode ter outras habilidades, como uma habilidade de inserção, como AzureOpenAIEmbedding, se o cenário incluir vetorização integrada.

Dependência do processamento do indexador

A indexação um-para-muitos usa uma dependência de conjuntos de habilidades e indexação baseada em indexador que inclui os quatro componentes a seguir:

  • Uma fonte de dados
  • Um ou mais índices para seu conteúdo pesquisável
  • Um conjunto de habilidades que contém uma projeção de índice*
  • Um indexador

Seus dados podem se originar de qualquer fonte de dados com suporte, mas a suposição é que o conteúdo é grande o suficiente para que você queira particioná-lo e o motivo para particioná-lo é que você está implementando um padrão RAG que fornece dados de aterramento para um modelo de chat. Ou, você está implementando a busca em vetores e precisa atender aos requisito de tamanho de entrada menores dos modelos de inserção.

Os indexadores carregam dados indexados em um índice predefinido. Como você define o esquema e se usar um ou mais índices é a primeira decisão a tomar em um cenário de indexação um para muitos. A próxima seção aborda o design do índice.

Criar um índice para indexação um para muitos

Se você criar um índice para partes que repetem valores pai ou índices separados para posicionamento de campo pai-filho, o índice primário usado para pesquisa é projetado em torno de partes de dados. Ele deve ter os seguintes campos:

  • Um campo de chave de documento que identifica exclusivamente cada documento. Ele deve ser definido como tipo Edm.String com o analisador keyword.

  • Um campo que associa cada parte com seu pai. Ela deve ser do tipo Edm.String. Ele não pode ser o campo de chave do documento e deve ter filterable definido como true. Ele é conhecido como parent_id nos exemplos e como um valor de chave projetado neste artigo.

  • Outros campos para conteúdo, como texto ou campos de partes vetorizados.

Um índice deve existir no serviço de pesquisa antes de criar o conjunto de habilidades ou executar o indexador.

Esquema de índice único, incluindo campos pai e filho

Um único índice projetado em torno de partes com conteúdo pai repetido para cada parte é o padrão predominante para cenários de busca em vetores e RAG. A capacidade de associar o conteúdo pai correto a cada parte é habilitada por meio de projeções de índice.

O esquema a seguir é um exemplo que atende aos requisitos para projeções de índice. Neste exemplo, os campos pai são o parent_id e o título. Campos filho são as partes vetoriais e não vetoriais. O chunk_id é a ID do documento desse índice. O parent_id e o título se repetem para cada parte do índice.

Você pode usar o portal do Azure, as APIs REST ou um SDK do Azure para criar um índice.

{
    "name": "my_consolidated_index",
    "fields": [
        {"name": "chunk_id", "type": "Edm.String", "key": true, "filterable": true, "analyzer": "keyword"},
        {"name": "parent_id", "type": "Edm.String", "filterable": true},
        {"name": "title", "type": "Edm.String", "searchable": true, "filterable": true, "sortable": true, "retrievable": true},
        {"name": "chunk", "type": "Edm.String","searchable": true,"retrievable": true},
        {"name": "chunk_vector", "type": "Collection(Edm.Single)", "searchable": true, "retrievable": false, "stored": false, "dimensions": 1536, "vectorSearchProfile": "hnsw"}
    ],
    "vectorSearch": {
        "algorithms": [{"name": "hsnw", "kind": "hnsw", "hnswParameters": {}}],
        "profiles": [{"name": "hsnw", "algorithm": "hnsw"}]
    }
}

Adicionar projeções de índice a um conjunto de habilidades

As projeções de índice são definidas dentro de uma definição de conjunto de habilidades e principalmente como uma matriz de selectors, em que cada seletor corresponde a um índice de destino diferente no serviço de pesquisa. Esta seção começa com sintaxe e exemplos de contexto, seguidos de referência de parâmetro.

Escolha uma guia para a sintaxe de várias API. No momento, não há suporte do portal para configurar projeções, além de editar a definição JSON do conjunto de habilidades. Consulte o exemplo REST para JSON.

As projeções do índice estão geralmente disponíveis. Recomendamos a API estável mais recente:

Aqui está um exemplo de conteúdo para uma definição de projeções de índice que você pode usar para projetar a saída de páginas individuais pela habilidade de Divisão de Texto como seus próprios documentos no índice de pesquisa.

"indexProjections": {
    "selectors": [
        {
            "targetIndexName": "my_consolidated_index",
            "parentKeyFieldName": "parent_id",
            "sourceContext": "/document/pages/*",
            "mappings": [
                {
                    "name": "chunk",
                    "source": "/document/pages/*",
                    "sourceContext": null,
                    "inputs": []
                },
                {
                    "name": "chunk_vector",
                    "source": "/document/pages/*/chunk_vector",
                    "sourceContext": null,
                    "inputs": []
                },
                {
                    "name": "title",
                    "source": "/document/title",
                    "sourceContext": null,
                    "inputs": []
                }
            ]
        }
    ],
    "parameters": {
        "projectionMode": "skipIndexingParentDocuments"
    }
}

Referência de parâmetro

Parâmetros de projeção de índice Definição
selectors Parâmetros para o corpus de pesquisa principal, geralmente aquele projetado em torno de partes.
projectionMode Um parâmetro opcional que fornece instruções ao indexador. O único valor válido para esse parâmetro é skipIndexingParentDocuments, e ele é usado quando o índice de partes é o corpus de pesquisa primário e você precisa especificar se os campos pai são indexados como documentos de pesquisa extra dentro do índice em partes. Se você não definir skipIndexingParentDocuments, obterá documentos de pesquisa extras em seu índice que são nulos para partes, mas preenchidos somente com campos pai. Por exemplo, se cinco documentos contribuem com 100 partes para o índice, o número de documentos no índice será 105. Os cinco documentos criados ou os campos pai têm nulos para campos de parte (filho), tornando-os substancialmente diferentes da maior parte dos documentos no índice. É recomendável definir projectionMode como skipIndexingParentDocument.

Os seletores têm os seguintes parâmetros como parte de sua definição.

Parâmetros do seletor Definição
targetIndexName O nome do índice no qual os dados de índice são projetados. É o índice em partes simples com campos pai repetidos ou é o índice filho se você estiver usando índices separados para conteúdo pai-filho.
parentKeyFieldName O nome do campo que fornece a chave para o documento pai.
sourceContext A anotação de enriquecimento que define em qual granularidade mapear os dados nos documentos de pesquisa individuais. Para obter mais informações, consulte o Contexto de habilidade e linguagem de anotação de entrada.
mappings Uma matriz de mapeamentos de dados enriquecidos para campos no índice de pesquisa. Cada mapeamento consiste em:
name: O nome do campo no índice de pesquisa no qual os dados devem ser indexados.
source: o caminho de anotação de enriquecimento do qual os dados devem ser extraídos.

Cada mapping também pode definir dados recursivamente com um campo sourceContext e inputs opcional, semelhante ao repositório de conhecimento ou à Habilidade do Shaper. Dependendo do aplicativo, esses parâmetros permitem que você modele dados em campos de tipo Edm.ComplexType no índice de pesquisa. Alguns LLMs não aceitam um tipo complexo nos resultados da pesquisa, portanto, o LLM que você está usando determina se um mapeamento de tipo complexo é útil ou não.

O parâmetro mappings é importante. Você deve mapear explicitamente cada campo no índice filho, exceto para os campos de ID, como chave de documento e A ID pai.

Esse requisito contrasta com outras convenções de mapeamento de campo na Pesquisa de IA do Azure. Para alguns tipos de fonte de dados, o indexador pode mapear implicitamente campos com base em nomes semelhantes ou características conhecidas (por exemplo, indexadores de blob usam o caminho de armazenamento de metadados exclusivo como a chave de documento padrão). No entanto, para projeções de indexador, você deve especificar explicitamente cada mapeamento de campo no lado "muitos" da relação.

Não crie um mapeamento de campo para o campo da chave pai. Fazer isso interrompe o rastreamento de alterações e a atualização de dados sincronizados.

Tratando documentos pai

Agora que você viu vários padrões para indexações um para muitos, vamos comparar as principais diferenças entre cada opção. As projeções de índice geram efetivamente documentos "filho" para cada documento "pai" executado por meio de um conjunto de habilidades. Você tem várias opções para lidar com os documentos "pai".

  • Para enviar documentos pai e filho para índices separados, configure targetIndexName para a definição do indexador como o índice pai e defina targetIndexName no seletor de projeção do índice como o índice filho.

  • Para manter documentos pai e filho no mesmo índice, defina o indexador targetIndexName e a projeção de índice targetIndexName como o mesmo índice.

  • Para evitar a criação de documentos de pesquisa pai e garantir que o índice contenha apenas documentos filho de um grão uniforme, configure targetIndexName para a definição do indexador e o seletor para o mesmo índice, mas adicione um objeto extra parameters depois selectors, com uma chave projectionMode definida como skipIndexingParentDocuments, conforme mostrado aqui:

    "indexProjections": {
        "selectors": [
            ...
        ],
        "parameters": {
            "projectionMode": "skipIndexingParentDocuments"
        }
    }
    

Examinar mapeamentos de campo

Os indexadores são afiliados a três tipos diferentes de mapeamentos de campo. Antes de executar o indexador, verifique os mapeamentos de campo e saiba quando usar cada tipo.

Os mapeamentos de campo são definidos em um indexador e usados para mapear um campo de origem para um campo de índice. Os mapeamentos de campo são usados para caminhos de dados que levantam dados da origem e os transmitem para indexação, sem nenhuma etapa de processamento de habilidades intermediárias. Normalmente, um indexador pode mapear automaticamente campos com o mesmo nome e tipo. Mapeamentos de campo explícitos só são necessários quando há discrepâncias. Na indexação um para muitos e nos padrões discutidos até agora, talvez você não precise de mapeamentos de campo.

Os mapeamentos de campo de saída são definidos em um indexador e usados para mapear o conteúdo enriquecido gerado por um conjunto de habilidades para um campo no índice principal. Nos padrões um-para-muitos abordados neste artigo, este é o índice pai em uma solução de dois índices. Nos exemplos mostrados neste artigo, o índice pai é esparso, com apenas um campo de título, e esse campo não é preenchido com conteúdo do processamento do conjunto de habilidades, portanto, não fazemos um mapeamento de campo de saída.

Os mapeamentos de campo de projeção do indexador são usados para mapear o conteúdo gerado pelo conjunto de habilidades para campos no índice filho. Nos casos em que o índice filho também inclui campos pai (como na solução de índice consolidado), você deve configurar mapeamentos de campo para cada campo que tenha conteúdo, incluindo o campo de título de nível pai, supondo que você queira que o título apareça em cada documento em partes. Se você estiver usando índices pai e filho separados, as projeções do indexador deverão ter mapeamentos de campo apenas para os campos de nível filho.

Observação

Mapeamentos de campo de saída e mapeamentos de campo de projeção do indexador aceitam nós de árvore de documento enriquecidos como entradas de origem. Saber como especificar um caminho para cada nó é essencial para configurar o caminho de dados. Para saber mais sobre a sintaxe do caminho, consulte Referenciar um caminho para nós enriquecidos e definição de conjunto de habilidades para obter exemplos.

Executar o indexador

Depois de criar uma fonte de dados, índices e conjunto de habilidades, você estará pronto para criar e executar o indexador. Esta etapa coloca o pipeline em execução.

Você pode consultar o índice de pesquisa após a conclusão do processamento para testar sua solução.

Ciclo de vida do conteúdo

Dependendo da fonte de dados subjacente, um indexador geralmente pode fornecer controle de alterações e detecção de exclusão contínuas. Esta seção explica o ciclo de vida de conteúdo da indexação um-para-muitos no que diz respeito à atualização de dados.

Para fontes de dados que fornecem controle de alterações e detecção de exclusão, um processo de indexador pode detectar alterações em seus dados de origem. Sempre que executar o indexador e o conjunto de habilidades, as projeções de índice serão atualizadas se o conjunto de habilidades ou os dados de origem subjacentes forem alterados. Todas as alterações selecionadas pelo indexador são propagadas por meio do processo de enriquecimento para as projeções no índice, garantindo que os dados projetados sejam uma representação atual do conteúdo na fonte de dados de origem. A atividade de atualização de dados é capturada em um valor de chave projetado para cada parte. Esse valor é atualizado quando os dados subjacentes são alterados.

Observação

Embora você possa editar manualmente os dados nos documentos projetados usando a API de push de índice, evite fazer isso. As atualizações manuais para um índice são substituídas na próxima invocação de pipeline, supondo que o documento nos dados de origem seja atualizado e a fonte de dados tenha controle de alterações ou detecção de exclusão habilitada.

Conteúdo atualizado

Se você adicionar um novo conteúdo à fonte de dados, novas partes ou documentos filho serão adicionados ao índice na próxima execução do indexador.

Se você modificar o conteúdo existente na fonte de dados, as partes serão atualizadas incrementalmente no índice de pesquisa se a fonte de dados que você está usando oferecer suporte à detecção de controle de alterações e exclusão. Para exame, se uma palavra ou frase for alterada em um documento, a parte no índice de destino que contém essa palavra ou frase será atualizada na próxima execução do indexador. Outros tipos de atualizações, como alterar um tipo de campo e algumas atribuições, não têm suporte para campos existentes. Para obter mais informações sobre atualizações permitidas, consulte Alterar um esquema de índice.

Algumas fontes de dados, como o Armazenamento do Microsoft Azure, dão suporte ao controle de alteração e exclusão por padrão, com base no carimbo de data/hora. Outras fontes de dados, como OneLake, SQL do Azure ou Azure Cosmos DB, devem ser configuradas para controle de alterações.

Conteúdo excluído

Se o conteúdo de origem não existir mais (por exemplo, se o texto for reduzido para ter menos partes), o documento filho correspondente no índice de pesquisa será excluído. Os documentos filho restantes também recebem a chave atualizada para incluir um novo valor de hash, mesmo que o conteúdo não tenha sido alterado de outra forma.

Se um documento pai for completamente excluído da fonte de dados, os documentos filho correspondentes serão excluídos somente se a exclusão for detectada por uma dataDeletionDetectionPolicy definida na definição da fonte de dados. Se você não tiver um dataDeletionDetectionPolicy configurado e precisar excluir um documento pai da fonte de dados, deverá excluir manualmente os documentos filho se eles não forem mais desejados.

Valor de chave projetado

Para garantir a integridade dos dados para conteúdo atualizado e excluído, a atualização de dados na indexação um para muitos depende de um valor de chave projetado no lado "muitos". Se você estiver usando a vetorização integrada ou o assistente de importação e vetorização de dados, o valor de chave projetado será o campo parent_id em um lado em partes ou "muitos" do índice.

Um valor de chave projetado é um identificador exclusivo que o indexador gera para cada documento. Ele garante a exclusividade e permite que o controle de alteração e exclusão funcione corretamente. Essa chave contém os seguintes segmentos:

  • Um hash aleatório para garantir a exclusividade. Esse hash será alterado se o documento pai for atualizado em execuções posteriores do indexador.
  • A chave do documento pai.
  • O caminho de anotação de enriquecimento que identifica o contexto do qual esse documento foi gerado.

Por exemplo, se você dividir um documento pai com o valor de chave "aa1b22c33" em quatro páginas e cada uma dessas páginas for projetada como seu próprio documento por meio de projeções de índice:

  • aa1b22c33
  • aa1b22c33_pages_0
  • aa1b22c33_pages_1
  • aa1b22c33_pages_2

Se o documento pai for atualizado nos dados de origem, talvez resultando em mais páginas em partes, os hash aleatórios mudam, mais páginas são adicionadas e o conteúdo de cada parte é atualizado para corresponder ao que está no documento de origem.

Exemplo de índices pai-filho separados

Esta seção mostra exemplos para índices pai e filho separados. É um padrão incomum, mas é possível que você tenha requisitos de aplicativo que sejam melhor atendidos usando essa abordagem. Nesse cenário, você está projetando conteúdo pai-filho em dois índices separados.

Cada esquema tem os campos de seu grão específico, com o campo ID pai comum a ambos os índices para uso em uma consulta de pesquisa. O corpus de pesquisa primário é o índice filho, mas emite uma consulta de pesquisa para recuperar os campos pai para cada correspondência no resultado. A Pesquisa de IA do Azure não dá suporte a junções no momento da consulta, portanto, o código do aplicativo ou a camada de orquestração precisariam mesclar ou agrupar resultados que possam ser passados para um aplicativo ou processo.

O índice pai tem um campo parent_id e um título. O parent_id é a chave do documento. Você não precisa da configuração de busca em vetores, a menos que deseje vetorizar campos no nível do documento pai.

{
    "name": "my-parent-index",
    "fields": [

        {"name": "parent_id", "type": "Edm.String", "filterable": true},
        {"name": "title", "type": "Edm.String", "searchable": true, "filterable": true, "sortable": true, "retrievable": true},
    ]
}

O índice filho tem os campos em partes, além do campo parent_id. Se você estiver usando vetorização integrada, perfis de pontuação, classificador semântico ou analisadores, você os definirá no índice filho.

{
    "name": "my-child-index",
    "fields": [
        {"name": "chunk_id", "type": "Edm.String", "key": true, "filterable": true, "analyzer": "keyword"},
        {"name": "parent_id", "type": "Edm.String", "filterable": true},
         {"name": "chunk", "type": "Edm.String","searchable": true,"retrievable": true},
        {"name": "chunk_vector", "type": "Collection(Edm.Single)", "searchable": true, "retrievable": false, "stored": false, "dimensions": 1536, "vectorSearchProfile": "hnsw"}
    ],
    "vectorSearch": {
        "algorithms": [{"name": "hsnw", "kind": "hnsw", "hnswParameters": {}}],
        "profiles": [{"name": "hsnw", "algorithm": "hnsw"}]
    },
    "scoringProfiles": [],
    "semanticConfiguration": [],
    "analyzers": []
}

Aqui está um exemplo de uma definição de projeção de índice que especifica o caminho de dados que o indexador deve usar para indexar o conteúdo. Ele especifica o nome do índice filho na definição de projeção de índice e especifica os mapeamentos de cada campo filho ou em nível de partes. Esse é o único lugar em que o nome do índice filho é especificado.

"indexProjections": {
    "selectors": [
        {
            "targetIndexName": "my-child-index",
            "parentKeyFieldName": "parent_id",
            "sourceContext": "/document/pages/*",
            "mappings": [
                {
                    "name": "chunk",
                    "source": "/document/pages/*",
                    "sourceContext": null,
                    "inputs": []
                },
                {
                    "name": "chunk_vector",
                    "source": "/document/pages/*/chunk_vector",
                    "sourceContext": null,
                    "inputs": []
                }
            ]
        }
    ]
}

A definição do indexador especifica os componentes do pipeline. Na definição do indexador, o nome do índice a ser fornecido é o índice pai. Se você precisar de mapeamentos de campo para os campos de nível pai, defina-os em outputFieldMappings. Para indexação um para muitos que usa índices separados, a definição do indexador pode ser semelhante ao exemplo a seguir.

{
  "name": "my-indexer",
  "dataSourceName": "my-ds",
  "targetIndexName": "my-parent-index",
  "skillsetName" : "my-skillset"
  "parameters": { },
  "fieldMappings": (optional) Maps fields in the underlying data source to fields in an index,
  "outputFieldMappings" : (required) Maps skill outputs to fields in an index,
}

Próxima etapa

O agrupamento de dados e a indexação um para muitos fazem parte do padrão RAG na Pesquisa de IA do Azure. Continue no tutorial a seguir e no exemplo de código para saber mais sobre ele.