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 créez vos documents, il est important de réfléchir aux propriétés que vous souhaitez définir et à la manière 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é représentant un "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 est le processus de récupération de tous les documents dont le texte pouvant faire l'objet d'une recherche 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'interface utilisateur. 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", "la", "les", "un", "une", "des", etc.) et racinise les mots restants. La racinisation réduit le mot à une version commune de la formulation, de sorte que les variations du mot correspondent. Par exemple : "travail", "travaille", "travaillé".

Sur quelles données porte la recherche ?

  • plain_text du document.
  • Si vous importez un objet Document AI, utilisez cloud_ai_document.text intégré.
  • display_name du document.
  • Toutes les propriétés pouvant faire l'objet d'une recherche.

La requête est partiellement compatible avec la syntaxe de style Google AIP. 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 (exemples : "42", "Hugo") est une valeur à faire correspondre. Elle effectue une recherche 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 : "engineer OR developer").
  • Opérateurs de négation : "NOT" et "!" sont des opérateurs de négation (exemple : "NOT software").
  • Opérateurs de comparaison : sont 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. Sont également compatibles avec l'opérateur "like" ~~ pour les chaînes. Il fournit une fonctionnalité de recherche sémantique en analysant, en racinisant et en développant les synonymes par rapport à la requête d'entrée.

    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 des littéraux. Par exemple : \"projects/123/locations/us\".property_a < 1 correspond aux résultats dont property_a est inférieur à 1 dans le projet 123 et l'emplacement us. Les littéraux et l'expression de comparaison peuvent être connectés dans une seule requête (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 s'il n'y a pas d'opérateur entre les expressions.

La requête peut être utilisée avec d'autres filtres, par exemple time_filters et folder_name_filter. Ils sont connecté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 requête de recherche

Pour appeler le service de recherche, vous devez utiliser une requête 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 rempli au format suivant :

/projects/PROJECT_ID/locations/LOCATION

Réponse à une requête 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 destiné aux mots de la requête de recherche de l'utilisateur qui en fait la demande. En règle générale, ils proviennent du champ de recherche de l'interface utilisateur.

Filtres

Document AI Warehouse propose différents filtres.

Filtre de temps de document

Le filtre de temps de création et de mise à jour est exactement ce à quoi vous vous attendez : il recherche 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 de spécifier si la période spécifiée dans time_range correspond à l'heure de création du document ou à l'heure de sa dernière mise à jour.

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 de 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, l'utilisation de filtres de propriété dans le secteur juridique peut filtrer une propriété appelée COURT pour rechercher uniquement les documents d'un tribunal particulier.

Les filtres de propriété utilisent un objet PropertyFilter. Vous pouvez avoir 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. Ainsi, le champ documentSchemaName vous permet de spécifier 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 un document entièrement rempli. Il contient des données minimales pour afficher une liste de résultats de recherche à l'utilisateur qui en fait la demande. Si le document complet est souhaité (par exemple, si l'utilisateur a cliqué sur un résultat de recherche), il doit être récupéré via 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.

Un document correspondant se présente comme suit :

{
  "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 : pertinence décroissante, c'est-à-dire que les meilleures correspondances sont en haut.
  • upload_date desc : date de création du document dans l'ordre décroissant (le plus récent 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 (le plus récent en haut).
  • Update_date : date de la dernière mise à jour du document dans l'ordre croissant (le plus ancien en haut).

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 heure de mise à jour décroissante (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 spécifier la taille de la page et obtenir le nombre total de résultats à afficher à l'utilisateur (par exemple, "Affichage de 50 documents sur 300").

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

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

Un décalage est l'index dans la liste des documents renvoyables que vous souhaitez renvoyer. Par exemple, un décalage de 5 signifie que vous souhaitez le sixième document et les suivants. Vous devez probablement incrémenter 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 décalage suivant. Après avoir effectué votre première requête de recherche, vous recevez une réponse de recherche contenant le champ next_page_token. Si ce champ est vide, 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 interfaces utilisateur 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 a un impact sur les performances. Définissez cette valeur sur la requête de la première page, puis définissez-la 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 Python Document AI Warehouse.

Pour vous authentifier auprès de Document AI Warehouse, configurez le service 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 le service 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