| name | user-to-item |
| description | Use when user needs personalized recommendations based on user profile. Triggers on: personalized, user recommendation, personalized recommendations, for you, feed, user preference, homepage recommendations. |
User-to-Item Personalized Recommendation
Build personalized recommendation feeds based on user behavior history — power "For You" and personalized homepage features.
When to Activate
Activate this skill when:
- User needs personalized recommendations for individual users
- User mentions "for you", "personalized feed", "user preferences"
- User wants to build homepage recommendations based on history
- User's recommendation should be user-centric (not item-centric)
Do NOT activate when:
- User needs "similar items" for a product → use
item-to-item
- User needs semantic search → use
semantic-search
- User has no user behavior data
Interactive Flow
Step 1: Understand User Data
"What user behavior data do you have?"
| Data Type | Weight | Example |
|---|
| Purchases | High (3-5x) | User bought product X |
| Add to cart | Medium (2x) | User added X to cart |
| Clicks | Low (1x) | User clicked on X |
| Views | Lowest (0.5x) | User viewed X |
| Dislikes | Negative | User marked X as not interested |
Which data types do you have?
Step 2: Cold Start Strategy
"How should we handle new users?"
A) Popular items: Show trending/popular items
B) Category-based: Ask for initial preferences
C) Hybrid: Popular initially, then personalize
Step 3: Confirm Configuration
"Based on your requirements:
- User profile: Weighted average of interacted items
- Time decay: 7-day half-life
- Cold start: Popular items fallback
Proceed? (yes / adjust [what])"
Core Concepts
Mental Model: Personal Shopper
Think of user-to-item as a personal shopper who knows your taste:
- Remembers what you bought, browsed, liked
- Learns your preferences over time
- Suggests items matching your taste profile
┌─────────────────────────────────────────────────────────┐
│ User-to-Item Recommendation │
│ │
│ User History: │
│ ┌──────────────────────────────────────────────────┐ │
│ │ Bought: ML Book (w=3) │ │
│ │ Clicked: Python Tutorial (w=1.5) │ │
│ │ Viewed: Data Science Course (w=1) │ │
│ └──────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌──────────────────────────────────────────────────┐ │
│ │ User Profile Vector │ │
│ │ = Weighted Average of Item Embeddings │ │
│ │ │ │
│ │ user_vec = (3×ML_vec + 1.5×Py_vec + 1×DS_vec) │ │
│ │ / (3 + 1.5 + 1) │ │
│ └──────────────────────┬───────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌──────────────────────────────────────────────────┐ │
│ │ Vector Search in Item Space │ │
│ │ (exclude already interacted) │ │
│ └──────────────────────┬───────────────────────────┘ │
│ │ │
│ ▼ │
│ Recommended Items: │
│ ┌─────┬─────┬─────┬─────┐ │
│ │AI │Deep │Stats│NLP │ (matches user taste) │
│ │Book │Learn│Book │Course│ │
│ └─────┴─────┴─────┴─────┘ │
└─────────────────────────────────────────────────────────┘
User Profile Construction
| Method | Pros | Cons |
|---|
| Weighted Average | Simple, fast | No learning |
| Last N items | Captures recent interest | Ignores long-term |
| Time-decayed | Balances recency and history | Requires tuning |
Implementation
from pymilvus import MilvusClient, DataType
from sentence_transformers import SentenceTransformer
import numpy as np
import time
class UserToItemRecommender:
def __init__(self, uri: str = "./milvus.db"):
self.client = MilvusClient(uri=uri)
self.model = SentenceTransformer('BAAI/bge-large-en-v1.5')
self.dim = 1024
self._init_collections()
def _init_collections(self):
if not self.client.has_collection("items"):
schema = self.client.create_schema()
schema.add_field("id", DataType.VARCHAR, is_primary=True, max_length=64)
schema.add_field("title", DataType.VARCHAR, max_length=1024)
schema.add_field("category", DataType.VARCHAR, max_length=256)
schema.add_field("embedding", DataType.FLOAT_VECTOR, dim=self.dim)
index_params = self.client.prepare_index_params()
index_params.add_index(field_name="embedding", index_type="AUTOINDEX", metric_type="COSINE")
self.client.create_collection(collection_name=, schema=schema, index_params=index_params)
.client.has_collection():
schema = .client.create_schema()
schema.add_field(, DataType.VARCHAR, is_primary=, max_length=)
schema.add_field(, DataType.FLOAT_VECTOR, dim=.dim)
schema.add_field(, DataType.INT64)
index_params = .client.prepare_index_params()
index_params.add_index(field_name=, index_type=, metric_type=)
.client.create_collection(collection_name=, schema=schema, index_params=index_params)
():
texts = [ item items]
embeddings = .model.encode(texts).tolist()
data = [{: item[], : item[],
: item.get(, ), : emb}
item, emb (items, embeddings)]
.client.insert(collection_name=, data=data)
():
action_weights = {
: ,
: ,
: ,
: ,
: ,
: -,
}
item_ids = [i[] i interactions]
items = .client.get(collection_name=, ids=item_ids, output_fields=[])
items:
item_emb_map = {item[]: item[] item items}
embeddings = []
weights = []
i, inter (interactions):
inter[] item_emb_map:
action = inter.get(, )
action_weight = action_weights.get(action, )
time_weight = decay_rate ** i
embeddings.append(item_emb_map[inter[]])
weights.append(action_weight * time_weight)
embeddings:
weights = np.array(weights)
weights = weights / weights.()
user_embedding = np.average(embeddings, axis=, weights=weights).tolist()
.client.upsert(
collection_name=,
data=[{
: user_id,
: user_embedding,
: (time.time())
}]
)
() -> :
users = .client.get(
collection_name=,
ids=[user_id],
output_fields=[]
)
users:
.get_popular_items(limit)
user_embedding = users[][]
filter_parts = []
exclude_ids:
ids_str = .join([ exclude_ids])
filter_parts.append()
category:
filter_parts.append()
filter_expr = .join(filter_parts) filter_parts
results = .client.search(
collection_name=,
data=[user_embedding],
=filter_expr,
limit=limit,
output_fields=[, , ]
)
[{: hit[][],
: hit[][],
: hit[][],
: hit[]} hit results[]]
() -> :
results = .client.query(
collection_name=,
=,
limit=limit,
output_fields=[, , ]
)
results
() -> :
exploit_count = (limit * ( - explore_ratio))
explore_count = limit - exploit_count
exploit_recs = .recommend(user_id, limit=exploit_count)
explore_recs = .get_diverse_items(limit=explore_count,
exclude_ids=[r[] r exploit_recs])
exploit_recs + explore_recs
recommender = UserToItemRecommender()
recommender.add_items([
{: , : , : },
{: , : , : },
{: , : , : },
{: , : , : },
{: , : , : },
])
recommender.update_user_profile(, [
{: , : },
{: , : },
{: , : },
])
recs = recommender.recommend(, limit=)
()
r recs:
()
User Profile Strategies
Time Decay Function
def time_decay_weight(timestamp: int, half_life_days: int = 7) -> float:
"""Exponential decay with configurable half-life"""
days_ago = (time.time() - timestamp) / 86400
return 0.5 ** (days_ago / half_life_days)
Action Weights Table
| Action | Suggested Weight | Rationale |
|---|
| Purchase | 5.0 | Strongest intent signal |
| Add to cart | 3.0 | High purchase intent |
| Favorite/save | 2.5 | Explicit interest |
| Click | 1.5 | Some interest |
| View/impression | 1.0 | Baseline |
| Skip/hide | -1.0 | Negative signal |
| Dislike | -2.0 | Strong negative |
Cold Start Strategies
For New Users
def recommend_cold_start(self, user_info: dict) -> list:
"""Handle users with no history"""
if user_info.get("interests"):
interest_text = " ".join(user_info["interests"])
embedding = self.model.encode(interest_text).tolist()
return self.search_by_embedding(embedding)
if user_info.get("age_group"):
return self.get_popular_by_demographic(user_info["age_group"])
return self.get_popular_items()
For New Items
def handle_new_item(self, item_id: str) -> None:
"""Boost new items for discovery"""
pass
Common Pitfalls
❌ Pitfall 1: Filter Bubble
Problem: User only sees similar content, no discovery
Fix: Add exploration ratio
recommendations = recommend_with_exploration(user_id, explore_ratio=0.2)
❌ Pitfall 2: Stale Profiles
Problem: Recommendations don't reflect changed interests
Fix: Time decay + recent activity boost
time_weight = 0.9 ** days_since_action
❌ Pitfall 3: Recommending Already Purchased
Problem: User sees items they already bought
Fix: Always exclude previous interactions
exclude_ids = get_user_purchase_history(user_id)
recommendations = recommend(user_id, exclude_ids=exclude_ids)
❌ Pitfall 4: Cold Start Black Hole
Problem: New users get nothing or irrelevant items
Fix: Have explicit cold start strategy
if not user_has_history(user_id):
return get_onboarding_recommendations(user_info)
When to Level Up
| Need | Upgrade To |
|---|
| Similar items (not personalized) | item-to-item |
| Real-time profile updates | Add streaming pipeline |
| Multi-objective optimization | Add re-ranking layer |
| A/B testing | Track engagement metrics |
References
- Similar items:
rec-system:item-to-item
- Vertical guides:
verticals/
- Embedding models:
core:embedding