A shopper who previously viewed a water bottle and a stainless-steel range now searches for “microwave.” Using every past interaction to personalize that search could pull results toward an unrelated interest. A categorical guardrail limits personalization to past items in categories related to the current query; it does not replace the query or filter the search results themselves. section 9.4.2 section 9.4.3
An embedding-based guardrail can be applied before reranking:
- Cluster product embeddings into categories when no suitable category field exists. Inspect common terms in each cluster to check whether its products belong together. section 9.4.2
- Embed the incoming query and find its closest product clusters. Associate each past product interaction with a cluster, then retain only interactions in the query’s selected clusters. section 9.4.2 section 9.4.3
- Combine the query embedding with the retained interaction embeddings to form a personalization vector. Search for the query, then use similarity to that vector to rerank the returned products. section 9.4.3
A microwave search
For “microwave,” the five selected clusters include microwaves, ranges, and other kitchen products. The prior range interaction falls within them; the water bottle does not. The guarded vector therefore uses the range interaction, while an unguarded vector uses both. Comparing unpersonalized, unguarded, and guarded results makes the effect visible. section 9.4.2 section 9.4.3
Listing 9.17 Generating personalization vectors from user queries
product_interests = ["7610465823828", #hello kitty water bottle
"36725569478"] #stainless steel electric range
user_embeddings = get_user_embeddings(product_interests)
query = "microwave"
unfiltered_personalization_vector = #1
get_personalization_vector(query=query, #1
user_items=user_embeddings['embedding'].to_numpy()) #1
print("\nPersonalization Vector (No Cluster Guardrails):")
print(format_vector(unfiltered_personalization_vector))
query_clusters = get_top_labels_centers(query, #2
centers, n=5) #2
print("\nQuery Clusters ('microwave'):\n" + str(query_clusters))
clustered = user_embeddings.cluster.isin(query_clusters) #3
products_in_cluster = user_embeddings[clustered] #3
print("\nProducts Filtered to Query Clusters:\n" + str(products_in_cluster))
filtered_personalization_vector = get_personalization_vector(query=query, #4
user_items=filtered['embedding'].to_numpy()) #4
print("\nFiltered Personalization Vector (With Cluster Guardrails):")
print(format_vector(filtered_personalization_vector))
The Python listing shows where query clusters are selected, past interactions are filtered, and guarded and unguarded personalization vectors are generated. section 9.4.3
Keep the constraint narrow: a shared word or past preference is not enough to justify influencing an unrelated query. Personalization can also overpower what the user explicitly asked for. With no interaction history, fall back to non-personalized results rather than forcing a preference signal. section 9.5













