LLM在推荐系统中的应用:从冷启动到个性化的工程实践

2 阅读1分钟

引言

传统推荐系统在"冷启动"问题上栽了多少跟头?新用户没有历史行为,新商品没有评分数据,协同过滤和矩阵分解统统束手无策。

LLM的出现为推荐系统带来了新的可能:强大的语义理解能力、丰富的世界知识储备,以及对用户自然语言偏好表达的准确解析。本文将深入探讨LLM增强推荐系统的工程架构,从冷启动到实时个性化的完整实践方案。


一、LLM推荐系统的技术路线

1.1 三种主要范式

LLM推荐系统技术路线
│
├── 1. LLM-as-Recommender(纯LLM推荐)
│      直接用LLM生成推荐列表
│      优点:零冷启动,语义理解强
│      缺点:无法实时,不了解实时库存
│
├── 2. LLM-Enhanced Retrieval(LLM增强检索)
│      LLM处理query,传统算法做检索
│      优点:可扩展,结合实时数据
│      缺点:两个系统需要协调
│
└── 3. LLM + Embedding Hybrid(混合架构)
       LLM生成语义向量,向量库做ANN检索
       优点:语义准确 + 速度快
       缺点:向量更新延迟

1.2 冷启动场景的对比

场景传统方案LLM方案
新用户基于人口统计学推荐自然语言询问偏好
新商品无法推荐(无行为数据)基于商品描述语义推荐
小众品类稀疏数据,效果差迁移世界知识
跨域推荐数据隔离,难以迁移语义空间统一

二、冷启动解决方案

2.1 对话式偏好采集

from anthropic import Anthropic
from typing import List, Dict, Optional
import json

client = Anthropic()

class ConversationalPreferenceCollector:
    """通过对话采集用户偏好,解决冷启动"""
    
    SYSTEM_PROMPT = """你是一个专业的推荐系统用户偏好分析师。
通过自然对话了解用户偏好,并以结构化JSON格式输出分析结果。

对话原则:
1. 每次只问一个问题
2. 问题要具体、有趣,不要像问卷调查
3. 从用户回答中推断隐性偏好
4. 收集3-5轮信息后,输出完整画像

当你认为信息足够时,输出:
```json
{
  "profile_complete": true,
  "preferences": {
    "explicit": [...],   // 用户明确表达的偏好
    "inferred": [...],   // 从回答中推断的偏好
    "exclusions": [...]  // 明确不喜欢的
  },
  "embedding_tags": [...]  // 用于向量检索的标签
}

"""

def __init__(self, domain: str = "电商"):
    self.domain = domain
    self.conversation_history = []
    self.collected_profile = None

def start_conversation(self) -> str:
    """开始偏好采集对话"""
    
    initial_prompt = f"请帮我了解用户在{self.domain}领域的偏好,开始采集对话。"
    
    response = client.messages.create(
        model="claude-3-5-sonnet-20241022",
        max_tokens=500,
        system=self.SYSTEM_PROMPT,
        messages=[{"role": "user", "content": initial_prompt}]
    )
    
    assistant_message = response.content[0].text
    self.conversation_history.append({
        "role": "assistant",
        "content": assistant_message
    })
    
    return assistant_message

def process_response(self, user_input: str) -> dict:
    """
    处理用户回答
    
    Returns:
        {
            "question": 下一个问题(如果采集未完成),
            "profile": 用户画像(如果采集完成),
            "done": bool
        }
    """
    self.conversation_history.append({
        "role": "user",
        "content": user_input
    })
    
    response = client.messages.create(
        model="claude-3-5-sonnet-20241022",
        max_tokens=1000,
        system=self.SYSTEM_PROMPT,
        messages=self.conversation_history
    )
    
    assistant_message = response.content[0].text
    self.conversation_history.append({
        "role": "assistant",
        "content": assistant_message
    })
    
    # 检查是否完成了画像采集
    if "profile_complete" in assistant_message:
        try:
            # 提取JSON
            import re
            json_match = re.search(r'\{.*"profile_complete".*\}', 
                                  assistant_message, re.DOTALL)
            if json_match:
                profile = json.loads(json_match.group())
                self.collected_profile = profile
                return {"question": None, "profile": profile, "done": True}
        except:
            pass
    
    return {
        "question": assistant_message,
        "profile": None,
        "done": False
    }

使用示例

collector = ConversationalPreferenceCollector(domain="书籍") question = collector.start_conversation() print(f"AI: {question}")

模拟用户对话

responses = [ "我喜欢科技和商业类书籍,特别是关于AI和创业的", "最近在读《人工智能:一种现代方法》", "不太喜欢纯理论的书,更喜欢有实际案例的" ]

for user_response in responses: print(f"\n用户: {user_response}") result = collector.process_response(user_response)

if result["done"]:
    print("\n✅ 用户画像采集完成:")
    print(json.dumps(result["profile"], ensure_ascii=False, indent=2))
    break
else:
    print(f"AI: {result['question']}")

### 2.2 商品语义向量化

解决新商品冷启动问题:

```python
from openai import OpenAI
import numpy as np
from dataclasses import dataclass

openai_client = OpenAI()

@dataclass
class ProductEmbedding:
    product_id: str
    title: str
    embedding: np.ndarray
    metadata: dict

class ProductEmbeddingService:
    """商品语义向量化服务"""
    
    EMBEDDING_MODEL = "text-embedding-3-large"
    EMBEDDING_DIM = 3072
    
    def create_product_text(self, product: dict) -> str:
        """将商品信息转化为富语义文本"""
        
        parts = []
        
        if product.get("title"):
            parts.append(f"商品名称:{product['title']}")
        
        if product.get("category"):
            parts.append(f"类别:{product['category']}")
        
        if product.get("brand"):
            parts.append(f"品牌:{product['brand']}")
        
        if product.get("description"):
            parts.append(f"描述:{product['description'][:500]}")
        
        if product.get("tags"):
            parts.append(f"标签:{', '.join(product['tags'])}")
        
        if product.get("specs"):
            specs_str = ', '.join(f"{k}:{v}" for k, v in product['specs'].items())
            parts.append(f"规格:{specs_str}")
        
        # 使用LLM生成增强描述(提升语义丰富度)
        if len(parts) > 2:
            enhanced = self._enhance_description(product)
            if enhanced:
                parts.append(f"特征总结:{enhanced}")
        
        return '\n'.join(parts)
    
    def _enhance_description(self, product: dict) -> str:
        """使用LLM增强商品描述"""
        
        response = client.messages.create(
            model="claude-3-5-haiku-20241022",
            max_tokens=200,
            messages=[{
                "role": "user",
                "content": f"""请用50字描述这个商品的核心特征和适合人群:
商品:{product.get('title', '')}
类别:{product.get('category', '')}
描述:{product.get('description', '')[:200]}"""
            }]
        )
        return response.content[0].text
    
    def embed_product(self, product: dict) -> ProductEmbedding:
        """生成商品向量"""
        
        text = self.create_product_text(product)
        
        response = openai_client.embeddings.create(
            model=self.EMBEDDING_MODEL,
            input=text,
            encoding_format="float"
        )
        
        embedding = np.array(response.data[0].embedding)
        
        return ProductEmbedding(
            product_id=product["id"],
            title=product.get("title", ""),
            embedding=embedding,
            metadata={
                "category": product.get("category"),
                "price": product.get("price"),
                "tags": product.get("tags", [])
            }
        )
    
    def batch_embed(
        self,
        products: List[dict],
        batch_size: int = 20
    ) -> List[ProductEmbedding]:
        """批量向量化(控制API调用频率)"""
        
        results = []
        
        for i in range(0, len(products), batch_size):
            batch = products[i:i+batch_size]
            
            # 批量生成文本
            texts = [self.create_product_text(p) for p in batch]
            
            # 批量请求embedding API
            response = openai_client.embeddings.create(
                model=self.EMBEDDING_MODEL,
                input=texts,
                encoding_format="float"
            )
            
            for j, (product, emb_data) in enumerate(
                zip(batch, response.data)
            ):
                results.append(ProductEmbedding(
                    product_id=product["id"],
                    title=product.get("title", ""),
                    embedding=np.array(emb_data.embedding),
                    metadata={"category": product.get("category")}
                ))
            
            print(f"已处理 {min(i+batch_size, len(products))}/{len(products)} 个商品")
        
        return results

三、实时个性化推荐

3.1 用户意图解析

class UserIntentParser:
    """解析用户实时意图"""
    
    def parse_query(
        self,
        query: str,
        user_context: dict,
        history: List[dict]
    ) -> dict:
        """
        解析用户查询意图
        
        Returns:
            {
                "intent_type": "search|browse|compare|buy",
                "expanded_query": "语义扩展后的查询",
                "filters": {"price_max": ..., "category": ...},
                "preference_signals": [...]
            }
        """
        
        history_summary = ""
        if history:
            recent = history[-5:]
            history_summary = f"\n最近浏览:{', '.join(h.get('title', '') for h in recent)}"
        
        response = client.messages.create(
            model="claude-3-5-haiku-20241022",
            max_tokens=500,
            messages=[{
                "role": "user",
                "content": f"""分析用户的推荐需求,返回JSON格式:

用户查询:{query}
用户等级:{user_context.get('tier', 'standard')}
{history_summary}

返回:
{{
  "intent_type": "search|browse|compare|buy",
  "expanded_query": "扩展语义后的查询",
  "filters": {{}},
  "preference_signals": [],
  "suggested_categories": []
}}"""
            }]
        )
        
        try:
            return json.loads(response.content[0].text)
        except:
            return {
                "intent_type": "search",
                "expanded_query": query,
                "filters": {},
                "preference_signals": []
            }

3.2 推荐理由生成

class RecommendationExplainer:
    """生成个性化推荐理由"""
    
    def generate_explanation(
        self,
        user_profile: dict,
        product: dict,
        reason_type: str = "preference"
    ) -> str:
        """生成推荐理由"""
        
        response = client.messages.create(
            model="claude-3-5-haiku-20241022",
            max_tokens=100,
            messages=[{
                "role": "user",
                "content": f"""为以下商品生成个性化推荐理由(30字以内,自然口语化):

商品:{product.get('title')}
用户偏好:{', '.join(user_profile.get('preferences', {}).get('explicit', [])[:3])}
推荐原因类型:{reason_type}

示例格式:"因为你喜欢[偏好],这款[产品特点]很适合你""""
            }]
        )
        
        return response.content[0].text.strip()

四、生产架构设计

4.1 整体系统架构

class LLMRecommendationSystem:
    """完整的LLM推荐系统"""
    
    def __init__(self, vector_store, cache):
        self.embedding_service = ProductEmbeddingService()
        self.intent_parser = UserIntentParser()
        self.explainer = RecommendationExplainer()
        self.vector_store = vector_store
        self.cache = cache
    
    async def recommend(
        self,
        user_id: str,
        query: str,
        context: dict,
        top_k: int = 10
    ) -> List[dict]:
        """主推荐流程"""
        
        # 1. 加载用户画像
        user_profile = await self._load_user_profile(user_id)
        browse_history = await self._load_history(user_id)
        
        # 2. 解析意图
        intent = self.intent_parser.parse_query(
            query, context, browse_history
        )
        
        # 3. 生成查询向量
        query_embedding = self._embed_query(
            intent["expanded_query"]
        )
        
        # 4. 向量检索
        candidates = self.vector_store.search(
            query_embedding,
            top_k=top_k * 3,
            filters=intent.get("filters")
        )
        
        # 5. 重排序(基于用户偏好)
        reranked = self._rerank_by_preference(
            candidates, user_profile
        )[:top_k]
        
        # 6. 生成推荐理由
        results = []
        for product in reranked:
            explanation = self.explainer.generate_explanation(
                user_profile, product
            )
            results.append({
                **product,
                "recommendation_reason": explanation,
                "score": product.get("score", 0)
            })
        
        return results
    
    def _rerank_by_preference(
        self,
        candidates: List[dict],
        user_profile: dict
    ) -> List[dict]:
        """基于用户偏好重排序"""
        
        preferences = set(
            user_profile.get("preferences", {}).get("explicit", []) +
            user_profile.get("preferences", {}).get("inferred", [])
        )
        exclusions = set(
            user_profile.get("preferences", {}).get("exclusions", [])
        )
        
        def score(product: dict) -> float:
            base_score = product.get("score", 0.5)
            tags = set(product.get("metadata", {}).get("tags", []))
            
            pref_match = len(tags & preferences)
            excl_match = len(tags & exclusions)
            
            return base_score + pref_match * 0.1 - excl_match * 0.3
        
        return sorted(candidates, key=score, reverse=True)

五、性能优化策略

5.1 关键延迟控制

import asyncio
from functools import lru_cache

class OptimizedRecommender:
    """高性能推荐器"""
    
    # LLM调用缓存(相同意图缓存1小时)
    @lru_cache(maxsize=1000)
    def _cached_intent_parse(self, query_hash: str) -> dict:
        pass
    
    async def fast_recommend(
        self,
        user_id: str,
        query: str,
        timeout: float = 2.0
    ) -> List[dict]:
        """2秒内完成推荐"""
        
        # 并发执行不相互依赖的任务
        user_profile_task = asyncio.create_task(
            self._load_user_profile(user_id)
        )
        intent_task = asyncio.create_task(
            self._parse_intent_async(query)
        )
        
        # 等待两个任务完成
        try:
            user_profile, intent = await asyncio.wait_for(
                asyncio.gather(user_profile_task, intent_task),
                timeout=timeout * 0.5  # 各阶段只用一半时间
            )
        except asyncio.TimeoutError:
            # 降级:使用缓存的向量直接检索
            user_profile = self._get_default_profile()
            intent = {"expanded_query": query, "filters": {}}
        
        # 向量检索(快速)
        embedding = self._embed_query(intent["expanded_query"])
        candidates = self.vector_store.search(embedding, top_k=20)
        
        # 快速重排(无LLM调用)
        reranked = self._fast_rerank(candidates, user_profile)
        
        return reranked[:10]

六、总结

LLM为推荐系统带来的核心价值:

  1. 零冷启动:通过对话采集偏好,新用户立即获得个性化推荐
  2. 语义理解:超越关键词匹配,理解用户真实意图
  3. 新商品推荐:基于语义向量,新商品立即可被推荐
  4. 推荐可解释性:自动生成自然语言推荐理由,提升用户信任

工程实践要点:

  • 将LLM调用集中在低频高价值的环节(偏好采集、理由生成)
  • 实时路径使用向量检索,不依赖LLM保证低延迟
  • 用户画像做持久化缓存,避免重复采集
  • 设计降级策略,LLM不可用时保持基础推荐能力

LLM推荐系统不是要替代传统协同过滤,而是在冷启动、语义理解、可解释性三个维度上形成有效补充,共同构建更智能的推荐体系。