Pesquisa de texto completo no Azure AI Search

A pesquisa de texto completo é uma abordagem na recuperação de informações que corresponde ao texto sem formatação armazenado em um índice. Por exemplo, dada uma string de consulta "hotéis em San Diego na praia", o mecanismo de busca procura strings tokenizadas com base nesses termos. Para tornar as varreduras mais eficientes, as cadeias de caracteres de consulta passam por análise lexical: abaixando todos os termos, removendo palavras de parada como "o" e reduzindo termos a formas de raiz primitivas. Quando os termos correspondentes são encontrados, o mecanismo de pesquisa recupera documentos, classifica-os em ordem de relevância e retorna os principais resultados.

A execução da consulta pode ser complexa. Este artigo é para desenvolvedores que precisam de uma compreensão mais profunda de como a pesquisa de texto completo funciona no Azure AI Search. Para consultas de texto, o Azure AI Search fornece perfeitamente os resultados esperados na maioria dos cenários, mas ocasionalmente você pode obter um resultado que parece "desativado" de alguma forma. Nessas situações, ter um plano de fundo nos quatro estágios de execução da consulta Lucene (análise de consulta, análise lexical, correspondência de documentos, pontuação) pode ajudá-lo a identificar alterações específicas nos parâmetros de consulta ou na configuração do índice que produzem o resultado desejado.

Nota

O Azure AI Search usa o Apache Lucene para pesquisa de texto completo, mas a integração do Lucene não é exaustiva. Expomos e estendemos seletivamente a funcionalidade Lucene para habilitar os cenários importantes para o Azure AI Search.

Visão geral da arquitetura e diagrama

A execução da consulta tem quatro etapas:

  1. Análise de consultas
  2. Análise lexical
  3. Recuperação de documentos
  4. Classificação

Uma consulta de pesquisa de texto completo começa com a análise do texto da consulta para extrair termos e operadores de pesquisa. Existem dois analisadores para que você possa escolher entre velocidade e complexidade. Segue-se uma fase de análise, em que os termos de consulta individuais são por vezes divididos e reconstituídos em novos formulários. Este passo ajuda a lançar uma rede mais ampla sobre o que pode ser considerado como uma possível correspondência. Em seguida, o motor de pesquisa analisa o índice para encontrar documentos com termos e pontuações correspondentes a cada correspondência. Um conjunto de resultados é então classificado por uma pontuação de relevância atribuída a cada documento de correspondência individual. Aqueles que estão no topo da lista classificada são retornados ao aplicativo de chamada.

O diagrama abaixo ilustra os componentes usados para processar uma solicitação de pesquisa.

Lucene query architecture diagram in Azure AI Search.

Componentes principais Descrição funcional
Analisadores de consulta Separe os termos de consulta dos operadores de consulta e crie a estrutura de consulta (uma árvore de consulta) a ser enviada para o mecanismo de pesquisa.
Analisadores Execute a análise lexical em termos de consulta. Esse processo pode envolver a transformação, remoção ou expansão de termos de consulta.
Índice Uma estrutura de dados eficiente usada para armazenar e organizar termos pesquisáveis extraídos de documentos indexados.
Motor de pesquisa Recupera e pontua documentos correspondentes com base no conteúdo do índice invertido.

Anatomia de um pedido de pesquisa

Uma solicitação de pesquisa é uma especificação completa do que deve ser retornado em um conjunto de resultados. Na forma mais simples, é uma consulta vazia sem nenhum tipo de critério. Um exemplo mais realista inclui parâmetros, vários termos de consulta, talvez com escopo para determinados campos, com possivelmente uma expressão de filtro e regras de ordenação.

O exemplo a seguir é uma solicitação de pesquisa que você pode enviar para a Pesquisa de IA do Azure usando a API REST.

POST /indexes/hotels/docs/search?api-version=2020-06-30
{
    "search": "Spacious, air-condition* +\"Ocean view\"",
    "searchFields": "description, title",
    "searchMode": "any",
    "filter": "price ge 60 and price lt 300",
    "orderby": "geo.distance(location, geography'POINT(-159.476235 22.227659)')", 
    "queryType": "full" 
}

