// Copyright 2025 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

syntax = "proto3";

package google.cloud.retail.v2;

import "google/api/annotations.proto";
import "google/api/client.proto";
import "google/api/field_behavior.proto";
import "google/api/resource.proto";
import "google/cloud/retail/v2/common.proto";
import "google/cloud/retail/v2/product.proto";
import "google/protobuf/field_mask.proto";
import "google/protobuf/struct.proto";

option csharp_namespace = "Google.Cloud.Retail.V2";
option go_package = "cloud.google.com/go/retail/apiv2/retailpb;retailpb";
option java_multiple_files = true;
option java_outer_classname = "SearchServiceProto";
option java_package = "com.google.cloud.retail.v2";
option objc_class_prefix = "RETAIL";
option php_namespace = "Google\\Cloud\\Retail\\V2";
option ruby_package = "Google::Cloud::Retail::V2";
option (google.api.resource_definition) = {
  type: "retail.googleapis.com/Experiment"
  pattern: "projects/{project}/locations/{location}/catalogs/{catalog}/experiments/{experiment}"
};

// Service for search.
//
// This feature is only available for users who have Retail Search enabled.
// Enable Retail Search on Cloud Console before using this feature.
service SearchService {
  option (google.api.default_host) = "retail.googleapis.com";
  option (google.api.oauth_scopes) =
      "https://www.googleapis.com/auth/cloud-platform";

  // Performs a search.
  //
  // This feature is only available for users who have Retail Search enabled.
  // Enable Retail Search on Cloud Console before using this feature.
  rpc Search(SearchRequest) returns (SearchResponse) {
    option (google.api.http) = {
      post: "/v2/{placement=projects/*/locations/*/catalogs/*/placements/*}:search"
      body: "*"
      additional_bindings {
        post: "/v2/{placement=projects/*/locations/*/catalogs/*/servingConfigs/*}:search"
        body: "*"
      }
    };
  }
}

// Product attribute which structured by an attribute name and value. This
// structure is used in conversational search filters and answers. For example,
// if we have `name=color` and `value=red`, this means that the color is `red`.
message ProductAttributeValue {
  // The attribute name.
  string name = 1;

  // The attribute value.
  string value = 2;
}

// Product attribute name and numeric interval.
message ProductAttributeInterval {
  // The attribute name (e.g. "length")
  string name = 1;

  // The numeric interval (e.g. [10, 20))
  Interval interval = 2;
}

// This field specifies the tile information including an attribute key,
// attribute value. More fields will be added in the future, eg: product id
// or product counts, etc.
message Tile {
  // The attribute key and value for the tile.
  oneof product_attribute {
    // The product attribute key-value.
    ProductAttributeValue product_attribute_value = 1;

    // The product attribute key-numeric interval.
    ProductAttributeInterval product_attribute_interval = 2;
  }

  // The representative product id for this tile.
  string representative_product_id = 3;
}

