AI-Powered Content-Based Recommendations for Mobile Apps
Content-Based Filtering doesn't need data about other users — builds specific user profile from characteristics of content they interact with. For new apps without accumulated user base, often only working recommendation option from day one.
When Content-Based Wins Over Collaborative Filtering
Three scenarios preferring CB approach:
Niche Content with Unique Metadata. Articles, recipes, tourist routes — each item has rich attributes (tags, categories, authors, locations). CF works on "users are similar" signal, but for niche content similar users may be too few.
Privacy-First Architecture. CB can work entirely on-device — user profile stored locally, recommendations built without sending data to server.
Long-Tail Content. New article published hour ago has no interaction history for CF. CB recommends immediately once metadata indexed.
System Core: Content and User Representation
TF-IDF and Embeddings for Text Content
For articles, descriptions, news — two approaches: TF-IDF for speed, sentence embeddings for quality. In practice use sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2 (278 MB, supports Russian): each item becomes 384-dimensional vector.
from sentence_transformers import SentenceTransformer
model = SentenceTransformer('paraphrase-multilingual-MiniLM-L12-v2')
def embed_article(article: Article) -> np.ndarray:
text = f"{article.title}. {article.description}. {' '.join(article.tags)}"
return model.encode(text, normalize_embeddings=True)
# Cosine similarity via numpy
def similarity(v1: np.ndarray, v2: np.ndarray) -> float:
return float(np.dot(v1, v2)) # normalized vectors, dot = cosine
User Profile — Moving Average of Embeddings
User profile is weighted average of embeddings from content they interacted with. Recent interactions weighted higher (exponential decay):
def update_user_profile(profile: np.ndarray, new_item_embedding: np.ndarray,
interaction_weight: float, decay: float = 0.9) -> np.ndarray:
updated = decay * profile + (1 - decay) * interaction_weight * new_item_embedding
return updated / np.linalg.norm(updated) # renormalize
On-Device Recommendations on iOS via CoreML
For small catalogs (up to 50K items), entire CB search moved to device:
// iOS: local CB search without network requests
class OnDeviceRecommender {
private let userProfileKey = "user_embedding_v2"
private var itemIndex: [(id: String, embedding: [Float])] = []
func loadItemIndex(from url: URL) {
// load pre-computed embeddings at startup
let data = try! Data(contentsOf: url)
itemIndex = try! JSONDecoder().decode([(id: String, embedding: [Float])].self, from: data)
}
func getRecommendations(count: Int) -> [String] {
guard let profileData = UserDefaults.standard.data(forKey: userProfileKey),
let profile = try? JSONDecoder().decode([Float].self, from: profileData)
else { return popularItemIds(count: count) }
return itemIndex
.map { item in (item.id, cosineSimilarity(profile, item.embedding)) }
.sorted { $0.1 > $1.1 }
.prefix(count)
.map { $0.0 }
}
private func cosineSimilarity(_ a: [Float], _ b: [Float]) -> Float {
zip(a, b).map(*).reduce(0, +) // assume normalized vectors
}
}
Embeddings index updated on app start or schedule — load JSON with pre-computed vectors from server (~20 MB for 50K items × 384d float32).
Structured Metadata: Not Just Text
For merchandise catalog, text embeddings complemented by categorical features: category, brand, price range, color. Final vector — concatenation of normalized text embedding and one-hot/ordinal features:
def build_item_vector(item: Product) -> np.ndarray:
text_emb = embed_text(f"{item.name} {item.description}") # 384-dim
cat_features = encode_categorical({
'category': item.category_id,
'brand': item.brand_id,
'price_range': bucket_price(item.price) # [0-500, 500-2000, 2000+]
}) # ~50-dim
return np.concatenate([text_emb * 0.7, cat_features * 0.3]) # weighted concatenation
Process
Analyze content structure: what metadata available, quality and completeness.
Choose embedding model for language and domain.
Build index and user profile update mechanism.
Decide on-device vs server recommendations based on catalog size and privacy requirements.
Timeline Guidance
Server CB with ready embeddings and API — 1–1.5 weeks. On-device variant for iOS/Android with local index — 2–3 weeks. Hybrid with partial on-device processing — 3–4 weeks.