Para este pedido, o motor de busca faz as seguintes operações:

  1. Encontra documentos onde o preço é de pelo menos US $ 60 e menos de US $ 300.

  2. Executa a consulta. Neste exemplo, a consulta de pesquisa consiste em frases e termos: "Spacious, air-condition* +\"Ocean view\"" (os usuários normalmente não inserem pontuação, mas incluí-la no exemplo nos permite explicar como os analisadores lidam com ela).

    Para esta consulta, o motor de busca verifica os campos de descrição e título especificados em "searchFields" para documentos que contenham "Ocean view", e adicionalmente sobre o termo "spacious", ou em termos que começam com o prefixo "air-condition". O parâmetro "searchMode" é usado para corresponder em qualquer termo (padrão) ou em todos eles, para casos em que um termo não é explicitamente necessário (+).

  3. Ordena o conjunto resultante de hotéis por proximidade a um determinado local geográfico e, em seguida, retorna os resultados para o aplicativo de chamada.

A maior parte deste artigo é sobre o processamento da consulta de pesquisa: "Spacious, air-condition* +\"Ocean view\"". A filtragem e a ordenação estão fora do escopo. Para obter mais informações, consulte a documentação de referência da API de pesquisa.

Etapa 1: Análise de consultas

Como observado, a cadeia de caracteres de consulta é a primeira linha da solicitação:

 "search": "Spacious, air-condition* +\"Ocean view\"", 

O analisador de consulta separa operadores (como * e no exemplo) de termos de pesquisa e + desconstrói a consulta de pesquisa em subconsultas de um tipo suportado:

  • Consulta de termos independentes (como Espaçoso)
  • consulta de frases para termos citados (como vista para o mar)
  • consulta de prefixo para termos seguidos por um operador * de prefixo (como ar-condicionado)

Para obter uma lista completa dos tipos de consulta suportados, consulte Sintaxe de consulta Lucene

Os operadores associados a uma subconsulta determinam se a consulta "deve ser" ou "deve ser" satisfeita para que um documento seja considerado uma correspondência. Por exemplo, +"Ocean view" é "must" devido ao + operador.

O analisador de consulta reestrutura as subconsultas em uma árvore de consulta (uma estrutura interna que representa a consulta) que ele passa para o mecanismo de pesquisa. No primeiro estágio da análise de consulta, a árvore de consulta tem esta aparência.

Conceptual diagram of a boolean query with searchmode set to any.

Analisadores suportados: Lucene simples e completo

O Azure AI Search expõe duas linguagens de consulta diferentes, simple (padrão) e full. Ao definir o queryType parâmetro com sua solicitação de pesquisa, você informa ao analisador de consulta qual idioma de consulta você escolhe para que ele saiba como interpretar os operadores e a sintaxe.

  • A linguagem de consulta simples é intuitiva e robusta, muitas vezes adequada para interpretar a entrada do usuário como está, sem processamento do lado do cliente. Ele suporta operadores de consulta familiares dos mecanismos de pesquisa da web.

  • A linguagem de consulta Full Lucene, que você obtém definindo queryType=full, estende a linguagem de consulta Simple padrão adicionando suporte para mais operadores e tipos de consulta, como consultas curinga, fuzzy, regex e com escopo de campo. Por exemplo, uma expressão regular enviada na sintaxe de consulta simples seria interpretada como uma cadeia de caracteres de consulta e não como uma expressão. A solicitação de exemplo neste artigo usa a linguagem de consulta Full Lucene.

Impacto do searchMode no analisador

Outro parâmetro de solicitação de pesquisa que afeta a análise é o parâmetro "searchMode". Ele controla o operador padrão para consultas booleanas: qualquer (padrão) ou todos.

Quando "searchMode=any", que é o padrão, o delimitador de espaço entre espaçoso e ar-condicionado é OR (||), tornando o texto de consulta de exemplo equivalente a:

Spacious,||air-condition*+"Ocean view" 

Operadores explícitos, como + em +"Ocean view", são inequívocos na construção de consultas booleanas (o termo deve corresponder). Menos óbvio é como interpretar os restantes termos: espaçoso e climatizado. O motor de busca deve encontrar correspondências com vista para o mar e espaçosas e com ar condicionado? Ou deve encontrar vista para o mar mais um dos termos restantes?

