Filter and order results

This page describes filtering and ordering with search.

Semantic filtering optimizes non-default sorting

Vertex AI Search for retail is designed to show a wide variety of results, including marginally relevant items that are popular or trending. This is effective in default searches (sorted by relevance), but in non-default searches that use the order_by feature, such as sort-by-price, less relevant items can appear more prominently due to lower price, for example.

To ameliorate this effect, Vertex AI Search for retail has introduced semantic embedding-based filtering to remove these less relevant items from non-default search results, improving user experience.

This intended behavior could, however, lead fewer search results overall. Disabling filtering can impact search-result quality. So if fewer search results is of concern, contact support.

Filtering tutorial

This tutorial demonstrates the filtering feature. It lets you fine-tune search requests according to your or your customer's needs. You can filter by single or multiple fields, filter by text or numeric fields, or both. You can use an expression language to construct a predicate for each field or combine different expressions using logical operators. For example, a shopper looking for shoes can use filters to narrow their search to their preferred brand and color.


To follow step-by-step guidance for this task directly in the Cloud Shell Editor, click Guide me:

Guide me


Ordering tutorial

This tutorial shows how to order items in a search response. When an end user looks for a product on your site, they are shown results ordered by multiple fields. For example, a user is searching for a dress with the best price and discount. Price and discount are multiple fields here. The user will see the dresses ordered by price, and for the dresses with the same price, ordered by discount.


To follow step-by-step guidance for this task directly in the Cloud Shell Editor, click Guide me:

Guide me


Example dataset

This page uses the following dataset as an example. Only the fields necessary for the example are included.

Filter

Java

import com.google.cloud.retail.v2.SearchRequest;
import com.google.cloud.retail.v2.SearchResponse;
import com.google.cloud.retail.v2.SearchServiceClient;

public static void searchFilteredProducts(String query, int pageSize,
    String filter) throws IOException, InterruptedException {
  SearchRequest searchRequest = SearchRequest.newBuilder()
      .setPlacement(DEFAULT_SEARCH_PLACEMENT_NAME)
      .setBranch(DEFAULT_BRANCH_NAME)
      .setVisitorId(VISITOR_ID)
      .setQuery(query)
      .setPageSize(pageSize)
      .setFilter(filter)
      .build();

  try (SearchServiceClient searchClient = SearchServiceClient.create()) {
    SearchResponse response = searchClient.search(searchRequest).getPage().getResponse();
    System.out.println("Search response: " + searchResponse);
  }
}

The filter expression syntax can be summarized by the following EBNF:

  # A single expression or multiple expressions that are joined by "AND" or "OR".
  filter = expression, { " AND " | "OR", expression };

  # Expressions can be prefixed with "-" or "NOT" to express a negation.
  expression = [ "-" | "NOT " ],
    # A parenthetical expression.
    | "(", expression, ")"
    # A simple expression applying to a text field.
    # Function "ANY" returns true if the field contains any of the literals.
    ( text_field, ":", "ANY", "(", literal, { ",", literal }, ")"
    # A simple expression applying to a numerical field. Function "IN" returns true
    # if a field value is within the range. By default, lower_bound is inclusive and
    # upper_bound is exclusive.
    | numerical_field, ":", "IN", "(", lower_bound, ",", upper_bound, ")"
    # A simple expression that applies to a numerical field and compares with a double value.
    | numerical_field, comparison, double );

  # A lower_bound is either a double or "*", which represents negative infinity.
  # Explicitly specify inclusive bound with the character 'i' or exclusive bound
  # with the character 'e'.
  lower_bound = ( double, [ "e" | "i" ] ) | "*";

  # An upper_bound is either a double or "*", which represents infinity.
  # Explicitly specify inclusive bound with the character 'i' or exclusive bound
  # with the character 'e'.
  upper_bound = ( double, [ "e" | "i" ] ) | "*";

  # Supported comparison operators.
  comparison = "<=" | "<" | ">=" | ">" | "=";

  # A literal is any double quoted string. You must escape backslash (\) and
  # quote (") characters.
  literal = double quoted string;

  text_field = see the table below;

  numerical_field = see the table below;

The supported textual fields are summarized in the following table.

