mcpField
R
recommendation-system

recommendation-system

github.com/secondsky数据与数据库240406 次安装5 周前更新
代码仓库

关于 recommendation-system

面向构建个性化推荐服务的开发者,提供可扩展推荐系统的设计与运维指导,涵盖特征存储、缓存、实验和质量监控。

主要功能

  1. 01用户与物品特征存储
  2. 02多层推荐缓存
  3. 03支持 A/B 测试的模型服务
  4. 04质量与延迟监控
  5. 05冷启动与多样性方案

适用场景

  1. 01构建个性化推荐 API
  2. 02通过实验比较推荐模型
  3. 03利用缓存降低响应延迟
  4. 04跟踪推荐质量指标

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/secondsky/claude-skills --skill recommendation-system

安装后在 Claude Code 中输入/recommendation-system确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

references/
ab-testing-framework.md
caching-strategies.md
monitoring-alerting.md
production-architecture.md

详细介绍

推荐系统

面向生产环境的可扩展推荐系统架构,包含特征存储、多层缓存、A/B 测试和全面监控。

何时使用此技能

在以下场景加载此技能:

  • 构建推荐 API:大规模提供个性化推荐
  • 实现缓存:通过多层缓存实现亚毫秒级延迟
  • 运行 A/B 测试:试验推荐算法
  • 监控质量:跟踪 CTR、转化率、多样性和覆盖率
  • 优化性能:降低延迟、提高吞吐量
  • 特征工程:使用特征存储管理用户和商品特征

快速开始:5 步构建推荐 API

# 1. Install dependencies
pip install "fastapi>=0.109.0" "redis>=5.0.0" "prometheus-client>=0.19.0"

# 2. Start Redis (for caching and feature store)
docker run -d -p 6379:6379 redis:alpine

# 3. Create recommendation service: app.py
cat > app.py << 'EOF'
from fastapi import FastAPI
from pydantic import BaseModel
from typing import List
import redis
import json

app = FastAPI()
cache = redis.Redis(host='localhost', port=6379, decode_responses=True)

class RecommendationResponse(BaseModel):
    user_id: str
    items: List[str]
    cached: bool

@app.post("/recommendations", response_model=RecommendationResponse)
async def get_recommendations(user_id: str, n: int = 10):
    # Check cache
    cache_key = f"recs:{user_id}:{n}"
    cached = cache.get(cache_key)

    if cached:
        return RecommendationResponse(
            user_id=user_id,
            items=json.loads(cached),
            cached=True
        )

    # Generate recommendations (simplified)
    items = [f"item_{i}" for i in range(n)]

    # Cache for 5 minutes
    cache.setex(cache_key, 300, json.dumps(items))

    return RecommendationResponse(
        user_id=user_id,
        items=items,
        cached=False
    )

@app.get("/health")
async def health():
    return {"status": "healthy"}
EOF

# 4. Run API
uvicorn app:app --host 0.0.0.0 --port 8000

# 5. Test
curl -X POST "http://localhost:8000/recommendations?user_id=user_123&n=10"

结果:不到 5 分钟即可获得一个带缓存的可用推荐 API。

系统架构

┌─────────────┐     ┌─────────────┐     ┌─────────────┐
│ User Events │────▶│ Feature     │────▶│ Model       │
│ (clicks,    │     │ Store       │     │ Serving     │
│  purchases) │     │ (Redis)     │     │             │
└─────────────┘     └─────────────┘     └─────────────┘
                           │                    │
                           ▼                    ▼
                    ┌─────────────┐     ┌─────────────┐
                    │ Training    │     │ API         │
                    │ Pipeline    │     │ (FastAPI)   │
                    └─────────────┘     └─────────────┘
                                               │
                                               ▼
                                        ┌─────────────┐
                                        │ Monitoring  │
                                        │ (Prometheus)│
                                        └─────────────┘

核心组件

1. 特征存储

集中存储用户和商品特征:

import redis
import json

class FeatureStore:
    """Fast feature access with Redis caching."""

    def __init__(self, redis_client):
        self.redis = redis_client
        self.ttl = 3600  # 1 hour

    def get_user_features(self, user_id: str) -> dict:
        cache_key = f"user_features:{user_id}"
        cached = self.redis.get(cache_key)

        if cached:
            return json.loads(cached)

        # Fetch from database
        features = fetch_from_db(user_id)

        # Cache
        self.redis.setex(cache_key, self.ttl, json.dumps(features))
        return features
2. 模型服务

为 A/B 测试提供多个模型:

class ModelServing:
    """Serve multiple recommendation models."""

    def __init__(self):
        self.models = {}

    def register_model(self, name: str, model, is_default: bool = False):
        self.models[name] = model
        if is_default:
            self.default_model = name

    def predict(self, user_features: dict, item_features: list, model_name: str = None):
        model = self.models.get(model_name or self.default_model)
        return model.predict(user_features, item_features)
3. 缓存层

通过多层缓存实现低延迟:

class TieredCache:
    """L1 (memory) -> L2 (Redis) -> L3 (database)."""

    def __init__(self, redis_client):
        self.l1_cache = {}  # In-memory
        self.redis = redis_client  # L2

    def get(self, key: str):
        # L1: In-memory (fastest)
        if key in self.l1_cache:
            return self.l1_cache[key]

        # L2: Redis
        cached = self.redis.get(key)
        if cached:
            value = json.loads(cached)
            self.l1_cache[key] = value  # Promote to L1
            return value

        # L3: Miss (fetch from database)
        return None

关键指标

指标描述目标
CTR点击率>5%
转化率来自推荐的购买量>2%
P95 延迟第 95 百分位响应时间<200ms
缓存命中率由缓存提供的请求占比>80%
覆盖率获得推荐的商品目录占比>50%
多样性推荐内容的丰富程度>0.7

预防已知问题

1. 新用户冷启动

问题:没有历史记录的用户无法获得推荐,初次体验较差。

解决方案:使用基于热门度的备用方案:

def get_recommendations(user_id: str, n: int = 10):
    user_features = feature_store.get_user_features(user_id)

    # Check if new user (no purchase history)
    if user_features.get('total_purchases', 0) == 0:
        # Fallback to popular items
        return get_popular_items(n)

    # Personalized recommendations
    return generate_personalized_recs(user_id, n)
2. 用户操作后缓存失效

问题:用户完成购买后,缓存中仍在推荐已购买的商品。

解决方案:在相关操作发生时使缓存失效:

INVALIDATING_ACTIONS = {'purchase', 'rating', 'add_to_cart'}

def on_user_action(user_id: str, action: str):
    if action in INVALIDATING_ACTIONS:
        cache_key = f"recs:{user_id}:*"
        redis_client.delete(cache_key)
        logger.info(f"Invalidated cache for {user_id} due to {action}")
3. 缓存过期引发惊群效应

问题:许多用户的缓存同时过期,导致数据库或模型过载。

解决方案:为 TTL 添加随机抖动:

import random

def set_cache(key: str, value: dict, base_ttl: int = 300):
    # Add ±10% jitter
    jitter = random.uniform(-0.1, 0.1) * base_ttl
    ttl = int(base_ttl + jitter)
    redis_client.setex(key, ttl, json.dumps(value))
4. 多样性不足导致信息茧房

问题:推荐内容过于相似,用户只能看到同一类别的内容。

解决方案:实施多样性约束:

def rank_with_diversity(items: list, scores: list, n: int = 10):
    selected = []
    category_counts = {}

    for item, score in sorted(zip(items, scores), key=lambda x: -x[1]):
        category = item['category']

        # Limit 3 items per category
        if category_counts.get(category, 0) >= 3:
            continue

        selected.append(item)
        category_counts[category] = category_counts.get(category, 0) + 1

        if len(selected) >= n:
            break

    return selected
5. 缺少监控导致问题悄然恶化

问题:推荐质量下降,直到用户投诉才有人发现。

解决方案:持续监控并设置告警:

from prometheus_client import Counter, Histogram

recommendation_clicks = Counter('recommendation_clicks_total')
recommendation_latency = Histogram('recommendation_latency_seconds')

@app.post("/recommendations")
async def get_recommendations(user_id: str):
    start = time.time()

    recs = generate_recs(user_id)

    latency = time.time() - start
    recommendation_latency.observe(latency)

    return recs

@app.post("/track/click")
async def track_click(user_id: str, item_id: str):
    recommendation_clicks.inc()
    # Alert if CTR drops below 3%
6. 特征过时导致推荐过时

问题:用户偏好发生变化,但特征未更新,导致推荐不相关。

解决方案:设置适当的 TTL 和更新触发条件:

class FeatureStore:
    def __init__(self, redis_client):
        self.redis = redis_client
        # Shorter TTL for frequently changing features
        self.user_ttl = 300  # 5 minutes
        self.item_ttl = 3600  # 1 hour

    def update_on_event(self, user_id: str, event: str):
        # Invalidate on important events
        if event in ['purchase', 'rating']:
            self.redis.delete(f"user_features:{user_id}")
            logger.info(f"Refreshed features for {user_id}")