Por defeito ("searchMode=any"), o motor de busca assume a interpretação mais ampla. Qualquer campo deve ser correspondido, refletindo a semântica "ou". A árvore de consulta inicial ilustrada anteriormente, com as duas operações "should", mostra o padrão.

Suponha que agora definimos "searchMode=all". Neste caso, o espaço é interpretado como uma operação "e". Cada um dos termos restantes deve estar presente no documento para se qualificar como uma correspondência. A consulta de exemplo resultante seria interpretada da seguinte forma:

+Spacious,+air-condition*+"Ocean view"

Uma árvore de consulta modificada para essa consulta seria a seguinte, onde um documento correspondente é a interseção de todas as três subconsultas:

Conceptual diagram of a boolean query with searchmode set to all.

Nota

Escolher "searchMode=any" em vez de "searchMode=all" é uma decisão melhor tomada executando consultas representativas. Os usuários que provavelmente incluirão operadores (comuns ao pesquisar repositórios de documentos) podem encontrar resultados mais intuitivos se "searchMode=all" informar construções de consulta booleanas. Para obter mais informações sobre a interação entre "searchMode" e operadores, consulte Sintaxe de consulta simples.

Etapa 2: Análise lexical

Os analisadores lexicais processam consultas de termos e consultas de frases depois que a árvore de consultas é estruturada. Um analisador aceita as entradas de texto fornecidas pelo analisador, processa o texto e, em seguida, envia de volta termos tokenizados para serem incorporados na árvore de consulta.

A forma mais comum de análise lexical é a *análise linguística que transforma termos de consulta com base em regras específicas de uma determinada língua:

  • Reduzir um termo de consulta à forma raiz de uma palavra
  • Remoção de palavras não essenciais (palavras paradas, como "o" ou "e" em inglês)
  • Dividir uma palavra composta em partes componentes
  • Caixa inferior uma palavra maiúscula

Todas essas operações tendem a apagar as diferenças entre a entrada de texto fornecida pelo usuário e os termos armazenados no índice. Tais operações vão além do processamento de texto e exigem um conhecimento aprofundado da própria língua. Para adicionar esta camada de consciência linguística, o Azure AI Search suporta uma longa lista de analisadores de linguagem da Lucene e da Microsoft.

Nota

Os requisitos de análise podem variar de mínimos a elaborados, dependendo do seu cenário. Você pode controlar a complexidade da análise lexical selecionando um dos analisadores predefinidos ou criando seu próprio analisador personalizado. Os analisadores têm como escopo campos pesquisáveis e são especificados como parte de uma definição de campo. Isso permite que você varie a análise lexical por campo. Não especificado, o analisador Lucene padrão é usado.

Em nosso exemplo, antes da análise, a árvore de consulta inicial tem o termo "Espaçoso", com um "S" maiúsculo e uma vírgula que o analisador de consulta interpreta como parte do termo de consulta (uma vírgula não é considerada um operador de linguagem de consulta).

Quando o analisador padrão processa o termo, ele irá minúsculas "vista para o mar" e "espaçoso", e removerá o caractere de vírgula. A árvore de consulta modificada tem a seguinte aparência:

Conceptual diagram of a boolean query with analyzed terms.

Testando comportamentos do analisador

O comportamento de um analisador pode ser testado usando a API de análise. Forneça o texto que você deseja analisar para ver quais termos determinado analisador gera. Por exemplo, para ver como o analisador padrão processaria o texto "ar-condicionado", você pode emitir a seguinte solicitação:

{
    "text": "air-condition",
    "analyzer": "standard"
}

O analisador padrão divide o texto de entrada nos dois tokens a seguir, anotando com atributos como deslocamentos de início e fim (usados para realce de acertos), bem como sua posição (usada para correspondência de frases):

{
  "tokens": [
    {
      "token": "air",
      "startOffset": 0,
      "endOffset": 3,
      "position": 0
    },
    {
      "token": "condition",
      "startOffset": 4,
      "endOffset": 13,
      "position": 1
    }
  ]
}

Exceções à análise lexical

