Pesquisar documentos

Antes de começar

Para ingerir documentos de amostra na Document AI Warehouse, consulte o guia de início rápido.

Ao definir os esquemas de documentos e criar documentos, é importante considerar quais propriedades você quer definir e como elas serão usadas com a pesquisa, se for o caso.

Marque uma propriedade como filtrável se quiser usá-la para incluir ou excluir uma parte dos documentos de uma pesquisa. Por exemplo, você pode tornar uma propriedade que representa um "fornecedor" filtrável porque os usuários querem pesquisar faturas de um fornecedor específico.

Se você quiser construir um histograma (consulte o exemplo mais adiante neste tópico) em uma propriedade, ela precisará ser filtrável.

Marque uma propriedade como pesquisável se ela tiver dados que os usuários vão querer consultar durante uma pesquisa por palavra-chave.

A pesquisa de texto completo é o processo de recuperação de todos os documentos que correspondem às palavras-chave de pesquisa no texto pesquisável. O usuário fornece uma lista de palavras-chave (palavras separadas por um espaço em branco), presumivelmente digitadas em um campo de pesquisa na UI. Na Document AI Warehouse, as palavras-chave são processadas e convertidas em uma consulta adequada. Esse processamento remove as palavras irrelevantes ("o", "em" e "um") e deriva as palavras restantes. A derivação reduz a palavra a uma versão comum da redação, para que a variação da palavra corresponda. Por exemplo: "trabalho", "trabalhando", "trabalhado".

Quais dados são pesquisados?

  • O plain_text do documento.
  • Se você estiver importando um objeto da Document AI, use o cloud_ai_document.text incorporado.
  • O display_name do documento.
  • Todas as propriedades pesquisáveis.

A consulta oferece suporte parcial à sintaxe do Google AIP. Especificamente, a consulta oferece suporte a literais, operadores lógicos, operadores de negação, operadores de comparação e funções.

  • Literais: um valor literal simples (exemplos: "42", "Hugo") é um valor a ser correspondido. Ele pesquisa o texto completo do documento e as propriedades pesquisáveis.
  • Operadores lógicos: "AND", "and", "OR" e "or" são operadores lógicos binários (exemplo: "engineer OR developer").
  • Operadores de negação: "NOT" e "!" são operadores de negação (exemplo: "NOT software").
  • Operadores de comparação: oferecem suporte aos operadores de comparação binária =, !=, <, >, <= e >= para string, numérico, enum, booleano. Também oferecem suporte ao operador "like" ~~ para string. Ele fornece funcionalidade de pesquisa semântica analisando, derivando e fazendo expansão de sinônimos na consulta de entrada.

    Para especificar uma propriedade na consulta, a expressão do lado esquerdo na comparação precisa ser o ID da propriedade, incluindo o pai. O lado direito precisa ser literais. Por exemplo: \"projects/123/locations/us\".property_a < 1 corresponde a resultados em que property_a é menor que 1 no projeto 123 e no local us. Os literais e a expressão de comparação podem ser conectados em uma única consulta (exemplo: software engineer \"projects/123/locations/us\".salary > 100).

  • Funções: as funções compatíveis são LOWER([property_name]) para realizar uma correspondência indiferente a maiúsculas e EMPTY([property_name]) para filtrar a existência de uma chave.

  • Oferece suporte a expressões aninhadas conectadas usando parênteses e operadores lógicos. Os operadores lógicos padrão são AND se não houver operadores entre as expressões.

A consulta pode ser usada com outros filtros, por exemplo, time_filters e folder_name_filter. Eles são conectados com o operador AND internamente.

As consultas de pesquisa podem ser filtradas por outros parâmetros, como property, time, schema, folder e creator.

Chamada para uma solicitação de pesquisa

Para chamar o serviço de pesquisa, você precisa usar uma solicitação de pesquisa, que é definida da seguinte maneira:

{
  "requestMetadata": {
    object (RequestMetadata)
  },
  "documentQuery": {
    object (DocumentQuery)
  },
  "offset": integer,
  "pageSize": integer,
  "pageToken": string,
  "orderBy": string,
  "histogramQueries": [
    {
      object (HistogramQuery)
    }
  ],
  "requireTotalSize": boolean,
  "totalResultSize": enum (TotalResultSize),
  "qaSizeLimit": integer
}

O campo parent precisa ser preenchido com o formato:

/projects/PROJECT_ID/locations/LOCATION

Resposta a uma solicitação de pesquisa

A resposta da pesquisa é definida da seguinte maneira:

{
  "matchingDocuments": [
    {
      object (MatchingDocument)
    }
  ],
  "nextPageToken": string,
  "totalSize": integer,
  "metadata": {
    object (ResponseMetadata)
  },
  "histogramQueryResults": [
    {
      object (HistogramQueryResult)
    }
  ]
}