// Request message for
// [SearchService.Search][google.cloud.retail.v2.SearchService.Search] method.
message SearchRequest {
  // A facet specification to perform faceted search.
  message FacetSpec {
    // Specifies how a facet is computed.
    message FacetKey {
      // Required. Supported textual and numerical facet keys in
      // [Product][google.cloud.retail.v2.Product] object, over which the facet
      // values are computed. Facet key is case-sensitive.
      //
      // Allowed facet keys when
      // [FacetKey.query][google.cloud.retail.v2.SearchRequest.FacetSpec.FacetKey.query]
      // is not specified:
      //
      // * textual_field =
      //     * "brands"
      //     * "categories"
      //     * "genders"
      //     * "ageGroups"
      //     * "availability"
      //     * "colorFamilies"
      //     * "colors"
      //     * "sizes"
      //     * "materials"
      //     * "patterns"
      //     * "conditions"
      //     * "attributes.key"
      //     * "pickupInStore"
      //     * "shipToStore"
      //     * "sameDayDelivery"
      //     * "nextDayDelivery"
      //     * "customFulfillment1"
      //     * "customFulfillment2"
      //     * "customFulfillment3"
      //     * "customFulfillment4"
      //     * "customFulfillment5"
      //     * "inventory(place_id,attributes.key)"
      //
      // * numerical_field =
      //     * "price"
      //     * "discount"
      //     * "rating"
      //     * "ratingCount"
      //     * "attributes.key"
      //     * "inventory(place_id,price)"
      //     * "inventory(place_id,original_price)"
      //     * "inventory(place_id,attributes.key)"
      string key = 1 [(google.api.field_behavior) = REQUIRED];

      // Set only if values should be bucketized into intervals. Must be set
      // for facets with numerical values. Must not be set for facet with text
      // values. Maximum number of intervals is 40.
      //
      // For all numerical facet keys that appear in the list of products from
      // the catalog, the percentiles 0, 10, 30, 50, 70, 90, and 100 are
      // computed from their distribution weekly. If the model assigns a high
      // score to a numerical facet key and its intervals are not specified in
      // the search request, these percentiles become the bounds
      // for its intervals and are returned in the response. If the
      // facet key intervals are specified in the request, then the specified
      // intervals are returned instead.
      repeated Interval intervals = 2;

      // Only get facet for the given restricted values. For example, when using
      // "pickupInStore" as key and set restricted values to
      // ["store123", "store456"], only facets for "store123" and "store456" are
      // returned. Only supported on predefined textual fields, custom textual
      // attributes and fulfillments. Maximum is 20.
      //
      // Must be set for the fulfillment facet keys:
      //
      // * pickupInStore
      //
      // * shipToStore
      //
      // * sameDayDelivery
      //
      // * nextDayDelivery
      //
      // * customFulfillment1
      //
      // * customFulfillment2
      //
      // * customFulfillment3
      //
      // * customFulfillment4
      //
      // * customFulfillment5
      repeated string restricted_values = 3;

      // Only get facet values that start with the given string prefix. For
      // example, suppose "categories" has three values "Women > Shoe",
      // "Women > Dress" and "Men > Shoe". If set "prefixes" to "Women", the
      // "categories" facet gives only "Women > Shoe" and "Women > Dress".
      // Only supported on textual fields. Maximum is 10.
      repeated string prefixes = 8;

      // Only get facet values that contains the given strings. For example,
      // suppose "categories" has three values "Women > Shoe",
      // "Women > Dress" and "Men > Shoe". If set "contains" to "Shoe", the
      // "categories" facet gives only "Women > Shoe" and "Men > Shoe".
      // Only supported on textual fields. Maximum is 10.
      repeated string contains = 9;

      // True to make facet keys case insensitive when getting faceting
      // values with prefixes or contains; false otherwise.
      bool case_insensitive = 10;

      // The order in which
      // [SearchResponse.Facet.values][google.cloud.retail.v2.SearchResponse.Facet.values]
      // are returned.
      //
      // Allowed values are:
      //
      // * "count desc", which means order by
      // [SearchResponse.Facet.values.count][google.cloud.retail.v2.SearchResponse.Facet.FacetValue.count]
      // descending.
      //
      // * "value desc", which means order by
      // [SearchResponse.Facet.values.value][google.cloud.retail.v2.SearchResponse.Facet.FacetValue.value]
      // descending.
      //   Only applies to textual facets.
      //
      // If not set, textual values are sorted in [natural
      // order](https://en.wikipedia.org/wiki/Natural_sort_order); numerical
      // intervals are sorted in the order given by
      // [FacetSpec.FacetKey.intervals][google.cloud.retail.v2.SearchRequest.FacetSpec.FacetKey.intervals];
      // [FulfillmentInfo.place_ids][google.cloud.retail.v2.FulfillmentInfo.place_ids]
      // are sorted in the order given by
      // [FacetSpec.FacetKey.restricted_values][google.cloud.retail.v2.SearchRequest.FacetSpec.FacetKey.restricted_values].
      string order_by = 4;

      // The query that is used to compute facet for the given facet key.
      // When provided, it overrides the default behavior of facet
      // computation. The query syntax is the same as a filter expression. See
      // [SearchRequest.filter][google.cloud.retail.v2.SearchRequest.filter] for
      // detail syntax and limitations. Notice that there is no limitation on
      // [FacetKey.key][google.cloud.retail.v2.SearchRequest.FacetSpec.FacetKey.key]
      // when query is specified.
      //
      // In the response,
      // [SearchResponse.Facet.values.value][google.cloud.retail.v2.SearchResponse.Facet.FacetValue.value]
      // is always "1" and
      // [SearchResponse.Facet.values.count][google.cloud.retail.v2.SearchResponse.Facet.FacetValue.count]
      // is the number of results that match the query.
      //
      // For example, you can set a customized facet for "shipToStore",
      // where
      // [FacetKey.key][google.cloud.retail.v2.SearchRequest.FacetSpec.FacetKey.key]
      // is "customizedShipToStore", and
      // [FacetKey.query][google.cloud.retail.v2.SearchRequest.FacetSpec.FacetKey.query]
      // is "availability: ANY(\"IN_STOCK\") AND shipToStore: ANY(\"123\")".
      // Then the facet counts the products that are both in stock and ship
      // to store "123".
      string query = 5;

      // Returns the min and max value for each numerical facet intervals.
      // Ignored for textual facets.
      bool return_min_max = 11;
    }

    // Required. The facet key specification.
    FacetKey facet_key = 1 [(google.api.field_behavior) = REQUIRED];

    // Maximum of facet values that should be returned for this facet. If
    // unspecified, defaults to 50. The maximum allowed value is 300. Values
    // above 300 will be coerced to 300.
    //
    // If this field is negative, an INVALID_ARGUMENT is returned.
    int32 limit = 2;

    // List of keys to exclude when faceting.
    //
    // By default,
    // [FacetKey.key][google.cloud.retail.v2.SearchRequest.FacetSpec.FacetKey.key]
    // is not excluded from the filter unless it is listed in this field.
    //
    // Listing a facet key in this field allows its values to appear as facet
    // results, even when they are filtered out of search results. Using this
    // field does not affect what search results are returned.
    //
    // For example, suppose there are 100 products with the color facet "Red"
    // and 200 products with the color facet "Blue". A query containing the
    // filter "colorFamilies:ANY("Red")" and having "colorFamilies" as
    // [FacetKey.key][google.cloud.retail.v2.SearchRequest.FacetSpec.FacetKey.key]
    // would by default return only "Red" products in the search results, and
    // also return "Red" with count 100 as the only color facet. Although there
    // are also blue products available, "Blue" would not be shown as an
    // available facet value.
    //
    // If "colorFamilies" is listed in "excludedFilterKeys", then the query
    // returns the facet values "Red" with count 100 and "Blue" with count
    // 200, because the "colorFamilies" key is now excluded from the filter.
    // Because this field doesn't affect search results, the search results
    // are still correctly filtered to return only "Red" products.
    //
    // A maximum of 100 values are allowed. Otherwise, an INVALID_ARGUMENT error
    // is returned.
    repeated string excluded_filter_keys = 3;

    // Enables dynamic position for this facet. If set to true, the position of
    // this facet among all facets in the response is determined by Google
    // Retail Search. It is ordered together with dynamic facets if dynamic
    // facets is enabled. If set to false, the position of this facet in the
    // response is the same as in the request, and it is ranked before
    // the facets with dynamic position enable and all dynamic facets.
    //
    // For example, you may always want to have rating facet returned in
    // the response, but it's not necessarily to always display the rating facet
    // at the top. In that case, you can set enable_dynamic_position to true so
    // that the position of rating facet in response is determined by
    // Google Retail Search.
    //
    // Another example, assuming you have the following facets in the request:
    //
    // * "rating", enable_dynamic_position = true
    //
    // * "price", enable_dynamic_position = false
    //
    // * "brands", enable_dynamic_position = false
    //
    // And also you have a dynamic facets enable, which generates a facet
    // "gender". Then, the final order of the facets in the response can be
    // ("price", "brands", "rating", "gender") or ("price", "brands", "gender",
    // "rating") depends on how Google Retail Search orders "gender" and
    // "rating" facets. However, notice that "price" and "brands" are always
    // ranked at first and second position because their enable_dynamic_position
    // values are false.
    bool enable_dynamic_position = 4;
  }

  // The specifications of dynamically generated facets.
  message DynamicFacetSpec {
    // Enum to control DynamicFacet mode
    enum Mode {
      // Default value.
      MODE_UNSPECIFIED = 0;

      // Disable Dynamic Facet.
      DISABLED = 1;

      // Automatic mode built by Google Retail Search.
      ENABLED = 2;
    }

    // Mode of the DynamicFacet feature.
    // Defaults to
    // [Mode.DISABLED][google.cloud.retail.v2.SearchRequest.DynamicFacetSpec.Mode.DISABLED]
    // if it's unset.
    Mode mode = 1;
  }

  // Boost specification to boost certain items.
  message BoostSpec {
    // Boost applies to products which match a condition.
    message ConditionBoostSpec {
      // An expression which specifies a boost condition. The syntax and
      // supported fields are the same as a filter expression. See
      // [SearchRequest.filter][google.cloud.retail.v2.SearchRequest.filter] for
      // detail syntax and limitations.
      //
      // Examples:
      //
      // * To boost products with product ID "product_1" or "product_2", and
      // color
      //   "Red" or "Blue":
      //     * (id: ANY("product_1", "product_2")) AND (colorFamilies:
      //     ANY("Red","Blue"))
      string condition = 1;

      // Strength of the condition boost, which should be in [-1, 1]. Negative
      // boost means demotion. Default is 0.0.
      //
      // Setting to 1.0 gives the item a big promotion. However, it does not
      // necessarily mean that the boosted item will be the top result at all
      // times, nor that other items will be excluded. Results could still be
      // shown even when none of them matches the condition. And results that
      // are significantly more relevant to the search query can still trump
      // your heavily favored but irrelevant items.
      //
      // Setting to -1.0 gives the item a big demotion. However, results that
      // are deeply relevant might still be shown. The item will have an
      // upstream battle to get a fairly high ranking, but it is not blocked out
      // completely.
      //
      // Setting to 0.0 means no boost applied. The boosting condition is
      // ignored.
      float boost = 2;
    }

    // Condition boost specifications. If a product matches multiple conditions
    // in the specifications, boost scores from these specifications are all
    // applied and combined in a non-linear way. Maximum number of
    // specifications is 20.
    repeated ConditionBoostSpec condition_boost_specs = 1;

    // Whether to skip boostspec validation. If this field is set to true,
    // invalid
    // [BoostSpec.condition_boost_specs][google.cloud.retail.v2.SearchRequest.BoostSpec.condition_boost_specs]
    // will be ignored and valid
    // [BoostSpec.condition_boost_specs][google.cloud.retail.v2.SearchRequest.BoostSpec.condition_boost_specs]
    // will still be applied.
    optional bool skip_boost_spec_validation = 2;
  }

  // Specification to determine under which conditions query expansion should
  // occur.
  message QueryExpansionSpec {
    // Enum describing under which condition query expansion should occur.
    enum Condition {
      // Unspecified query expansion condition. In this case, server behavior
      // defaults to
      // [Condition.DISABLED][google.cloud.retail.v2.SearchRequest.QueryExpansionSpec.Condition.DISABLED].
      CONDITION_UNSPECIFIED = 0;

      // Disabled query expansion. Only the exact search query is used, even if
      // [SearchResponse.total_size][google.cloud.retail.v2.SearchResponse.total_size]
      // is zero.
      DISABLED = 1;

      // Automatic query expansion built by Google Retail Search.
      AUTO = 3;
    }

    // The condition under which query expansion should occur. Default to
    // [Condition.DISABLED][google.cloud.retail.v2.SearchRequest.QueryExpansionSpec.Condition.DISABLED].
    Condition condition = 1;

    // Whether to pin unexpanded results. If this field is set to true,
    // unexpanded products are always at the top of the search results, followed
    // by the expanded results.
    bool pin_unexpanded_results = 2;
  }

  // The specification for personalization.
  message PersonalizationSpec {
    // The personalization mode of each search request.
    enum Mode {
      // Default value. In this case, server behavior defaults to
      // [Mode.AUTO][google.cloud.retail.v2.SearchRequest.PersonalizationSpec.Mode.AUTO].
      MODE_UNSPECIFIED = 0;

      // Let CRS decide whether to use personalization based on quality of user
      // event data.
      AUTO = 1;

      // Disable personalization.
      DISABLED = 2;
    }

    // Defaults to
    // [Mode.AUTO][google.cloud.retail.v2.SearchRequest.PersonalizationSpec.Mode.AUTO].
    Mode mode = 1;
  }

  // The specification for query spell correction.
  message SpellCorrectionSpec {
    // Enum describing under which mode spell correction should occur.
    enum Mode {
      // Unspecified spell correction mode. In this case, server behavior
      // defaults to
      // [Mode.AUTO][google.cloud.retail.v2.SearchRequest.SpellCorrectionSpec.Mode.AUTO].
      MODE_UNSPECIFIED = 0;

      // Google Retail Search will try to find a spell suggestion if there
      // is any and put in the
      // [SearchResponse.corrected_query][google.cloud.retail.v2.SearchResponse.corrected_query].
      // The spell suggestion will not be used as the search query.
      SUGGESTION_ONLY = 1;

      // Automatic spell correction built by Google Retail Search. Search will
      // be based on the corrected query if found.
      AUTO = 2;
    }

    // The mode under which spell correction should take effect to
    // replace the original search query. Default to
    // [Mode.AUTO][google.cloud.retail.v2.SearchRequest.SpellCorrectionSpec.Mode.AUTO].
    Mode mode = 1;
  }

  // This field specifies all conversational related parameters addition to
  // traditional retail search.
  message ConversationalSearchSpec {
    // This field specifies the current user answer during the conversational
    // search. This can be either user selected from suggested answers or user
    // input plain text.
    message UserAnswer {
      // This field specifies the selected answers during the conversational
      // search.
      message SelectedAnswer {
        // This field is deprecated and should not be set.
        repeated ProductAttributeValue product_attribute_values = 1
            [deprecated = true];

        // This field specifies the selected answer which is a attribute
        // key-value.
        ProductAttributeValue product_attribute_value = 2;
      }

      // This field specifies the type of user answer.
      oneof type {
        // This field specifies the incremental input text from the user during
        // the conversational search.
        string text_answer = 1;

        // This field specifies the selected attributes during the
        // conversational search. This should be a subset of
        // [ConversationalSearchResult.suggested_answers][].
        SelectedAnswer selected_answer = 2;
      }
    }

    // This field specifies whether the customer would like to do conversational
    // search. If this field is set to true, conversational related extra
    // information will be returned from server side, including follow-up
    // question, answer options, etc.
    bool followup_conversation_requested = 1;

    // This field specifies the conversation id, which maintains the state of
    // the conversation between client side and server side. Use the value from
    // the previous [ConversationalSearchResult.conversation_id][]. For the
    // initial request, this should be empty.
    string conversation_id = 2;

    // This field specifies the current user answer during the conversational
    // search. This can be either user selected from suggested answers or user
    // input plain text.
    UserAnswer user_answer = 3;
  }

  // This field specifies tile navigation related parameters.
  message TileNavigationSpec {
    // This field specifies whether the customer would like to request tile
    // navigation.
    bool tile_navigation_requested = 1;

    // This optional field specifies the tiles which are already clicked in
    // client side. While the feature works without this field set, particularly
    // for an initial query, it is highly recommended to set this field because
    // it can improve the quality of the search response and removes possible
    // duplicate tiles.
    //
    // NOTE: This field is not being used for filtering search
    // products. Client side should also put all the applied tiles in
    // [SearchRequest.filter][google.cloud.retail.v2.SearchRequest.filter].
    repeated Tile applied_tiles = 2;
  }

  // The search mode of each search request.
  enum SearchMode {
    // Default value. In this case both product search and faceted search will
    // be performed. Both
    // [SearchResponse.SearchResult][google.cloud.retail.v2.SearchResponse.SearchResult]
    // and [SearchResponse.Facet][google.cloud.retail.v2.SearchResponse.Facet]
    // will be returned.
    SEARCH_MODE_UNSPECIFIED = 0;

    // Only product search will be performed. The faceted search will be
    // disabled.
    //
    // Only
    // [SearchResponse.SearchResult][google.cloud.retail.v2.SearchResponse.SearchResult]
    // will be returned.
    // [SearchResponse.Facet][google.cloud.retail.v2.SearchResponse.Facet] will
    // not be returned, even if
    // [SearchRequest.facet_specs][google.cloud.retail.v2.SearchRequest.facet_specs]
    // or
    // [SearchRequest.dynamic_facet_spec][google.cloud.retail.v2.SearchRequest.dynamic_facet_spec]
    // is set.
    PRODUCT_SEARCH_ONLY = 1;

    // Only faceted search will be performed. The product search will be
    // disabled.
    //
    // When in this mode, one or both of
    // [SearchRequest.facet_specs][google.cloud.retail.v2.SearchRequest.facet_specs]
    // and
    // [SearchRequest.dynamic_facet_spec][google.cloud.retail.v2.SearchRequest.dynamic_facet_spec]
    // should be set. Otherwise, an INVALID_ARGUMENT error is returned. Only
    // [SearchResponse.Facet][google.cloud.retail.v2.SearchResponse.Facet] will
    // be returned.
    // [SearchResponse.SearchResult][google.cloud.retail.v2.SearchResponse.SearchResult]
    // will not be returned.
    FACETED_SEARCH_ONLY = 2;
  }

  // Required. The resource name of the Retail Search serving config, such as
  // `projects/*/locations/global/catalogs/default_catalog/servingConfigs/default_serving_config`
  // or the name of the legacy placement resource, such as
  // `projects/*/locations/global/catalogs/default_catalog/placements/default_search`.
  // This field is used to identify the serving config name and the set
  // of models that are used to make the search.
  string placement = 1 [(google.api.field_behavior) = REQUIRED];

  // The branch resource name, such as
  // `projects/*/locations/global/catalogs/default_catalog/branches/0`.
  //
  // Use "default_branch" as the branch ID or leave this field empty, to search
  // products under the default branch.
  string branch = 2 [
    (google.api.resource_reference) = { type: "retail.googleapis.com/Branch" }
  ];

  // Raw search query.
  //
  // If this field is empty, the request is considered a category browsing
  // request and returned results are based on
  // [filter][google.cloud.retail.v2.SearchRequest.filter] and
  // [page_categories][google.cloud.retail.v2.SearchRequest.page_categories].
  string query = 3;

  // Required. A unique identifier for tracking visitors. For example, this
  // could be implemented with an HTTP cookie, which should be able to uniquely
  // identify a visitor on a single device. This unique identifier should not
  // change if the visitor logs in or out of the website.
  //
  // This should be the same identifier as
  // [UserEvent.visitor_id][google.cloud.retail.v2.UserEvent.visitor_id].
  //
  // The field must be a UTF-8 encoded string with a length limit of 128
  // characters. Otherwise, an INVALID_ARGUMENT error is returned.
  string visitor_id = 4 [(google.api.field_behavior) = REQUIRED];

  // User information.
  UserInfo user_info = 5;

  // Maximum number of [Product][google.cloud.retail.v2.Product]s to return. If
  // unspecified, defaults to a reasonable value. The maximum allowed value is
  // 120. Values above 120 will be coerced to 120.
  //
  // If this field is negative, an INVALID_ARGUMENT is returned.
  int32 page_size = 7;

  // A page token
  // [SearchResponse.next_page_token][google.cloud.retail.v2.SearchResponse.next_page_token],
  // received from a previous
  // [SearchService.Search][google.cloud.retail.v2.SearchService.Search] call.
  // Provide this to retrieve the subsequent page.
  //
  // When paginating, all other parameters provided to
  // [SearchService.Search][google.cloud.retail.v2.SearchService.Search] must
  // match the call that provided the page token. Otherwise, an INVALID_ARGUMENT
  // error is returned.
  string page_token = 8;

  // A 0-indexed integer that specifies the current offset (that is, starting
  // result location, amongst the [Product][google.cloud.retail.v2.Product]s
  // deemed by the API as relevant) in search results. This field is only
  // considered if [page_token][google.cloud.retail.v2.SearchRequest.page_token]
  // is unset.
  //
  // If this field is negative, an INVALID_ARGUMENT is returned.
  int32 offset = 9;

  // The filter syntax consists of an expression language for constructing a
  // predicate from one or more fields of the products being filtered. Filter
  // expression is case-sensitive. For more information, see
  // [Filter](https://cloud.google.com/retail/docs/filter-and-order#filter).
  //
  // If this field is unrecognizable, an INVALID_ARGUMENT is returned.
  string filter = 10;

  // The default filter that is applied when a user performs a search without
  // checking any filters on the search page.
  //
  // The filter applied to every search request when quality improvement such as
  // query expansion is needed. In the case a query does not have a sufficient
  // amount of results this filter will be used to determine whether or not to
  // enable the query expansion flow. The original filter will still be used for
  // the query expanded search.
  // This field is strongly recommended to achieve high search quality.
  //
  // For more information about filter syntax, see
  // [SearchRequest.filter][google.cloud.retail.v2.SearchRequest.filter].
  string canonical_filter = 28;

  // The order in which products are returned. Products can be ordered by
  // a field in an [Product][google.cloud.retail.v2.Product] object. Leave it
  // unset if ordered by relevance. OrderBy expression is case-sensitive. For
  // more information, see
  // [Order](https://cloud.google.com/retail/docs/filter-and-order#order).
  //
  // If this field is unrecognizable, an INVALID_ARGUMENT is returned.
  string order_by = 11;

  // Facet specifications for faceted search. If empty, no facets are returned.
  //
  // A maximum of 200 values are allowed. Otherwise, an INVALID_ARGUMENT error
  // is returned.
  repeated FacetSpec facet_specs = 12;

  // Deprecated. Refer to https://cloud.google.com/retail/docs/configs#dynamic
  // to enable dynamic facets. Do not set this field.
  //
  // The specification for dynamically generated facets. Notice that only
  // textual facets can be dynamically generated.
  DynamicFacetSpec dynamic_facet_spec = 21 [deprecated = true];

  // Boost specification to boost certain products. For more information, see
  // [Boost results](https://cloud.google.com/retail/docs/boosting).
  //
  // Notice that if both
  // [ServingConfig.boost_control_ids][google.cloud.retail.v2.ServingConfig.boost_control_ids]
  // and
  // [SearchRequest.boost_spec][google.cloud.retail.v2.SearchRequest.boost_spec]
  // are set, the boost conditions from both places are evaluated. If a search
  // request matches multiple boost conditions, the final boost score is equal
  // to the sum of the boost scores from all matched boost conditions.
  BoostSpec boost_spec = 13;

  // The query expansion specification that specifies the conditions under which
  // query expansion occurs. For more information, see [Query
  // expansion](https://cloud.google.com/retail/docs/result-size#query_expansion).
  QueryExpansionSpec query_expansion_spec = 14;

  // The keys to fetch and rollup the matching
  // [variant][google.cloud.retail.v2.Product.Type.VARIANT]
  // [Product][google.cloud.retail.v2.Product]s attributes,
  // [FulfillmentInfo][google.cloud.retail.v2.FulfillmentInfo] or
  // [LocalInventory][google.cloud.retail.v2.LocalInventory]s attributes. The
  // attributes from all the matching
  // [variant][google.cloud.retail.v2.Product.Type.VARIANT]
  // [Product][google.cloud.retail.v2.Product]s or
  // [LocalInventory][google.cloud.retail.v2.LocalInventory]s are merged and
  // de-duplicated. Notice that rollup attributes will lead to extra query
  // latency. Maximum number of keys is 30.
  //
  // For [FulfillmentInfo][google.cloud.retail.v2.FulfillmentInfo], a
  // fulfillment type and a fulfillment ID must be provided in the format of
  // "fulfillmentType.fulfillmentId". E.g., in "pickupInStore.store123",
  // "pickupInStore" is fulfillment type and "store123" is the store ID.
  //
  // Supported keys are:
  //
  // * colorFamilies
  // * price
  // * originalPrice
  // * discount
  // * variantId
  // * inventory(place_id,price)
  // * inventory(place_id,original_price)
  // * inventory(place_id,attributes.key), where key is any key in the
  //   [Product.local_inventories.attributes][google.cloud.retail.v2.LocalInventory.attributes]
  //   map.
  // * attributes.key, where key is any key in the
  //   [Product.attributes][google.cloud.retail.v2.Product.attributes] map.
  // * pickupInStore.id, where id is any
  // [FulfillmentInfo.place_ids][google.cloud.retail.v2.FulfillmentInfo.place_ids]
  // for [FulfillmentInfo.type][google.cloud.retail.v2.FulfillmentInfo.type]
  //   "pickup-in-store".
  // * shipToStore.id, where id is any
  // [FulfillmentInfo.place_ids][google.cloud.retail.v2.FulfillmentInfo.place_ids]
  // for [FulfillmentInfo.type][google.cloud.retail.v2.FulfillmentInfo.type]
  //   "ship-to-store".
  // * sameDayDelivery.id, where id is any
  // [FulfillmentInfo.place_ids][google.cloud.retail.v2.FulfillmentInfo.place_ids]
  // for [FulfillmentInfo.type][google.cloud.retail.v2.FulfillmentInfo.type]
  //   "same-day-delivery".
  // * nextDayDelivery.id, where id is any
  // [FulfillmentInfo.place_ids][google.cloud.retail.v2.FulfillmentInfo.place_ids]
  // for [FulfillmentInfo.type][google.cloud.retail.v2.FulfillmentInfo.type]
  //   "next-day-delivery".
  // * customFulfillment1.id, where id is any
  // [FulfillmentInfo.place_ids][google.cloud.retail.v2.FulfillmentInfo.place_ids]
  // for [FulfillmentInfo.type][google.cloud.retail.v2.FulfillmentInfo.type]
  //   "custom-type-1".
  // * customFulfillment2.id, where id is any
  // [FulfillmentInfo.place_ids][google.cloud.retail.v2.FulfillmentInfo.place_ids]
  // for [FulfillmentInfo.type][google.cloud.retail.v2.FulfillmentInfo.type]
  //   "custom-type-2".
  // * customFulfillment3.id, where id is any
  // [FulfillmentInfo.place_ids][google.cloud.retail.v2.FulfillmentInfo.place_ids]
  // for [FulfillmentInfo.type][google.cloud.retail.v2.FulfillmentInfo.type]
  //   "custom-type-3".
  // * customFulfillment4.id, where id is any
  // [FulfillmentInfo.place_ids][google.cloud.retail.v2.FulfillmentInfo.place_ids]
  // for [FulfillmentInfo.type][google.cloud.retail.v2.FulfillmentInfo.type]
  //   "custom-type-4".
  // * customFulfillment5.id, where id is any
  // [FulfillmentInfo.place_ids][google.cloud.retail.v2.FulfillmentInfo.place_ids]
  // for [FulfillmentInfo.type][google.cloud.retail.v2.FulfillmentInfo.type]
  //   "custom-type-5".
  //
  // If this field is set to an invalid value other than these, an
  // INVALID_ARGUMENT error is returned.
  repeated string variant_rollup_keys = 17;

  // The categories associated with a category page. Must be set for category
  // navigation queries to achieve good search quality. The format should be
  // the same as
  // [UserEvent.page_categories][google.cloud.retail.v2.UserEvent.page_categories];
  //
  // To represent full path of category, use '>' sign to separate different
  // hierarchies. If '>' is part of the category name, replace it with
  // other character(s).
  //
  // Category pages include special pages such as sales or promotions. For
  // instance, a special sale page may have the category hierarchy:
  // "pageCategories" : ["Sales > 2017 Black Friday Deals"].
  repeated string page_categories = 23;

  // The search mode of the search request. If not specified, a single search
  // request triggers both product search and faceted search.
  SearchMode search_mode = 31;

  // The specification for personalization.
  //
  // Notice that if both
  // [ServingConfig.personalization_spec][google.cloud.retail.v2.ServingConfig.personalization_spec]
  // and
  // [SearchRequest.personalization_spec][google.cloud.retail.v2.SearchRequest.personalization_spec]
  // are set.
  // [SearchRequest.personalization_spec][google.cloud.retail.v2.SearchRequest.personalization_spec]
  // will override
  // [ServingConfig.personalization_spec][google.cloud.retail.v2.ServingConfig.personalization_spec].
  PersonalizationSpec personalization_spec = 32;

  // The labels applied to a resource must meet the following requirements:
  //
  // * Each resource can have multiple labels, up to a maximum of 64.
  // * Each label must be a key-value pair.
  // * Keys have a minimum length of 1 character and a maximum length of 63
  //   characters and cannot be empty. Values can be empty and have a maximum
  //   length of 63 characters.
  // * Keys and values can contain only lowercase letters, numeric characters,
  //   underscores, and dashes. All characters must use UTF-8 encoding, and
  //   international characters are allowed.
  // * The key portion of a label must be unique. However, you can use the same
  //   key with multiple resources.
  // * Keys must start with a lowercase letter or international character.
  //
  // For more information, see [Requirements for
  // labels](https://cloud.google.com/resource-manager/docs/creating-managing-labels#requirements)
  // in the Resource Manager documentation.
  map<string, string> labels = 34;

  // The spell correction specification that specifies the mode under
  // which spell correction will take effect.
  optional SpellCorrectionSpec spell_correction_spec = 35;

  // The entity for customers that may run multiple different entities, domains,
  // sites or regions, for example, `Google US`, `Google Ads`, `Waymo`,
  // `google.com`, `youtube.com`, etc.
  // If this is set, it should be exactly matched with
  // [UserEvent.entity][google.cloud.retail.v2.UserEvent.entity] to get search
  // results boosted by entity.
  string entity = 38;

  // Optional. This field specifies all conversational related parameters
  // addition to traditional retail search.
  ConversationalSearchSpec conversational_search_spec = 40
      [(google.api.field_behavior) = OPTIONAL];

  // Optional. This field specifies tile navigation related parameters.
  TileNavigationSpec tile_navigation_spec = 41
      [(google.api.field_behavior) = OPTIONAL];

  // Optional. The BCP-47 language code, such as "en-US" or "sr-Latn"
  // [list](https://www.unicode.org/cldr/charts/46/summary/root.html). For more
  // information, see [Standardized codes](https://google.aip.dev/143). This
  // field helps to better interpret the query. If a value isn't specified, the
  // query language code is automatically detected, which may not be accurate.
  string language_code = 43 [(google.api.field_behavior) = OPTIONAL];

  // Optional. The Unicode country/region code (CLDR) of a location, such as
  // "US" and "419"
  // [list](https://www.unicode.org/cldr/charts/46/supplemental/territory_information.html).
  // For more information, see [Standardized codes](https://google.aip.dev/143).
  // If set, then results will be boosted based on the region_code provided.
  string region_code = 44 [(google.api.field_behavior) = OPTIONAL];

  // Optional. An id corresponding to a place, such as a store id or region id.
  // When specified, we use the price from the local inventory with the matching
  // product's
  // [LocalInventory.place_id][google.cloud.retail.v2.LocalInventory.place_id]
  // for revenue optimization.
  string place_id = 46 [(google.api.field_behavior) = OPTIONAL];
}

