搜索文档

前期准备

如需将示例文档注入到 Document AI Warehouse 中,请参阅快速入门指南

在定义文档架构和创建文档时,请务必考虑要定义哪些属性,以及这些属性是否会用于搜索,如果会,又将如何用于搜索。

如果您想使用某个属性来包含或排除搜索中的部分文档,请将该属性标记为可过滤。例如,您可能会将表示“供应商”的属性设为可过滤,因为用户希望搜索特定供应商的账单。

如果您想针对某个媒体资源构建直方图(请参阅本主题后面的示例),则该媒体资源需要可过滤。

如果属性包含用户在关键字搜索期间想要查询的数据,请将其标记为可搜索。

全文搜索是指检索所有可搜索文本中包含搜索关键字的文档。用户提供一个关键字列表(字词之间以空格分隔),这些关键字可能是在界面中的搜索字段中输入的。在 Document AI Warehouse 中,系统会处理关键字并将其转换为合适的查询。此类处理会去除停用字(“the”“in”和“an”),并对剩余的字词进行词干提取。词干提取会将单词缩减为措辞的常见版本,以便匹配单词变体。例如:“work”“working”“worked”。

搜索范围内的数据

  • 相应文档的 plain_text
  • 如果您要导入 Document AI 对象,请使用嵌入式 cloud_ai_document.text
  • 相应文档的 display_name。
  • 所有可搜索的属性。

该查询部分支持 Google AIP 样式语法。具体而言,该查询支持字面量、逻辑运算符、求反运算符、比较运算符和函数。

  • 字面值:裸字面值(例如:“42”“Hugo”)是要匹配的值。它会搜索文档的全文和可搜索的属性。
  • 逻辑运算符:“AND”“and”“OR”和“or”是二元逻辑运算符(例如:“engineer OR developer”)。
  • 求反运算符:“NOT”和“!”是求反运算符(例如:“NOT software”)。
  • 比较运算符:支持用于字符串、数值、枚举、布尔值的二元比较运算符 =!=<><=>=。还支持字符串的 like 运算符 ~~。它通过对输入查询进行解析、词干提取和同义词扩展来提供语义搜索功能。

    如需在查询中指定媒体资源,比较中的左侧表达式必须是包含父级的媒体资源 ID。右侧必须是字面量。例如:\"projects/123/locations/us\".property_a < 1 会匹配项目 123 和位置 usproperty_a 小于 1 的结果。字面值和比较表达式可以在单个查询中连接起来(例如:software engineer \"projects/123/locations/us\".salary > 100)。

  • 函数:支持的函数有 LOWER([property_name])EMPTY([property_name]),前者用于执行不区分大小写的匹配,后者用于过滤是否存在键。

  • 支持使用括号和逻辑运算符连接的嵌套表达式。如果表达式之间没有运算符,则默认逻辑运算符为 AND

该查询可与其他过滤条件(例如 time_filtersfolder_name_filter)搭配使用。它们在后台通过 AND 运算符连接。

搜索查询可以按其他参数进行过滤,例如 propertytimeschemafoldercreator

对搜索请求的调用

如需调用搜索服务,您必须使用搜索请求,其定义如下:

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

parent 字段必须按以下格式填写:

/projects/PROJECT_ID/locations/LOCATION

对搜索请求的响应

搜索响应的定义如下:

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

文档查询

document_query 字段的定义如下:

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

query 字段用于请求用户的搜索查询字词。这些信息通常来自界面中的搜索字段。

过滤条件

Document AI Warehouse 提供各种过滤器。

文档时间过滤条件

创建和更新时间过滤条件的作用与您预期的一致:它会查找在指定时间段内与关键字匹配的文档。

TimeFilter 对象用于指定时间范围,其定义如下:

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

您可以在 time_field 字段中指定 time_range 中指定的时间范围是针对文档的创建时间还是文档的上次更新时间。

time_range 字段将时间范围指定为 IntervalInterval 的定义如下:

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