Consulta de documentos

O campo document_query é definido da seguinte maneira:

{
  "query": string,
  "isNlQuery": boolean,
  "customPropertyFilter": string,
  "timeFilters": [
    {
      object (TimeFilter)
    }
  ],
  "documentSchemaNames": [
    string
  ],
  "propertyFilter": [
    {
      object (PropertyFilter)
    }
  ],
  "fileTypeFilter": {
    object (FileTypeFilter)
  },
  "folderNameFilter": string,
  "queryContext": [
    string
  ],
  "documentCreatorFilter": [
    string
  ],
  "customWeightsMetadata": {
    object (CustomWeightsMetadata)
  }
}

O campo query é para as palavras de consulta de pesquisa do usuário solicitante. Normalmente, elas vêm do campo de pesquisa na UI.

Filtros

A Document AI Warehouse oferece vários filtros.

Filtro de tempo do documento

O filtro de tempo de criação e atualização é exatamente o que você espera: ele encontra documentos que correspondem às palavras-chave em um período especificado.

Um objeto TimeFilter é usado para especificar o período e é definido da seguinte maneira:

{
  "timeRange": {
    object (Interval)
  },
  "timeField": enum (TimeField)
}

O campo time_field é onde você especifica se o período especificado no time_range é para o tempo de criação ou a última atualização do documento.

O campo time_range especifica o período como um Interval. Um Interval é definido como:

{
  "startTime": string,
  "endTime": string
}

Filtro de criador

Para pesquisar documentos criados por um ou mais usuários específicos, use o filtro de criador. Por exemplo:

  {
    document_query {
      query: "videogames director",
      documentCreatorFilter: [
        "diane@some_company.com",
        "frank@some_company.com",
      ],
    },
  }

Filtro de propriedade

O filtro de propriedade permite especificar filtros em qualquer uma das propriedades especificadas em um esquema, desde que essa propriedade tenha sido configurada para ser filtrável.

Por exemplo, o uso de filtros de propriedade no setor jurídico pode filtrar uma propriedade chamada COURT para pesquisar apenas documentos de um tribunal específico.

Os filtros de propriedade usam um objeto PropertyFilter. É possível ter mais de um filtro de propriedade. Quando você usa vários filtros de propriedade, eles são combinados usando o operador OR. Um filtro de propriedade é definido da seguinte maneira:

  {
    "documentSchemaName": string,
    "condition": string
  }

As propriedades são definidas em esquemas. Assim, o campo documentSchemaName é onde você especifica o esquema da propriedade usada para filtragem. No campo condition, especifique a lógica desejada. Para exemplos de uso dos campos documentSchemaName e condition, consulte os exemplos anteriores nesta página.

Documento correspondente

Um documento correspondente contém um Document e um snippet (discutido mais adiante). O documento retornado em MatchingDocument não é um documento totalmente preenchido. Ele contém dados mínimos para exibir uma lista de resultados da pesquisa ao usuário solicitante. Se o documento completo for desejado (por exemplo, se o usuário clicar em um resultado da pesquisa), ele precisará ser recuperado pela API GetDocument.

Os seguintes Document campos são preenchidos: Project number, Document id, Document schema id, Create time, Update time, Display name, Raw document file type, Reference id e Filterable properties.

Um documento correspondente seria assim:

{
  "document": {
    object (Document)
  },
  "searchTextSnippet": string,
  "qaResult": {
    object (QAResult)
  }
}

Classificação/ordenação

A solicitação de pesquisa permite especificar como você quer que os resultados sejam classificados. Para classificar, use o campo order_by na solicitação de pesquisa. Os valores possíveis para esse campo incluem:

  • relevance desc: relevância decrescente, ou seja, as melhores correspondências estão na parte de cima.
  • upload_date desc: a data em que o documento foi criado em ordem decrescente (mais recente na parte de cima).
  • upload_date - a data em que o documento foi criado em ordem crescente (mais antigo na parte de cima).
  • update_date desc - a data em que o documento foi atualizado pela última vez em ordem decrescente (mais recente na parte de cima).
  • Update_date - a data em que o documento foi atualizado pela última vez em ordem crescente (mais antigo na parte de cima).

Se você não especificar uma classificação, mas fornecer palavras-chave de pesquisa, a classificação será por relevância decrescente (as melhores correspondências na parte de cima). Se nem a classificação nem as palavras-chave forem fornecidas, a classificação padrão será por tempo de atualização decrescente (os documentos mais recentes na parte de cima).

Paginação

A paginação é útil para exibir uma página de dados para o usuário final. Aqui, você pode especificar o tamanho da página e receber uma contagem total do tamanho do resultado para exibir ao usuário (por exemplo, "Mostrando 50 documentos de 300").

