Partilhar 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. Através de uma projeção de índice, você pode enviar conteúdo para:

  • Um único índice, onde os campos pai se repetem para cada pedaço, mas o grão do índice está no nível do bloco. O tutorial 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 blocos. O índice filho é o corpus de pesquisa primário, mas o índice pai pode ser usado para consultas de pesquisa quando você deseja recuperar os campos pai de um bloco específico ou para consultas independentes.

A maioria das implementações são um único índice organizado em blocos com campos pai, como o nome do arquivo do documento, repetindo para cada bloco. No entanto, o sistema foi projetado para suportar índices filho separados e múltiplos, se essa for a sua necessidade. O Azure AI Search não dá suporte a junções de índice, portanto, seu código de 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 bloco. Ele melhora o funcionamento da fragmentação de dados nativos, oferecendo 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

  • Um pipeline de indexação baseado em indexador.

  • Um índice (um ou mais) que aceita a saída do pipeline do indexador.

  • Uma fonte de dados suportada com conteúdo que você deseja fragmentar. Pode ser conteúdo vetorial ou não vetorial.

  • Uma habilidade que divide o conteúdo em partes, seja a habilidade Divisão de texto ou uma habilidade personalizada que fornece funcionalidade equivalente.

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 incorporação como AzureOpenAIEmbedding se seu cenário incluir vetorização integrada.

Dependência do processamento do indexador

A indexação um-para-muitos depende 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 o seu conteúdo pesquisável
  • Um conjunto de habilidades que contém uma projeção de índice*
  • Um indexador

Seus dados podem ser originados de qualquer fonte de dados suportada, mas a suposição é que o conteúdo é grande o suficiente para que você queira fragmentá-lo, e a razão para fragmentá-lo é que você está implementando um padrão RAG que fornece dados de aterramento para um modelo de bate-papo. Ou, você está implementando a pesquisa vetorial e precisa atender aos requisitos de tamanho de entrada menor dos modelos de incorporação.

Os indexadores carregam dados indexados em um índice predefinido. Como você define o esquema e se deve usar um ou mais índices é a primeira decisão a ser tomada 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

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

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

  • Um campo que associa cada parte ao seu pai. Deve ser do tipo Edm.String. Não pode ser o campo chave do documento e deve ter filterable definido como true. É referido como parent_id nos exemplos e como um valor-chave projetado neste artigo.

  • Outros campos para conteúdo, como texto ou campos de blocos 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 blocos com conteúdo pai repetindo para cada bloco é o padrão predominante para cenários de pesquisa de 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. Os campos filho são os blocos vetoriais e não vetoriais. O chunk_id é o ID do documento deste índice. O parent_id e o título se repetem para cada parte do índice.

Você pode usar o portal do Azure, 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 são definidas principalmente como uma matriz de , onde cada seletor corresponde a um índice de destino diferente no serviço de selectorspesquisa. Esta seção começa com sintaxe e exemplos de contexto, seguidos por referência de parâmetro.

Escolha uma guia para as várias sintaxe da API. Atualmente, não há suporte de 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 dos índices estão geralmente disponíveis. Recomendamos a API estável mais recente:

Aqui está um exemplo de carga útil para uma definição de projeções de índice que você pode usar para projetar páginas individuais saídas pela habilidade 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 do parâmetro

Parâmetros de projeção do índice Definição
selectors Parâmetros para o corpus de pesquisa principal, geralmente aquele projetado em torno de pedaços.
projectionMode Um parâmetro opcional que fornece instruções para o indexador. O único valor válido para esse parâmetro é skipIndexingParentDocuments, e ele é usado quando o índice de bloco é 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 fragmentado. Se você não definir skipIndexingParentDocumentso , obterá documentos de pesquisa adicionais em seu índice que são nulos para partes, mas preenchidos apenas com campos pai. Por exemplo, se cinco documentos contribuírem com 100 partes para o índice, o número de documentos no índice será 105. Os cinco documentos criados ou campos pai têm nulos para campos de bloco (filho), tornando-os substancialmente diferentes da maior parte dos documentos no índice. Recomendamos projectionMode definir 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 do índice são projetados. É o índice de fragmentação única 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 a granularidade na qual mapear dados em documentos de pesquisa individuais. Para obter mais informações, consulte 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 um também pode definir dados recursivamente com um campo e inputs opcionalsourceContext, semelhante ao armazenamento de conhecimento ou Habilidade Shaper. Dependendo do seu aplicativo, esses parâmetros permitem que você molde dados em campos do 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 mappings parâmetro é importante. Você deve mapear explicitamente todos os campos no índice filho, exceto os campos ID, como a chave do documento e a ID pai.