// Response message for
// [SearchService.Search][google.cloud.retail.v2.SearchService.Search] method.
message SearchResponse {
  // Represents the search results.
  message SearchResult {
    // [Product.id][google.cloud.retail.v2.Product.id] of the searched
    // [Product][google.cloud.retail.v2.Product].
    string id = 1;

    // The product data snippet in the search response. Only
    // [Product.name][google.cloud.retail.v2.Product.name] is guaranteed to be
    // populated.
    //
    // [Product.variants][google.cloud.retail.v2.Product.variants] contains the
    // product variants that match the search query. If there are multiple
    // product variants matching the query, top 5 most relevant product variants
    // are returned and ordered by relevancy.
    //
    // If relevancy can be deternmined, use
    // [matching_variant_fields][google.cloud.retail.v2.SearchResponse.SearchResult.matching_variant_fields]
    // to look up matched product variants fields. If relevancy cannot be
    // determined, e.g. when searching "shoe" all products in a shoe product can
    // be a match, 5 product variants are returned but order is meaningless.
    Product product = 2;

    // The count of matched
    // [variant][google.cloud.retail.v2.Product.Type.VARIANT]
    // [Product][google.cloud.retail.v2.Product]s.
    int32 matching_variant_count = 3;

    // If a [variant][google.cloud.retail.v2.Product.Type.VARIANT]
    // [Product][google.cloud.retail.v2.Product] matches the search query, this
    // map indicates which [Product][google.cloud.retail.v2.Product] fields are
    // matched. The key is the
    // [Product.name][google.cloud.retail.v2.Product.name], the value is a field
    // mask of the matched [Product][google.cloud.retail.v2.Product] fields. If
    // matched attributes cannot be determined, this map will be empty.
    //
    // For example, a key "sku1" with field mask
    // "products.color_info" indicates there is a match between
    // "sku1" [ColorInfo][google.cloud.retail.v2.ColorInfo] and the query.
    map<string, google.protobuf.FieldMask> matching_variant_fields = 4;

    // The rollup matching
    // [variant][google.cloud.retail.v2.Product.Type.VARIANT]
    // [Product][google.cloud.retail.v2.Product] attributes. The key is one of
    // the
    // [SearchRequest.variant_rollup_keys][google.cloud.retail.v2.SearchRequest.variant_rollup_keys].
    // The values are the merged and de-duplicated
    // [Product][google.cloud.retail.v2.Product] attributes. Notice that the
    // rollup values are respect filter. For example, when filtering by
    // "colorFamilies:ANY(\"red\")" and rollup "colorFamilies", only "red" is
    // returned.
    //
    // For textual and numerical attributes, the rollup values is a list of
    // string or double values with type
    // [google.protobuf.ListValue][google.protobuf.ListValue]. For example, if
    // there are two variants with colors "red" and "blue", the rollup values
    // are
    //
    //     { key: "colorFamilies"
    //       value {
    //         list_value {
    //           values { string_value: "red" }
    //           values { string_value: "blue" }
    //          }
    //       }
    //     }
    //
    // For [FulfillmentInfo][google.cloud.retail.v2.FulfillmentInfo], the rollup
    // values is a double value with type
    // [google.protobuf.Value][google.protobuf.Value]. For example,
    // `{key: "pickupInStore.store1" value { number_value: 10 }}` means a there
    // are 10 variants in this product are available in the store "store1".
    map<string, google.protobuf.Value> variant_rollup_values = 5;

    // Specifies previous events related to this product for this user based on
    // [UserEvent][google.cloud.retail.v2.UserEvent] with same
    // [SearchRequest.visitor_id][google.cloud.retail.v2.SearchRequest.visitor_id]
    // or [UserInfo.user_id][google.cloud.retail.v2.UserInfo.user_id].
    //
    // This is set only when
    // [SearchRequest.PersonalizationSpec.mode][google.cloud.retail.v2.SearchRequest.PersonalizationSpec.mode]
    // is
    // [SearchRequest.PersonalizationSpec.Mode.AUTO][google.cloud.retail.v2.SearchRequest.PersonalizationSpec.Mode.AUTO].
    //
    // Possible values:
    //
    // * `purchased`: Indicates that this product has been purchased before.
    repeated string personal_labels = 7;
  }

  // A facet result.
  message Facet {
    // A facet value which contains value names and their count.
    message FacetValue {
      // A facet value which contains values.
      oneof facet_value {
        // Text value of a facet, such as "Black" for facet "colorFamilies".
        string value = 1;

        // Interval value for a facet, such as [10, 20) for facet "price".
        Interval interval = 2;
      }

      // Number of items that have this facet value.
      int64 count = 3;

      // The minimum value in the
      // [FacetValue.interval][google.cloud.retail.v2.SearchResponse.Facet.FacetValue.interval].
      // Only supported on numerical facets and returned if
      // [SearchRequest.FacetSpec.FacetKey.return_min_max][google.cloud.retail.v2.SearchRequest.FacetSpec.FacetKey.return_min_max]
      // is true.
      double min_value = 5;

      // The maximum value in the
      // [FacetValue.interval][google.cloud.retail.v2.SearchResponse.Facet.FacetValue.interval].
      // Only supported on numerical facets and returned if
      // [SearchRequest.FacetSpec.FacetKey.return_min_max][google.cloud.retail.v2.SearchRequest.FacetSpec.FacetKey.return_min_max]
      // is true.
      double max_value = 6;
    }

    // The key for this facet. E.g., "colorFamilies" or "price" or
    // "attributes.attr1".
    string key = 1;

    // The facet values for this field.
    repeated FacetValue values = 2;

    // Whether the facet is dynamically generated.
    bool dynamic_facet = 3;
  }

  // Information describing query expansion including whether expansion has
  // occurred.
  message QueryExpansionInfo {
    // Bool describing whether query expansion has occurred.
    bool expanded_query = 1;

    // Number of pinned results. This field will only be set when expansion
    // happens and
    // [SearchRequest.QueryExpansionSpec.pin_unexpanded_results][google.cloud.retail.v2.SearchRequest.QueryExpansionSpec.pin_unexpanded_results]
    // is set to true.
    int64 pinned_result_count = 2;
  }

  // This field specifies all related information that is needed on client
  // side for UI rendering of conversational retail search.
  message ConversationalSearchResult {
    // Suggested answers to the follow-up question.
    message SuggestedAnswer {
      // Product attribute value, including an attribute key and an
      // attribute value. Other types can be added here in the future.
      ProductAttributeValue product_attribute_value = 1;
    }

    // Additional filter that client side need to apply.
    message AdditionalFilter {
      // Product attribute value, including an attribute key and an
      // attribute value. Other types can be added here in the future.
      ProductAttributeValue product_attribute_value = 1;
    }

    // Conversation UUID. This field will be stored in client side storage to
    // maintain the conversation session with server and will be used for next
    // search request's
    // [SearchRequest.ConversationalSearchSpec.conversation_id][google.cloud.retail.v2.SearchRequest.ConversationalSearchSpec.conversation_id]
    // to restore conversation state in server.
    string conversation_id = 1;

    // The current refined query for the conversational search. This field
    // will be used in customer UI that the query in the search bar should be
    // replaced with the refined query. For example, if
    // [SearchRequest.query][google.cloud.retail.v2.SearchRequest.query] is
    // `dress` and next
    // [SearchRequest.ConversationalSearchSpec.UserAnswer.text_answer][google.cloud.retail.v2.SearchRequest.ConversationalSearchSpec.UserAnswer.text_answer]
    // is `red color`, which does not match any product attribute value filters,
    // the refined query will be `dress, red color`.
    string refined_query = 2;

    // This field is deprecated but will be kept for backward compatibility.
    // There is expected to have only one additional filter and the value will
    // be the same to the same as field `additional_filter`.
    repeated AdditionalFilter additional_filters = 3 [deprecated = true];

    // The follow-up question. e.g., `What is the color?`
    string followup_question = 4;

    // The answer options provided to client for the follow-up question.
    repeated SuggestedAnswer suggested_answers = 5;

    // This is the incremental additional filters implied from the current
    // user answer. User should add the suggested addition filters to the
    // previous
    // [SearchRequest.filter][google.cloud.retail.v2.SearchRequest.filter],  and
    // use the merged filter in the follow up search request.
    AdditionalFilter additional_filter = 6;
  }

  // This field specifies all related information for tile navigation that will
  // be used in client side.
  message TileNavigationResult {
    // The current tiles that are used for tile navigation, sorted by
    // engagement.
    repeated Tile tiles = 1;
  }

  // A list of matched items. The order represents the ranking.
  repeated SearchResult results = 1;

  // Results of facets requested by user.
  repeated Facet facets = 2;

  // The estimated total count of matched items irrespective of pagination. The
  // count of [results][google.cloud.retail.v2.SearchResponse.results] returned
  // by pagination may be less than the
  // [total_size][google.cloud.retail.v2.SearchResponse.total_size] that
  // matches.
  int32 total_size = 3;

  // Contains the spell corrected query, if found. If the spell correction type
  // is AUTOMATIC, then the search results are based on corrected_query.
  // Otherwise the original query is used for search.
  string corrected_query = 4;

  // A unique search token. This should be included in the
  // [UserEvent][google.cloud.retail.v2.UserEvent] logs resulting from this
  // search, which enables accurate attribution of search model performance.
  string attribution_token = 5;

  // A token that can be sent as
  // [SearchRequest.page_token][google.cloud.retail.v2.SearchRequest.page_token]
  // to retrieve the next page. If this field is omitted, there are no
  // subsequent pages.
  string next_page_token = 6;

  // Query expansion information for the returned results.
  QueryExpansionInfo query_expansion_info = 7;

  // The URI of a customer-defined redirect page. If redirect action is
  // triggered, no search is performed, and only
  // [redirect_uri][google.cloud.retail.v2.SearchResponse.redirect_uri] and
  // [attribution_token][google.cloud.retail.v2.SearchResponse.attribution_token]
  // are set in the response.
  string redirect_uri = 10;

  // The fully qualified resource name of applied
  // [controls](https://cloud.google.com/retail/docs/serving-control-rules).
  repeated string applied_controls = 12;

  // Metadata for pin controls which were applicable to the request.
  // This contains two map fields, one for all matched pins and one for pins
  // which were matched but not applied.
  //
  // The two maps are keyed by pin position, and the values are the product ids
  // which were matched to that pin.
  PinControlMetadata pin_control_metadata = 22;

  // The invalid
  // [SearchRequest.BoostSpec.condition_boost_specs][google.cloud.retail.v2.SearchRequest.BoostSpec.condition_boost_specs]
  // that are not applied during serving.
  repeated SearchRequest.BoostSpec.ConditionBoostSpec
      invalid_condition_boost_specs = 14;

  // Metadata related to A/B testing experiment associated with this
  // response. Only exists when an experiment is triggered.
  repeated ExperimentInfo experiment_info = 17;

  // This field specifies all related information that is needed on client
  // side for UI rendering of conversational retail search.
  ConversationalSearchResult conversational_search_result = 18;

  // This field specifies all related information for tile navigation that will
  // be used in client side.
  TileNavigationResult tile_navigation_result = 19;
}