A análise lexical aplica-se apenas a tipos de consulta que requerem termos completos – uma consulta de termo ou uma consulta de frase. Não se aplica a tipos de consulta com termos incompletos – consulta de prefixo, consulta curinga, consulta regex – ou a uma consulta difusa. Esses tipos de consulta, incluindo a consulta de prefixo com termo air-condition* em nosso exemplo, são adicionados diretamente à árvore de consulta, ignorando o estágio de análise. A única transformação executada em termos de consulta desses tipos é a caixa baixa.

Etapa 3: Recuperação de documentos

A recuperação de documentos refere-se à localização de documentos com termos correspondentes no índice. Esta etapa é melhor compreendida através de um exemplo. Vamos começar com um índice de hotéis com o seguinte esquema simples:

{
    "name": "hotels",
    "fields": [
        { "name": "id", "type": "Edm.String", "key": true, "searchable": false },
        { "name": "title", "type": "Edm.String", "searchable": true },
        { "name": "description", "type": "Edm.String", "searchable": true }
    ] 
} 

Suponhamos ainda que este índice contém os seguintes quatro documentos:

{
    "value": [
        {
            "id": "1",
            "title": "Hotel Atman",
            "description": "Spacious rooms, ocean view, walking distance to the beach."
        },
        {
            "id": "2",
            "title": "Beach Resort",
            "description": "Located on the north shore of the island of Kauaʻi. Ocean view."
        },
        {
            "id": "3",
            "title": "Playa Hotel",
            "description": "Comfortable, air-conditioned rooms with ocean view."
        },
        {
            "id": "4",
            "title": "Ocean Retreat",
            "description": "Quiet and secluded"
        }
    ]
}

Como os termos são indexados

Para entender a recuperação, é útil conhecer alguns conceitos básicos sobre indexação. A unidade de armazenamento é um índice invertido, um para cada campo pesquisável. Dentro de um índice invertido há uma lista ordenada de todos os termos de todos os documentos. Cada termo é mapeado para a lista de documentos em que ocorre, como evidenciado no exemplo abaixo.

Para produzir os termos num índice invertido, o motor de busca realiza uma análise lexical sobre o conteúdo dos documentos, à semelhança do que acontece durante o processamento da consulta:

  1. As entradas de texto são passadas para um analisador, minúsculas , despojadas de pontuação e assim por diante, dependendo da configuração do analisador.
  2. Os tokens são a saída da análise lexical.
  3. Os termos são adicionados ao índice.

É comum, mas não obrigatório, usar os mesmos analisadores para operações de pesquisa e indexação para que os termos de consulta se pareçam mais com termos dentro do índice.

Nota

O Azure AI Search permite especificar diferentes analisadores para indexação e pesquisa por meio de parâmetros adicionais indexAnalyzer e searchAnalyzer de campo. Se não for especificado, o conjunto de analisadores com a analyzer propriedade será usado para indexação e pesquisa.

Índice invertido, por exemplo, documentos

Voltando ao nosso exemplo, para o campo de título, o índice invertido tem esta aparência:

Termo Lista de documentos
Atman 5
praia 2
hotel 1, 3
oceano 4
playa 3
resort 3
Retiro 4

No campo do título, apenas o hotel aparece em dois documentos: 1, 3.

Para o campo de descrição, o índice é o seguinte:

Termo Lista de documentos
Ar 3
e 4
praia 5
condicionada 3
confortável 3
Distância 5
island 2
Kauaʻi 2
localizado 2
norte 2
oceano 1, 2, 3
de 2
em 2
sossegado 4
quartos 1, 3
isolado 4
costa 2
espaçoso 5
o 1, 2
para 5
ver 1, 2, 3
andar a pé 5
com o 3

Correspondência de termos de consulta com termos indexados

Dados os índices invertidos acima, vamos retornar à consulta de exemplo e ver como os documentos correspondentes são encontrados para nossa consulta de exemplo. Lembre-se de que a árvore de consulta final tem esta aparência:

Conceptual diagram of a boolean query with analyzed terms.

