Rechercher des documents

Avant de commencer

Pour ingérer des exemples de documents dans Document AI Warehouse, consultez le guide de démarrage rapide.

Lorsque vous définissez vos schémas de document et que vous créez vos documents, il est important de réfléchir aux propriétés que vous souhaitez définir et à la façon dont elles seront utilisées avec la recherche, le cas échéant.

Marquez une propriété comme filtrable si vous souhaitez l'utiliser pour inclure ou exclure une partie des documents pour une recherche. Par exemple, vous pouvez rendre une propriété "Fournisseur" filtrable, car vos utilisateurs souhaitent rechercher des factures d'un fournisseur spécifique.

Si vous souhaitez créer un histogramme (voir l'exemple plus loin dans cette rubrique) sur une propriété, celle-ci doit être filtrable.

Marquez une propriété comme pouvant faire l'objet d'une recherche si elle contient des données que vos utilisateurs souhaiteront interroger lors d'une recherche par mot clé.

La recherche en texte intégral consiste à récupérer tous les documents dont le texte interrogeable correspond aux mots clés de la recherche. L'utilisateur fournit une liste de mots clés (mots séparés par un espace), probablement saisis dans un champ de recherche de l'UI. Dans Document AI Warehouse, les mots clés sont traités et convertis en requête appropriée. Ce traitement supprime les mots vides ("le", "dans", "un", etc.) et réduit les mots restants à leur forme de base. La racinisation réduit le mot à une version courante de la formulation, de sorte que les variantes du mot correspondent. Par exemple : "travail", "travaillez", "travaillé".

Quelles données sont recherchées ?

  • Le plain_text du document.
  • Si vous importez un objet Document AI, utilisez le cloud_ai_document.text intégré.
  • Nom à afficher du document.
  • Toutes les propriétés sur lesquelles il est possible d'effectuer une recherche.

La requête est partiellement compatible avec la syntaxe de style AIP Google. Plus précisément, la requête est compatible avec les littéraux, les opérateurs logiques, les opérateurs de négation, les opérateurs de comparaison et les fonctions.

  • Littéraux : une valeur littérale brute (par exemple, "42", "Hugo") est une valeur à comparer. Elle effectue des recherches dans le texte intégral du document et dans les propriétés pouvant faire l'objet d'une recherche.
  • Opérateurs logiques : "AND", "and", "OR" et "or" sont des opérateurs logiques binaires (exemple : "ingénieur OR développeur").
  • Opérateurs de négation : "NOT" et "!" sont des opérateurs de négation (exemple : "NOT logiciel").
  • Opérateurs de comparaison : compatibles avec les opérateurs de comparaison binaires =, !=, <, >, <= et >= pour les chaînes, les valeurs numériques, les énumérations et les valeurs booléennes. Prise en charge de l'opérateur "like" ~~ pour les chaînes. Il fournit une fonctionnalité de recherche sémantique en analysant, en effectuant une expansion des synonymes et en appliquant des racines aux mots de la requête saisie.

    Pour spécifier une propriété dans la requête, l'expression de gauche dans la comparaison doit être l'ID de propriété, y compris le parent. Le côté droit doit être constitué de littéraux. Par exemple, \"projects/123/locations/us\".property_a < 1 correspond aux résultats dont le property_a est inférieur à 1 dans le projet 123 et l'emplacement us. Les littéraux et l'expression de comparaison peuvent être associés dans une même requête (par exemple, software engineer \"projects/123/locations/us\".salary > 100).

  • Fonctions : les fonctions compatibles sont LOWER([property_name]), qui permet d'effectuer une correspondance sans distinction de casse, et EMPTY([property_name]), qui permet de filtrer en fonction de l'existence d'une clé.

  • Accepte les expressions imbriquées connectées à l'aide de parenthèses et d'opérateurs logiques. L'opérateur logique par défaut est AND si aucun opérateur n'est spécifié entre les expressions.

La requête peut être utilisée avec d'autres filtres, par exemple time_filters et folder_name_filter. Ils sont associés à l'opérateur AND en arrière-plan.

Les requêtes de recherche peuvent être filtrées par des paramètres supplémentaires tels que property, time, schema, folder et creator.

Appel à une demande de recherche

Pour appeler le service de recherche, vous devez utiliser une demande de recherche, qui est définie comme suit :

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

Le champ parent doit être renseigné au format suivant :

/projects/PROJECT_ID/locations/LOCATION

Réponse à une demande de recherche

La réponse de recherche est définie comme suit :

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

Requête de document

Le champ document_query est défini comme suit :

{
  "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)
  }
}

Le champ query est réservé aux mots de la requête de recherche de l'utilisateur. Elles proviennent généralement du champ de recherche de l'interface utilisateur.

Filtres

Document AI Warehouse propose différents filtres.

Filtre de période des documents

Le filtre de période de création et de modification fonctionne comme vous l'attendez : il trouve les documents correspondant aux mots clés dans une période spécifiée.

Un objet TimeFilter est utilisé pour spécifier la période. Il est défini comme suit :

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

Le champ time_field vous permet d'indiquer si la période spécifiée dans time_range correspond à la date de création ou de dernière modification du document.

