Vector Recommendation Systems
Recommendation systems suggest items a user is likely to want — products, movies, songs, or articles. Vector databases power modern recommendation engines by representing both items and users as vectors, then finding the closest matches between them. This topic shows you how it works.
Two Recommendation Strategies
Content-Based Recommendations
Recommend items similar to ones the user already liked, based on the item's own properties (description, image, category).
USER LIKED: "Wireless Noise-Canceling Headphones"
│
▼ embed this product's description
Query Vector: [0.3, 0.8, 0.2, ...]
│
▼ find nearest neighbors in product catalog
RESULTS:
"Bluetooth Over-Ear Headphones" (score: 0.91)
"Noise-Canceling Earbuds" (score: 0.88)
"Wireless Gaming Headset" (score: 0.79)
Collaborative-Style Recommendations Using Vectors
Recommend items liked by users with similar taste profiles. Instead of comparing item descriptions, you build a vector representing the user's overall preferences.
USER'S RECENT ACTIVITY:
Liked: Sci-fi movie A, Sci-fi movie B, Thriller movie C
│
▼ average their embeddings to build a "taste vector"
User Taste Vector: [0.6, 0.4, 0.7, ...]
│
▼ find nearest movies the user hasn't watched yet
RECOMMENDATIONS:
Sci-fi movie D (score: 0.89)
Thriller movie E (score: 0.84)
Building a User Taste Vector
A simple and effective approach averages the embeddings of items a user has interacted with positively.
import numpy as np
def build_user_vector(liked_item_vectors):
"""Average multiple item vectors into one user taste vector"""
return np.mean(liked_item_vectors, axis=0).tolist()
# User liked 3 products
liked_vectors = [
get_embedding("Wireless headphones with noise canceling"),
get_embedding("Bluetooth earbuds for running"),
get_embedding("Over-ear studio headphones"),
]
user_vector = build_user_vector(liked_vectors)
This averaged vector sits in the "audio equipment" region of the vector space, naturally pulling recommendations toward similar products.
Weighted Taste Vectors
Recent interactions usually matter more than old ones. Weight recent likes higher than older ones for more relevant recommendations.
def build_weighted_user_vector(item_vectors_with_recency):
"""
item_vectors_with_recency: list of (vector, weight) tuples
Recent items get higher weight
"""
weighted_sum = np.zeros(len(item_vectors_with_recency[0][0]))
total_weight = 0
for vector, weight in item_vectors_with_recency:
weighted_sum += np.array(vector) * weight
total_weight += weight
return (weighted_sum / total_weight).tolist()
# Example: most recent like weighted highest
items = [
(get_embedding("Action movie"), 1.0), # most recent
(get_embedding("Action movie 2"), 0.7),
(get_embedding("Comedy movie"), 0.3), # oldest
]
user_vector = build_weighted_user_vector(items)
Querying for Recommendations
def get_recommendations(user_vector, exclude_ids, top_k=10):
results = index.query(
vector=user_vector,
top_k=top_k + len(exclude_ids), # fetch extra to account for exclusions
include_metadata=True
)
# Remove items the user already interacted with
recommendations = [
match for match in results["matches"]
if match["id"] not in exclude_ids
]
return recommendations[:top_k]
already_seen = ["item-12", "item-45", "item-78"]
recommendations = get_recommendations(user_vector, already_seen)
Diversifying Recommendations
Pure similarity search can produce repetitive results — five nearly identical headphone models, for example. Adding diversity improves the user experience.
MMR (Maximal Marginal Relevance) APPROACH:
─────────────────────────────────────────────
1. Fetch top 30 candidates by similarity
2. Pick the most similar item first
3. For each next pick, balance:
- similarity to user vector (relevance)
- dissimilarity to already-picked items (diversity)
4. Repeat until you have enough diverse recommendations
def diversify_results(candidates, user_vector, top_k=10, diversity_weight=0.3):
selected = []
remaining = candidates.copy()
while len(selected) < top_k and remaining:
if not selected:
# First pick: most relevant
best = max(remaining, key=lambda x: x["score"])
else:
# Balance relevance with distance from already-selected items
def combined_score(candidate):
relevance = candidate["score"]
max_similarity_to_selected = max(
cosine_similarity(candidate["vector"], s["vector"])
for s in selected
)
return relevance - (diversity_weight * max_similarity_to_selected)
best = max(remaining, key=combined_score)
selected.append(best)
remaining.remove(best)
return selected
The Cold Start Problem
New users with no interaction history have no taste vector to build recommendations from. Common solutions:
| Solution | How It Works |
|---|---|
| Popular items fallback | Show trending or best-selling items until enough data exists |
| Onboarding questions | Ask new users to pick a few interests, build an initial vector from those |
| Demographic vectors | Use average taste vector of similar user segments |
| Content browsing signals | Build a lightweight vector from pages viewed, even without explicit likes |
Updating Recommendations Over Time
User preferences shift. Refresh the taste vector regularly — after each new interaction, on a daily batch job, or using a sliding window of the most recent 20–50 interactions. A vector database makes this update cheap because you only recompute one user vector and re-run a fast similarity query.