Durante a execução da consulta, consultas individuais são executadas nos campos pesquisáveis de forma independente.

  • O TermQuery, "espaçoso", corresponde ao documento 1 (Hotel Atman).

  • O PrefixQuery, "ar-condicionado*", não corresponde a nenhum documento.

    Este é um comportamento que às vezes confunde os desenvolvedores. Embora o termo ar-condicionado exista no documento, ele é dividido em dois termos pelo analisador padrão. Lembre-se de que as consultas de prefixo, que contêm termos parciais, não são analisadas. Portanto, termos com prefixo "ar-condicionado" são pesquisados no índice invertido e não encontrados.

  • O PhraseQuery, "vista para o mar", procura os termos "oceano" e "vista" e verifica a proximidade dos termos no documento original. Os documentos 1, 2 e 3 correspondem a esta consulta no campo de descrição. Observe que o documento 4 tem o termo oceano no título, mas não é considerado uma correspondência, pois estamos procurando a frase "vista para o mar" em vez de palavras individuais.

Nota

Uma consulta de pesquisa é executada independentemente em relação a todos os campos pesquisáveis no índice do Azure AI Search, a menos que você limite os campos definidos com o searchFields parâmetro, conforme ilustrado na solicitação de pesquisa de exemplo. Os documentos que correspondem em qualquer um dos campos selecionados são retornados.

Em geral, para a consulta em questão, os documentos que correspondem são 1, 2, 3.

Etapa 4: Pontuação

A cada documento de um conjunto de resultados de pesquisa é atribuída uma pontuação de relevância. A função da pontuação de relevância é classificar mais alto os documentos que melhor respondem a uma pergunta do usuário, conforme expresso pela consulta de pesquisa. A pontuação é calculada com base em propriedades estatísticas de termos que corresponderam. No centro da fórmula de pontuação está TF/IDF (term frequency-inverse document frequency). Em consultas que contêm termos raros e comuns, TF/IDF promove resultados contendo o termo raro. Por exemplo, em um índice hipotético com todos os artigos da Wikipédia, a partir de documentos que correspondem à consulta do presidente, os documentos correspondentes ao presidente são considerados mais relevantes do que os documentos correspondentes no presidente.

Exemplo de pontuação

Lembre-se dos três documentos que corresponderam à nossa consulta de exemplo:

search=Spacious, air-condition* +"Ocean view"  
{
  "value": [
    {
      "@search.score": 0.25610128,
      "id": "1",
      "title": "Hotel Atman",
      "description": "Spacious rooms, ocean view, walking distance to the beach."
    },
    {
      "@search.score": 0.08951007,
      "id": "3",
      "title": "Playa Hotel",
      "description": "Comfortable, air-conditioned rooms with ocean view."
    },
    {
      "@search.score": 0.05967338,
      "id": "2",
      "title": "Ocean Resort",
      "description": "Located on a cliff on the north shore of the island of Kauai. Ocean view."
    }
  ]
}

O documento 1 correspondeu melhor à consulta porque tanto o termo espaçoso como a frase necessária vista oceano ocorrem no campo de descrição. Os dois documentos seguintes correspondem apenas à frase vista para o mar. Pode ser surpreendente que a pontuação de relevância para os documentos 2 e 3 seja diferente, embora correspondam à consulta da mesma maneira. É porque a fórmula de pontuação tem mais componentes do que apenas TF/IDF. Neste caso, foi atribuída ao documento 3 uma pontuação ligeiramente mais elevada porque a sua descrição é mais curta. Saiba mais sobre a Fórmula Prática de Pontuação de Lucene para entender como o comprimento do campo e outros fatores podem influenciar a pontuação de relevância.

Alguns tipos de consulta (curinga, prefixo, regex) sempre contribuem com uma pontuação constante para a pontuação geral do documento. Isso permite que as correspondências encontradas por meio da expansão da consulta sejam incluídas nos resultados, mas sem afetar a classificação.

Um exemplo ilustra por que isso é importante. Pesquisas curinga, incluindo pesquisas de prefixo, são ambíguas por definição porque a entrada é uma cadeia parcial com correspondências potenciais em um número muito grande de termos díspares (considere uma entrada de "tour*", com correspondências encontradas em "tours", "tourettes" e "turmalina"). Dada a natureza desses resultados, não há como inferir razoavelmente quais termos são mais valiosos do que outros. Por esse motivo, ignoramos as frequências de termos ao pontuar resultados em consultas dos tipos curinga, prefixo e regex. Em uma solicitação de pesquisa com várias partes que inclui termos parciais e completos, os resultados da entrada parcial são incorporados com uma pontuação constante para evitar viés para correspondências potencialmente inesperadas.