创作者过滤条件

如需搜索由特定用户创建的文档,请使用“创建者”过滤条件。例如:

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

媒体资源过滤条件

借助属性过滤条件,您可以针对架构中指定的任何属性指定过滤条件,前提是该属性已配置为可过滤。

例如,在法律行业中使用属性过滤条件时,可以按名为 COURT 的属性进行过滤,以仅搜索特定法院的文档。

媒体资源过滤条件使用 PropertyFilter 对象。您可以设置多个媒体资源过滤条件。如果您使用多个属性过滤条件,系统会使用 OR 运算符将它们组合在一起。属性过滤条件的定义如下:

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

属性是在架构中定义的。因此,您可以在 documentSchemaName 字段中指定用于过滤的属性的架构。在 condition 字段中,您可以指定所需的逻辑。如需查看使用 documentSchemaNamecondition 字段的示例,请参阅本页中的前述示例。

匹配的文档

匹配的文档包含 Document 和摘要(稍后会讨论)。MatchingDocument 中返回的文档不是完全填写完毕的文档。它包含用于向请求用户显示搜索结果列表的最少数据。如果需要完整文档(例如,用户点击了搜索结果),则应通过 GetDocument API 检索完整文档。

以下 Document 字段已填写:Project numberDocument idDocument schema idCreate timeUpdate timeDisplay nameRaw document file typeReference idFilterable properties

匹配的文档如下所示:

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

排名/排序

通过搜索请求,您可以指定结果的排序方式。如需排序,请在搜索请求中使用 order_by 字段。此字段的可能值包括:

  • relevance desc - 相关性降序,即最佳匹配项位于顶部。
  • upload_date desc - 文档的创建日期(降序排列,最新文档位于顶部)。
  • upload_date - 文档的创建日期(升序排序,最旧的在最上方)。
  • update_date desc - 文档的上次更新日期(按降序排列,最新日期在最上方)。
  • Update_date - 文档上次更新的日期(按升序排列,最旧的在最上方)。

如果您未指定排序方式,但提供了搜索关键字,则按相关性降序排序(最匹配的结果位于顶部)。如果未提供排序依据和关键字,则默认按更新时间降序排序(最新文档位于顶部)。

分页

分页功能可用于向最终用户显示一页数据。您可以在此处指定网页的大小,并获取要向用户显示的结果大小总数(例如“正在显示 300 个文档中的 50 个”)。

page_size 字段设置为您希望通过搜索请求接收的结果数量。这可能符合界面搜索结果显示大小的要求。

有两种机制:偏移量和网页令牌。

偏移量是指您希望返回的可返回文档列表中的索引。例如,偏移量为 5 表示您想要从第六个文档开始。对于下一页结果,您可能会将偏移量增加页面大小。

或者,您也可以使用页面令牌,这样就不必担心计算下一个偏移量。发出第一个搜索请求后,您会收到包含 next_page_token 字段的搜索响应。如果此字段为空,则表示没有更多结果。如果该字段不为空,请在下一个搜索请求中设置 page_token 字段,以使用此令牌。

某些界面会显示搜索找到的文档数量。例如 you are viewing 10 documents of 120。如需获取返回的文档数量,请将请求的 require_total_size boolean 字段设置为 True。提示:require_total_size=True 会带来性能损失。在第一个网页查询中设置此值,然后在所有后续请求中将其设置为 false,并将总数保存在局部变量中。

代码示例

Python

如需了解详情,请参阅 Document AI Warehouse Python API 参考文档

如需向 Document AI Warehouse 进行身份验证,请设置应用默认凭据。 如需了解详情,请参阅为本地开发环境设置身份验证


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

如需了解详情,请参阅 Document AI Warehouse Java API 参考文档

如需向 Document AI Warehouse 进行身份验证,请设置应用默认凭据。 如需了解详情,请参阅为本地开发环境设置身份验证

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);
    } 
  }
}

后续步骤