Defina o campo page_size como o número desejado de resultados que você quer receber com a solicitação de pesquisa. Isso pode corresponder aos requisitos do tamanho de exibição do resultado da pesquisa da UI.

Há dois mecanismos: deslocamento e token de página.

Um deslocamento é o índice na lista de documentos retornáveis que você quer retornar. Por exemplo, um deslocamento de 5 significa que você quer o sexto documento em diante. Presumivelmente, você incrementaria o deslocamento pelo tamanho da página para a próxima página de resultados.

Como alternativa, você pode usar um token de página e não precisar se preocupar em calcular o próximo deslocamento. Depois de fazer sua primeira solicitação de pesquisa, você recebe uma resposta de pesquisa que contém o campo next_page_token. Se esse campo estiver vazio, não haverá mais resultados. Se o campo não estiver vazio, use esse token na próxima solicitação de pesquisa definindo o campo page_token.

Algumas interfaces mostram a contagem de documentos encontrados pela pesquisa. Por exemplo, you are viewing 10 documents of 120. Para receber uma contagem de documentos retornados, defina o campo require_total_size boolean da solicitação como True. Dica: require_total_size=True acarreta uma penalidade de performance. Defina isso na consulta da primeira página e, em seguida, defina como false em todas as solicitações subsequentes, mantendo a contagem total em uma variável local.

Exemplos de código

Python

Para mais informações, consulte a documentação de referência da API Document AI Warehouse Python.

Para autenticar na Document AI Warehouse, configure o Application Default Credentials. Se quiser mais informações, consulte Configurar a autenticação para um ambiente de desenvolvimento local.


from google.cloud import contentwarehouse

# TODO(developer): Uncomment these variables before running the sample.
# project_number = 'YOUR_PROJECT_NUMBER'
# location = 'YOUR_PROJECT_LOCATION' # Format is 'us' or 'eu'
# document_query_text = 'YOUR_DOCUMENT_QUERY'
# user_id = 'user:YOUR_SERVICE_ACCOUNT_ID' # Format is "user:xxxx@example.com"


def search_documents_sample(
    project_number: str, location: str, document_query_text: str, user_id: str
) -> None:
    # Create a client
    client = contentwarehouse.DocumentServiceClient()

    # The full resource name of the location, e.g.:
    # projects/{project_number}/locations/{location}
    parent = client.common_location_path(project=project_number, location=location)

    # File Type Filter
    # Options: DOCUMENT, FOLDER
    file_type_filter = contentwarehouse.FileTypeFilter(
        file_type=contentwarehouse.FileTypeFilter.FileType.DOCUMENT
    )

    # Document Text Query
    document_query = contentwarehouse.DocumentQuery(
        query=document_query_text,
        file_type_filter=file_type_filter,
    )

    # Histogram Query
    histogram_query = contentwarehouse.HistogramQuery(
        histogram_query='count("DocumentSchemaId")'
    )

    request_metadata = contentwarehouse.RequestMetadata(
        user_info=contentwarehouse.UserInfo(id=user_id)
    )

    # Define request
    request = contentwarehouse.SearchDocumentsRequest(
        parent=parent,
        request_metadata=request_metadata,
        document_query=document_query,
        histogram_queries=[histogram_query],
    )

    # Make the request
    response = client.search_documents(request=request)

    # Print search results
    for matching_document in response.matching_documents:
        document = matching_document.document
        # Display name - schema display name.
        # Name.
        # Create date.
        # Snippet - keywords are highlighted with <b> & </b>.
        print(
            f"{document.display_name} - {document.document_schema_name}\n"
            f"{document.name}\n"
            f"{document.create_time}\n"
            f"{matching_document.search_text_snippet}\n"
        )

    # Print histogram
    for histogram_query_result in response.histogram_query_results:
        print(
            f"Histogram Query: {histogram_query_result.histogram_query}\n"
            f"| {'Schema':<70} | {'Count':<15} |"
        )
        for key, value in histogram_query_result.histogram.items():
            print(f"| {key:<70} | {value:<15} |")

Java

Para mais informações, consulte a Document AI Warehouse Java API documentação de referência.

Para autenticar na Document AI Warehouse, configure o Application Default Credentials. Se quiser mais informações, consulte Configurar a autenticação para um ambiente de desenvolvimento local.