7. A/B 测试样本量过小

问题:过早判定胜出方案,结果不具备统计显著性。

解决方案:先计算所需样本量:

def calculate_sample_size(
    baseline_rate: float,
    min_detectable_effect: float,
    alpha: float = 0.05,
    power: float = 0.8
) -> int:
    """Calculate required sample size per variant."""
    from scipy import stats

    z_alpha = stats.norm.ppf(1 - alpha/2)
    z_beta = stats.norm.ppf(power)

    p1 = baseline_rate
    p2 = baseline_rate * (1 + min_detectable_effect)
    p_avg = (p1 + p2) / 2

    n = (
        (z_alpha + z_beta)**2 * 2 * p_avg * (1 - p_avg) /
        (p2 - p1)**2
    )

    return int(n)

# Example: detect 10% lift with baseline CTR=5%
n_required = calculate_sample_size(
    baseline_rate=0.05,
    min_detectable_effect=0.10
)
print(f"Required sample size: {n_required} per variant")
# Wait until both variants reach this size before concluding

何时加载参考资料

加载参考文件,了解生产环境实现的详细信息:

  • 生产架构:加载 references/production-architecture.md,查看完整的 FeatureStore、ModelServing 和 RecommendationService 实现,涵盖批量获取、缓存集成和 FastAPI 部署模式。

  • 缓存策略:在实现多层缓存(L1/L2/L3)、缓存预热、失效策略、概率刷新或惊群效应防范时,加载 references/caching-strategies.md。

  • A/B 测试框架:加载 references/ab-testing-framework.md,了解确定性变体分配、Thompson 抽样(多臂老虎机)、贝叶斯和频率学派显著性检验,以及实验跟踪。

  • 监控与告警:加载 references/monitoring-alerting.md,了解 Prometheus 指标集成、仪表板端点、告警规则和质量监控(多样性、覆盖率)。

最佳实践

  1. 特征预计算:离线计算特征,并从缓存提供
  2. 批量获取:使用 Redis MGET 获取多个用户/条目的数据
  3. 积极缓存:为用户推荐设置 5-15 分钟的 TTL
  4. 优雅降级:个性化推荐失败时,返回热门条目
  5. 全面监控:跟踪 CTR、延迟、多样性和覆盖率
  6. 持续进行 A/B 测试:不断尝试新算法
  7. 多样性约束:确保推荐内容丰富多样
  8. 解释推荐理由:提供原因(“评分很高”“很受欢迎”)

常见模式

推荐服务
class RecommendationService:
    def __init__(self, feature_store, model_serving, cache):
        self.feature_store = feature_store
        self.model_serving = model_serving
        self.cache = cache

    def get_recommendations(self, user_id: str, n: int = 10):
        # 1. Check cache
        cached = self.cache.get(f"recs:{user_id}:{n}")
        if cached:
            return cached

        # 2. Get features
        user_features = self.feature_store.get_user_features(user_id)
        candidates = self.get_candidates(user_id)

        # 3. Score candidates
        scores = self.model_serving.predict(user_features, candidates)

        # 4. Rank with diversity
        recommendations = self.rank_with_diversity(candidates, scores, n)

        # 5. Cache
        self.cache.set(f"recs:{user_id}:{n}", recommendations, ttl=300)

        return recommendations
A/B 测试
def assign_variant(user_id: str, experiment_id: str) -> str:
    """Deterministic assignment - same user always gets same variant."""
    import hashlib

    hash_input = f"{user_id}:{experiment_id}"
    hash_value = int(hashlib.md5(hash_input.encode()).hexdigest(), 16)

    # 50/50 split
    return 'control' if hash_value % 2 == 0 else 'treatment'

# Usage
variant = assign_variant('user_123', 'rec_algo_v2')
model_name = 'main' if variant == 'control' else 'experimental'
recs = get_recommendations(user_id, model_name=model_name)
监控
from prometheus_client import Counter, Histogram

requests_total = Counter('recommendation_requests_total', ['status'])
latency_seconds = Histogram('recommendation_latency_seconds')

@app.post("/recommendations")
async def get_recommendations(user_id: str):
    with latency_seconds.time():
        try:
            recs = generate_recs(user_id)
            requests_total.labels(status='success').inc()
            return recs
        except Exception as e:
            requests_total.labels(status='error').inc()
            raise

评论

支持 Markdown