field description
"productId" The product ID (the last segment of Product.name).
"brands" The Product.brands.
"categories" The Product.categories.
"genders" The Audience.genders.
"ageGroups" The Audience.age_groups.
"availability" The Product.availability. Value is one of "IN_STOCK", "OUT_OF_STOCK", PREORDER", "BACKORDER".
"colorFamilies" The ColorInfo.color_families.
"colors" The ColorInfo.colors.
"sizes" The Product.sizes.
"materials" The Product.materials.
"patterns" The Product.patterns.
"conditions" The Product.conditions.
"attributes.key" The textual custom attribute in Product object. Key can be any key in the Product.attributes map, if the attribute values are textual.
"pickupInStore" The FulfillmentInfo.place_ids for type "pickup-in-store".
"shipToStore" The FulfillmentInfo.place_ids for type "ship-to-store".
"sameDayDelivery" The FulfillmentInfo.place_ids for type "same-day-delivery".
"nextDayDelivery" The FulfillmentInfo.place_ids for type "next-day-delivery".
"customFulfillment1" The FulfillmentInfo.place_ids for type "custom-type-1".
"customFulfillment2" The FulfillmentInfo.place_ids for type "custom-type-2".
"customFulfillment3" The FulfillmentInfo.place_ids for type "custom-type-3".
"customFulfillment4" The FulfillmentInfo.place_ids for type "custom-type-4".
"customFulfillment5" The FulfillmentInfo.place_ids for type "custom-type-5".
"inventory(place_id,attributes.key)" The textual custom attribute in Inventory.

The supported numerical fields are summarized in the following table.

field description
"price" The PriceInfo.price.
"discount" The discount. Computed by (original_price - price) / original_price.
"rating" The Rating.average_rating.
"ratingCount" The Rating.rating_count.
"attributes.key" The numerical custom attribute in Product object. Key can be any key in the Product.attributes map, if the attribute values are numerical.
"inventory(place_id,price)" The inventory price.
"inventory(place_id,original_price)" The original inventory price.
"inventory(place_id,attributes.key)" The numerical custom attribute in Inventory.

At most, 10 nested conjunctions or disjunctions are allowed.

For example, to search for a Google product in the following situations respectively, you could set query as "Google" and set filter as the values shown in the following table:

scenario filter
not a Pixel accessory "NOT categories: ANY(\"Pixel > featured accessories\")"
"cheaper than 100 dollars" "price: IN(*, 100.0e)"
"Nest speaker not cheaper than 80 dollars" "(categories: ANY(\"Nest > speakers and displays\")) AND (price: IN(80.0i, *))"

Order

Java

import com.google.cloud.retail.v2.SearchRequest;
import com.google.cloud.retail.v2.SearchResponse;
import com.google.cloud.retail.v2.SearchServiceClient;

public static void searchOrderedProducts(String query, int pageSize,
    String orderBy) throws IOException, InterruptedException {
  SearchRequest searchRequest = SearchRequest.newBuilder()
      .setPlacement(DEFAULT_SEARCH_PLACEMENT_NAME)
      .setBranch(DEFAULT_BRANCH_NAME)
      .setVisitorId(VISITOR_ID)
      .setQuery(query)
      .setPageSize(pageSize)
      .setOrderBy(orderBy)
      .build();

  try (SearchServiceClient searchClient = SearchServiceClient.create()) {
    SearchResponse response = searchClient.search(searchRequest).getPage().getResponse();
    System.out.println("Search response: " + searchResponse);
  }
}

The supported orderable fields are summarized in the following table.

field description
"productId" The product ID (the last segment of Product.name).
"title" The Product.title.
"brands" The Product.brands.
"categories" The Product.categories.
"genders" The Audience.genders.
"ageGroups" The Audience.age_groups.
"price" The PriceInfo.price.
"discount" The discount. Computed by (original_price - price) / price.
"rating" The Rating.average_rating.
"ratingCount" The Rating.rating_count.
"attributes.key" The custom attribute in Product object. Key can be any key in the Product.attributes map.
"inventory(place_id,price)" The inventory price.
"inventory(place_id,original_price)" The original inventory price.
"inventory(place_id,attributes.key)" The numerical or textual custom attribute in Inventory.

By default, the order is ascending. Descending order can be specified by "desc" suffix, such as, "rating desc".

For a numeric field with multiple values, for example, a repeated field or a field that is set for variant products, the minimum value is used for sorting by ascending order and the maximum value is used for sorting by descending order.

Ordering by multiple fields is supported through the use of comma-separated fields in order of priority. The lower priority fields are used to order items with equal values for higher priority fields. For example, "rating desc, price" orders items with the same rating by price.