import com.google.cloud.contentwarehouse.v1.DocumentQuery;
import com.google.cloud.contentwarehouse.v1.DocumentServiceClient;
import com.google.cloud.contentwarehouse.v1.DocumentServiceClient.SearchDocumentsPagedResponse;
import com.google.cloud.contentwarehouse.v1.DocumentServiceSettings;
import com.google.cloud.contentwarehouse.v1.FileTypeFilter;
import com.google.cloud.contentwarehouse.v1.FileTypeFilter.FileType;
import com.google.cloud.contentwarehouse.v1.LocationName;
import com.google.cloud.contentwarehouse.v1.RequestMetadata;
import com.google.cloud.contentwarehouse.v1.SearchDocumentsRequest;
import com.google.cloud.contentwarehouse.v1.SearchDocumentsResponse.MatchingDocument;
import com.google.cloud.contentwarehouse.v1.UserInfo;
import com.google.cloud.resourcemanager.v3.Project;
import com.google.cloud.resourcemanager.v3.ProjectName;
import com.google.cloud.resourcemanager.v3.ProjectsClient;
import java.io.IOException;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeoutException;

public class SearchDocuments {
  public static void main(String[] args) throws IOException, 
        InterruptedException, ExecutionException, TimeoutException { 
    // TODO(developer): Replace these variables before running the sample.
    String projectId = "your-project-id";
    String location = "your-region"; // Format is "us" or "eu".
    String documentQuery = "your-document-query";
    String userId = "your-user-id"; // Format is user:<user-id>

    searchDocuments(projectId, location, documentQuery, userId);
  }

  // Searches all documents for a given Document Query
  public static void searchDocuments(String projectId, String location,
        String documentQuery, String userId) throws IOException, InterruptedException,
          ExecutionException, TimeoutException { 
    String projectNumber = getProjectNumber(projectId);

    String endpoint = "contentwarehouse.googleapis.com:443";
    if (!"us".equals(location)) {
      endpoint = String.format("%s-%s", location, endpoint);
    }

    DocumentServiceSettings documentServiceSettings = 
             DocumentServiceSettings.newBuilder().setEndpoint(endpoint)
             .build(); 

    /*
     * Create the Document Service Client 
     * Initialize client that will be used to send requests. 
     * This client only needs to be created once, and can be reused for multiple requests. 
     */
    try (DocumentServiceClient documentServiceClient = 
            DocumentServiceClient.create(documentServiceSettings)) {  

      /*
       * The full resource name of the location, e.g.:
       * projects/{project_number}/locations/{location} 
       */
      String parent = LocationName.format(projectNumber, location);

      // Define RequestMetadata object for context of the user making the API call
      RequestMetadata requestMetadata = RequestMetadata.newBuilder()
          .setUserInfo(
          UserInfo.newBuilder()
            .setId(userId)
            .build())
            .build();

      // Set file type for filter to 'DOCUMENT'
      FileType documentFileType = FileType.DOCUMENT;

      // Create a file type filter for documents 
      FileTypeFilter fileTypeFilter = FileTypeFilter.newBuilder()
          .setFileType(documentFileType)
          .build();

      // Create document query to search all documents for text given at input
      DocumentQuery query = DocumentQuery.newBuilder()
          .setQuery(documentQuery)
          .setFileTypeFilter(fileTypeFilter)
          .build();

      /*
       * Create the request to search all documents for specified query. 
       * Please note the offset in this request is to only return the specified number of results 
       * to avoid hitting the API quota. 
       */
      SearchDocumentsRequest searchDocumentsRequest = SearchDocumentsRequest.newBuilder()
          .setParent(parent)
          .setRequestMetadata(requestMetadata)
          .setOffset(5)
          .setDocumentQuery(query)
          .build();

      // Make the call to search documents with document service client and store the response
      SearchDocumentsPagedResponse searchDocumentsPagedResponse = 
          documentServiceClient.searchDocuments(searchDocumentsRequest);

      // Iterate through response and print search results for documents matching the search query
      for (MatchingDocument matchingDocument :
          searchDocumentsPagedResponse.iterateAll()) {
        System.out.println(
            "Display Name: " + matchingDocument.getDocument().getDisplayName()
            + "Document Name: " + matchingDocument.getDocument().getName()
            + "Document Creation Time: " + matchingDocument.getDocument().getCreateTime().toString()
            + "Search Text Snippet: " + matchingDocument.getSearchTextSnippet());
      }
    }
  }

  private static String getProjectNumber(String projectId) throws IOException { 
    /*
     * Initialize client that will be used to send requests. 
     * This client only needs to be created once, and can be reused for multiple requests.
     */
    try (ProjectsClient projectsClient = ProjectsClient.create()) { 
      ProjectName projectName = ProjectName.of(projectId); 
      Project project = projectsClient.getProject(projectName);
      String projectNumber = project.getName(); // Format returned is projects/xxxxxx
      return projectNumber.substring(projectNumber.lastIndexOf("/") + 1);
    } 
  }
}

Próximas etapas