Este requisito contrasta com outras convenções de mapeamento de campo no Azure AI Search. 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 de chave pai. Isso interrompe o controle de alterações e a atualização sincronizada de dados.

Tratamento de documentos pai

Agora que você já viu vários padrões para indexações um-para-muitos, vamos comparar as principais diferenças sobre cada opção. As projeções de índice geram efetivamente documentos "filhos" para cada documento "pai" que passa por 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, defina a definição do targetIndexName indexador como o índice pai e defina o targetIndexName seletor de projeção de índice como o índice filho.

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

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

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

Revisar 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 passam para indexação, sem nenhuma etapa de processamento de habilidades intermediárias. Normalmente, um indexador pode mapear automaticamente campos que têm 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.

Nota

Os mapeamentos de campo de saída e os mapeamentos de campo de projeção de indexador aceitam nós de árvore de documentos enriquecidos como entradas de origem. Saber como especificar um caminho para cada nó é essencial para configurar o caminho de dados. Para saber mais sobre sintaxe de caminho, consulte Referenciar um caminho para nós enriquecidos e definição de conjunto de habilidades para 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 seu í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 contínuo de alterações e deteção de exclusão. Esta seção explica o ciclo de vida do conteúdo da indexação um-para-muitos em relação à atualização de dados.

Para fontes de dados que fornecem controle de alterações e deteção de exclusão, um processo de indexador pode coletar alterações em seus dados de origem. Cada vez que você executa o indexador e o conjunto de habilidades, as projeções de índice são atualizadas se o conjunto de habilidades ou os dados de origem subjacentes tiverem sido alterados. Quaisquer alterações captadas pelo indexador são propagadas através 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-chave projetado para cada bloco. Esse valor é atualizado quando os dados subjacentes são alterados.

Nota

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

Conteúdo atualizado

Se você adicionar 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 ao controle de alterações e à deteção de exclusão. Para exame, se uma palavra ou frase for alterada em um documento, o bloco no índice de destino que contém essa palavra ou frase será atualizado na próxima execução do indexador. Outros tipos de atualizações, como alterar um tipo de campo e algumas atribuições, não são suportados para campos existentes. Para obter mais informações sobre atualizações permitidas, consulte Atualizar um esquema de índice.

Algumas fontes de dados, como o Armazenamento do Azure, oferecem suporte ao controle de alterações e exclusões por padrão, com base no carimbo de data/hora. Outras fontes de dados, como OneLake, Azure SQL 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 encurtado para ter menos partes), o documento filho correspondente no índice de pesquisa será excluído. Os documentos filho restantes também recebem sua chave atualizada para incluir um novo valor de hash, mesmo que seu 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 só serão excluídos se a exclusão for detetada por uma dataDeletionDetectionPolicy definição definida na fonte de dados. Se você não tiver um dataDeletionDetectionPolicy documento pai configurado e precisar excluir um documento pai da fonte de dados, exclua manualmente os documentos filho se eles não forem mais desejados.

Valor-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-chave projetado no lado "muitos". Se você estiver usando vetorização integrada ou o assistente Importar e vetorizar dados, o valor da chave projetada será o parent_id campo em um lado fragmentado ou "muitos" do índice.

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

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

Por exemplo, se você dividir um documento pai com o valor-chave "aa1b22c33" em quatro páginas e, em seguida, cada uma dessas páginas será 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, o hash aleatório será alterado, mais páginas serão adicionadas e o conteúdo de cada parte será atualizado para corresponder ao que estiver no documento de origem.

Exemplo de índices pai-filho separados

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

Cada esquema tem os campos para 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 emita uma consulta de pesquisa para recuperar os campos pai para cada correspondência no resultado. O Azure AI Search não oferece suporte a junções no momento da consulta, portanto, seu código de aplicativo ou camada de orquestração precisaria mesclar ou agrupar resultados que podem ser passados para um aplicativo ou processo.

O índice pai tem um campo parent_id e título. O parent_id é a chave do documento. Você não precisa de configuração de pesquisa vetorial, a menos que queira 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, mais o campo parent_id. Se você estiver usando vetorização integrada, perfis de pontuação, classificador semântico ou analisadores, você os definiria 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 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 de nível de bloco. Este é o único lugar onde 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 se parecer com o 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óximo passo

O agrupamento de dados e a indexação um-para-muitos fazem parte do padrão RAG no Azure AI Search. Continue para o tutorial e exemplo de código a seguir para saber mais sobre isso.