Ajuste de relevância

Há duas maneiras de ajustar as pontuações de relevância na Pesquisa de IA do Azure:

  1. Os perfis de pontuação promovem documentos na lista classificada de resultados com base em um conjunto de regras. No nosso exemplo, podemos considerar os documentos que correspondem no campo de título mais relevantes do que os documentos que correspondem no campo de descrição. Além disso, se o nosso índice tivesse um campo de preço para cada hotel, poderíamos promover documentos com preço mais baixo. Saiba mais sobre como adicionar perfis de pontuação a um índice de pesquisa.

  2. O aumento de termos (disponível apenas na sintaxe de consulta Full Lucene) fornece um operador ^ de impulsionamento que pode ser aplicado a qualquer parte da árvore de consulta. No nosso exemplo, em vez de pesquisar no prefixo ar-condicionado*, pode-se procurar o termo exato ar-condicionado ou o prefixo, mas os documentos que correspondem ao termo exato são classificados mais alto aplicando impulso ao termo consulta: ar-condicionado^2||Ar condicionado*. Saiba mais sobre o aumento de termos em uma consulta.

Pontuação em um índice distribuído

Todos os índices no Azure AI Search são automaticamente divididos em vários fragmentos, o que nos permite distribuir rapidamente o índice entre vários nós durante o aumento ou redução da escala do serviço. Quando uma solicitação de pesquisa é emitida, ela é emitida contra cada fragmento de forma independente. Os resultados de cada fragmento são então mesclados e ordenados por pontuação (se nenhuma outra ordem for definida). É importante saber que a função de pontuação pondera a frequência do termo de consulta em relação à sua frequência de documento inversa em todos os documentos dentro do fragmento, não em todos os fragmentos!

Isso significa que uma pontuação de relevância pode ser diferente para documentos idênticos se eles residirem em fragmentos diferentes. Felizmente, essas diferenças tendem a desaparecer à medida que o número de documentos no índice cresce devido a uma distribuição de termos mais uniforme. Não é possível presumir em qual fragmento um determinado documento será colocado. No entanto, supondo que uma chave de documento não seja alterada, ela sempre será atribuída ao mesmo fragmento.

Em geral, a pontuação do documento não é o melhor atributo para solicitar documentos se a estabilidade do pedido for importante. Por exemplo, dado dois documentos com uma pontuação idêntica, não há garantia de que um apareça primeiro em execuções subsequentes da mesma consulta. A pontuação do documento deve dar apenas uma noção geral da relevância do documento em relação a outros documentos do conjunto de resultados.

Conclusão

O sucesso dos motores de busca comerciais aumentou as expectativas para a pesquisa de texto completo sobre dados privados. Para quase qualquer tipo de experiência de pesquisa, agora esperamos que o mecanismo entenda nossa intenção, mesmo quando os termos estão escritos incorretamente ou incompletos. Podemos até esperar correspondências baseadas em termos quase equivalentes ou sinônimos que nunca especificamos.

Do ponto de vista técnico, a pesquisa de texto completo é altamente complexa, exigindo uma análise linguística sofisticada e uma abordagem sistemática ao processamento de forma a destilar, expandir e transformar termos de consulta para fornecer um resultado relevante. Dadas as complexidades inerentes, há muitos fatores que podem afetar o resultado de uma consulta. Por esta razão, investir tempo para entender a mecânica da pesquisa de texto completo oferece benefícios tangíveis ao tentar trabalhar com resultados inesperados.

Este artigo explorou a pesquisa de texto completo no contexto da Pesquisa de IA do Azure. Esperamos que ele lhe forneça informações básicas suficientes para reconhecer possíveis causas e resoluções para resolver problemas comuns de consulta.

Próximos passos

Consulte também

Search Documents REST API (Pesquisar Documentos com a API REST)

Sintaxe de consulta simples

Sintaxe de consulta Lucene completa

Processar os resultados da pesquisa