Le champ time_range spécifie la période sous la forme d'un Interval. Un Interval est défini comme suit :

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

Filtre de créateur

Pour rechercher des documents créés par un ou plusieurs utilisateurs spécifiques, utilisez le filtre "Créateur". Exemple :

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

Filtre de propriété

Le filtre de propriété vous permet de spécifier des filtres sur l'une des propriétés que vous avez spécifiées dans un schéma, à condition que cette propriété ait été configurée pour être filtrable.

Par exemple, dans le secteur juridique, vous pouvez utiliser des filtres de propriété pour filtrer une propriété appelée COURT afin de rechercher uniquement les documents d'un tribunal spécifique.

Les filtres de propriété utilisent un objet PropertyFilter. Vous pouvez définir plusieurs filtres de propriété. Lorsque vous utilisez plusieurs filtres de propriété, ils sont combinés à l'aide de l'opérateur OR. Un filtre de propriété est défini comme suit :

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

Les propriétés sont définies dans des schémas. Le champ documentSchemaName est donc l'endroit où vous spécifiez le schéma de la propriété que vous utilisez pour le filtrage. Dans le champ condition, vous spécifiez la logique souhaitée. Pour obtenir des exemples d'utilisation des champs documentSchemaName et condition, consultez les exemples précédents sur cette page.

Document correspondant

Un document correspondant contient un Document et un extrait (abordés plus loin). Le document renvoyé dans MatchingDocument n'est pas entièrement rempli. Il contient un minimum de données pour afficher une liste de résultats de recherche à l'utilisateur qui en fait la demande. Si l'utilisateur souhaite obtenir le document complet (par exemple, s'il a cliqué sur un résultat de recherche), il doit le récupérer à l'aide de l'API GetDocument.

Les champs Document suivants sont renseignés : Project number, Document id, Document schema id, Create time, Update time, Display name, Raw document file type, Reference id et Filterable properties.

Voici à quoi ressemblerait un document correspondant :

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

Classement/Tri

La requête de recherche vous permet de spécifier le mode de tri des résultats. Pour effectuer un tri, utilisez le champ order_by dans la requête de recherche. Les valeurs possibles pour ce champ sont les suivantes :

  • relevance desc : par pertinence décroissante (les meilleures correspondances sont en haut).
  • upload_date desc : date de création du document dans l'ordre décroissant (les plus récents en haut).
  • upload_date : date de création du document dans l'ordre croissant (le plus ancien en haut).
  • update_date desc : date de la dernière mise à jour du document, dans l'ordre décroissant (les plus récents en haut).
  • Update_date : date de la dernière mise à jour du document, par ordre croissant (du plus ancien au plus récent).

Si vous ne spécifiez pas de tri, mais que vous fournissez des mots clés de recherche, le tri est effectué par pertinence décroissante (les meilleures correspondances en haut). Si ni le tri ni les mots clés ne sont fournis, le tri par défaut est effectué par ordre décroissant de la date de mise à jour (les documents les plus récents en haut).

Pagination

La pagination est utile pour afficher une page de données à l'utilisateur final. Vous pouvez y spécifier la taille de la page et obtenir le nombre total de résultats à afficher à l'utilisateur (par exemple, "50 documents sur 300").

Définissez le champ page_size sur le nombre de résultats que vous souhaitez recevoir avec la requête de recherche. Cela peut correspondre aux exigences concernant la taille d'affichage des résultats de recherche dans l'UI.

Il existe deux mécanismes : le décalage et le jeton de page.

Un décalage correspond à l'index dans la liste des documents pouvant être renvoyés que vous souhaitez renvoyer. Par exemple, un décalage de 5 signifie que vous souhaitez obtenir les documents à partir du sixième. Vous incrémenterez probablement le décalage de la taille de la page pour la page de résultats suivante.

Vous pouvez également utiliser un jeton de page et ne pas avoir à vous soucier du calcul du prochain décalage. Après avoir effectué votre première requête de recherche, vous obtenez une réponse de recherche contenant le champ next_page_token. Si ce champ est vide, cela signifie qu'il n'y a plus de résultats. Si le champ n'est pas vide, utilisez ce jeton dans votre prochaine requête de recherche en définissant le champ page_token.

Certaines UI affichent le nombre de documents trouvés par la recherche. Par exemple, you are viewing 10 documents of 120. Pour obtenir le nombre de documents renvoyés, définissez le champ require_total_size boolean de la requête sur True. Conseil : require_total_size=True entraîne une pénalité de performances. Définissez-le sur la requête de la première page, puis définissez-le sur false pour toutes les requêtes suivantes, en conservant le nombre total dans une variable locale.

Exemples de code

Python

Pour en savoir plus, consultez la documentation de référence de l'API Document AI Warehouse Python.

Pour vous authentifier auprès de Document AI Warehouse, configurez les Identifiants par défaut de l'application. Pour en savoir plus, consultez Configurer l'authentification pour un environnement de développement 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

Pour en savoir plus, consultez la documentation de référence de l'API Document AI Warehouse Java.

Pour vous authentifier auprès de Document AI Warehouse, configurez les Identifiants par défaut de l'application. Pour en savoir plus, consultez Configurer l'authentification pour un environnement de développement 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);
    } 
  }
}

Étapes suivantes