// Metadata for active A/B testing experiment.
message ExperimentInfo {
  // Metadata for active serving config A/B tests.
  message ServingConfigExperiment {
    // The fully qualified resource name of the original
    // [SearchRequest.placement][google.cloud.retail.v2.SearchRequest.placement]
    // in the search request prior to reassignment by experiment API. For
    // example: `projects/*/locations/*/catalogs/*/servingConfigs/*`.
    string original_serving_config = 1 [(google.api.resource_reference) = {
      type: "retail.googleapis.com/ServingConfig"
    }];

    // The fully qualified resource name of the serving config
    // `Experiment.VariantArm.serving_config_id` responsible for generating
    // the search response. For example:
    // `projects/*/locations/*/catalogs/*/servingConfigs/*`.
    string experiment_serving_config = 2 [(google.api.resource_reference) = {
      type: "retail.googleapis.com/ServingConfig"
    }];
  }

  // Information associated with the specific experiment entity being recorded.
  oneof experiment_metadata {
    // A/B test between existing Cloud Retail Search
    // [ServingConfig][google.cloud.retail.v2.ServingConfig]s.
    ServingConfigExperiment serving_config_experiment = 2;
  }

  // The fully qualified resource name of the experiment that provides the
  // serving config under test, should an active experiment exist. For example:
  // `projects/*/locations/global/catalogs/default_catalog/experiments/experiment_id`
  string experiment = 1 [(google.api.resource_reference) = {
    type: "retail.googleapis.com/Experiment"
  }];
}
