摘要:昨天搭了 AI 客服的 RAG 架构,今天把同一套向量检索能力用到商品搜索和推荐上。传统搜索只认关键词,用户搜"夏天穿的透气鞋子"就抓瞎。语义搜索把商品描述变成向量,按"意思相近"来检索。再配合用户浏览和购买历史做个性化推荐。整套方案用 GLM Embedding(免费额度)+ Redis 向量存储实现,成本约等于零。
搜"好看的裙子",传统搜索只能干瞪眼
做过电商的都知道,搜索栏是用户用得最多的功能。
但传统搜索有个致命问题——它只认关键词,不认"意思"。
用户搜索:好看的裙子 传统搜索匹配逻辑: ✅ "法式碎花连衣裙 好看的裙子春季新款" ← 关键词命中 ❌ "复古波点半身裙 气质百搭显瘦" ← 没命中 ❌ "轻薄雪纺长裙 夏日通勤穿搭" ← 没命中后面两个明明就是"好看的裙子",但关键词没对上,搜索结果为空。
更离谱的是:
用户搜索:适合跑步的鞋 商品标题:轻量缓震运动跑鞋 男款透气网面"跑步"和"跑鞋"差一个字,关键词搜索可能就匹配不上。
商品库做到几百个 SKU 后,光靠 Django 的icontains模糊查询,用户体验就到瓶颈了。今天搞一套真正"懂人话"的搜索系统。
Embedding 到底是个啥
大白话解释 Embedding
Embedding,翻译过来叫"嵌入"。别被名字吓到,其实特好理解。
想象一个巨大的空间,每件商品都是空间里的一个点。位置相近的点,意思就相近。
透气 ↑ 🏃跑鞋 · · 运动鞋 | ← 正式 ──┼── 休闲 → | 👠皮鞋 · · 板鞋 ↓"跑鞋"和"运动鞋"在这个空间里挨得很近。"跑鞋"和"皮鞋"隔得远。
Embedding 做的事情就是:把一段文字变成一组数字(向量),让"意思相近"的文字在数字空间里也离得近。
通常是 1024 维。别怕,你不用手算,模型自动帮你转。
"适合跑步的透气鞋子" → [0.023, -0.15, 0.87, ..., 0.04] # 1024 个数 "轻量缓震运动跑鞋" → [0.019, -0.12, 0.85, ..., 0.06] # 很接近! "复古真皮商务皮鞋" → [-0.31, 0.44, -0.02, ..., 0.71] # 差得远语义搜索 vs 关键词搜索
| 对比项 | 关键词搜索 | 语义搜索 |
|---|---|---|
| 匹配方式 | 精确匹配文字 | 匹配"意思" |
| 用户搜"跑步鞋" | 只能匹配含"跑步鞋"的 | 也能匹配"运动跑鞋"“慢跑鞋” |
| 同义词处理 | 搞不定 | 自动识别 |
| 适用场景 | 精确搜索(订单号) | 模糊搜索(逛商品) |
最佳实践:两者结合。搜索栏默认走语义搜索,如果用户输入的是订单号格式(
MYD开头),自动切到精确搜索。
推荐算法基础
推荐的核心逻辑:“你看过 A,A 和 B 很像,所以你大概率也喜欢 B”。
Step 1:计算商品之间的相似度 商品 A 的向量 · 商品 B 的向量 = 相似度分数 Step 2:基于用户行为推荐 用户最近看了 [跑鞋, 运动裤, 瑜伽垫] → 取这三个商品各自最相似的其他商品 → 按相似度加权排序 → 排除已看过的 → 返回 Top-N本质就是 Day15 的向量检索换了个用法——昨天是"问题找答案",今天是"商品找商品"。
动手实战
环境准备
pipinstallzhipuai redis numpy| 库 | 作用 |
|---|---|
zhipuai | 调用 GLM Embedding(免费额度) |
redis | 向量存储 |
numpy | 向量计算 |
Day15 用了
sentence-transformers做本地向量化,今天换成 GLM Embedding 云端接口。GLM 的中文 Embedding 质量更好(1024 维 vs 768 维),免费额度够用。
GLM Embedding 接口对接
GLM Embedding 免费额度 500K tokens。把商品描述丢进去,出来一个 1024 维向量。
fromzhipuaiimportZhipuAIimportnumpyasnp client=ZhipuAI(api_key="your-api-key-here")defget_embedding(text:str)->list:"""把文本转成 1024 维向量"""response=client.embeddings.create(model="embedding-3",# 1024 维input=text,)returnresponse.data[0].embeddingdefcosine_similarity(v1,v2)->float:"""余弦相似度:越接近 1 越相似,越接近 0 越不相关"""v1,v2=np.array(v1),np.array(v2)returnfloat(np.dot(v1,v2)/(np.linalg.norm(v1)*np.linalg.norm(v2)))# 验证一下vec1=get_embedding("适合跑步的透气鞋子")vec2=get_embedding("轻量缓震运动跑鞋")vec3=get_embedding("复古真皮商务皮鞋")print(f"跑鞋 vs 运动鞋:{cosine_similarity(vec1,vec2):.4f}")# ≈ 0.91print(f"跑鞋 vs 皮鞋:{cosine_similarity(vec1,vec3):.4f}")# ≈ 0.35余弦相似度就是两个向量之间的"夹角"。夹角越小(值越接近 1),说明两段文字越相似。不用理解数学细节,记住:越接近 1 越相关。
商品向量化入库
把商品描述转成向量,存到 Redis:
importredisimportjson redis_client=redis.Redis(host='localhost',port=6379,db=0,decode_responses=True)PRODUCTS=[{"id":1,"name":"轻量缓震运动跑鞋","description":"男款透气网面跑鞋,EVA缓震中底,轻量230g","category":"运动鞋","price":299.00,"tags":["跑步","透气","轻量","缓震"]},{"id":2,"name":"法式碎花连衣裙","description":"春夏法式复古碎花长裙,V领收腰显瘦,雪纺面料","category":"女装","price":199.00,"tags":["连衣裙","碎花","显瘦","春夏"]},# ... 更多商品]defbuild_product_vector_index():"""构建商品向量索引"""forproductinPRODUCTS:# 名称 + 描述 + 标签,信息越丰富,向量语义表达越准确search_text=f"{product['name']}{product['description']}{' '.join(product['tags'])}"vector=get_embedding(search_text)product_key=f"product:{product['id']}"redis_client.hset(product_key,mapping={"name":product["name"],"category":product["category"],"price":str(product["price"]),"vector":json.dumps(vector),})redis_client.sadd("product_ids",product["id"])为什么把名称+描述+标签都拼起来?单独用"跑鞋"做 Embedding 信息量太少。拼上"透气网面"“缓震中底"和标签"跑步”“轻量”,向量语义就丰富多了。
语义搜索 API
搜索词也转成向量,和所有商品算相似度:
defsemantic_search(query:str,top_k:int=5)->list:"""语义搜索:搜索词 → 向量 → 和所有商品算余弦相似度 → 返回 Top-K"""query_vector=get_embedding(query)product_ids=redis_client.smembers("product_ids")results=[]forpidinproduct_ids:product_data=redis_client.hgetall(f"product:{pid}")if"vector"notinproduct_data:continuescore=cosine_similarity(query_vector,json.loads(product_data["vector"]))results.append({"id":int(pid),"name":product_data["name"],"category":product_data["category"],"price":float(product_data["price"]),"score":round(score,4),})results.sort(key=lambdax:x["score"],reverse=True)returnresults[:top_k]搜索效果:
用户搜索:适合跑步的透气鞋子 1. 轻量缓震运动跑鞋(运动鞋)¥299 | 相关度:0.91 ✅ 2. 瑜伽裤高腰提臀(运动装)¥129 | 相关度:0.68 ✅ 运动相关 3. 轻薄羽绒服男款(男装)¥349 | 相关度:0.42 ❌ 用户搜索:夏天穿的漂亮裙子 1. 法式碎花连衣裙(女装)¥199 | 相关度:0.89 ✅“适合跑步的透气鞋子” → 第一名是"轻量缓震运动跑鞋"。几乎没有重复关键词,但语义搜索精准命中。
个性化推荐 API
推荐的核心:根据用户浏览/购买记录,找到相似商品。
USER_BEHAVIOR={"user_001":{"viewed":[1,4],"purchased":[]},# 看过跑鞋+瑜伽裤"user_002":{"viewed":[2],"purchased":[2]},# 买过连衣裙}defrecommend_products(user_id:str,top_k:int=5)->list:""" 个性化推荐:基于用户行为的协同推荐 购买权重 2.0 > 浏览权重 1.0(买过 = 更强的偏好信号) """behavior=USER_BEHAVIOR.get(user_id)ifnotbehavior:return[]# 兜底返回热门商品# ① 收集交互商品集合(浏览 ∪ 购买),推荐时排除已交互的# ② 加权:浏览 +1.0,购买 +2.0# ③ 对每个候选商品:与所有交互商品算余弦相似度,按权重累加# ④ 按总分排序 → 返回 Top-K...为什么买过的权重更高?浏览可能只是随便看看,买了就是真的喜欢。权重 2:1 是经验值,实际可以 A/B 测试调。
Django DRF 接口
fromrest_framework.decoratorsimportapi_view,permission_classesfromrest_framework.permissionsimportIsAuthenticated,AllowAnyfromrest_framework.responseimportResponse@api_view(['GET'])@permission_classes([AllowAny])defsearch_products(request):"""语义搜索 GET /api/search/?q=适合跑步的透气鞋子"""query=request.query_params.get('q','').strip()ifnotquery:returnResponse({'error':'搜索词不能为空'},status=400)returnResponse({'results':semantic_search(query,int(request.query_params.get('top_k',5)))})@api_view(['GET'])@permission_classes([IsAuthenticated])defrecommend_view(request):"""个性化推荐 GET /api/recommend/?top_k=5"""user_id=f"user_{request.user.id}"returnResponse({'results':recommend_products(user_id,int(request.query_params.get('top_k',5)))})前端两个请求:搜索框走/api/search/,首页推荐位走/api/recommend/。
AI 加速:优化商品描述的 Embedding 质量
Embedding 质量取决于商品描述。商家写"好看的衣服便宜卖",信息量太低,向量也"空"。
用 GLM-4.7-Flash 把简陋描述改写成信息丰富的版本:
defenrich_product_description(raw_desc:str,name:str)->str:"""用 AI 丰富商品描述,提升 Embedding 质量"""prompt=f"""根据商品信息生成丰富的搜索描述(用于向量索引)。 要求:包含使用场景、适用人群、核心卖点,加入用户可能搜索的关键词。100 字以内。 商品名称:{name}原始描述:{raw_desc}直接输出优化后的描述。"""response=client.chat.completions.create(model="glm-4-flash",messages=[{"role":"user","content":prompt}],temperature=0.5,max_tokens=200,)returnresponse.choices[0].message.content# 效果raw="好看的衣服,质量很好,便宜卖"enriched=enrich_product_description(raw,"法式碎花连衣裙")# 优化前:好看的衣服,质量很好,便宜卖# 优化后:春夏法式复古碎花连衣裙,V领收腰显瘦,雪纺面料轻盈透气。# 适合约会、通勤、度假穿搭,百搭气质款,小个子也能驾驭。优化后包含了场景(约会、通勤)、卖点(显瘦、透气)、关键词(春夏、百搭),向量语义信息量大得多。
学完来练练手
- 🟢 入门:给推荐系统加"最近购买"权重(30 天内 ×1.5,30 天外 ×1.0)
- 🟡 进阶:把 Redis 向量存储换成 Milvus 或 Qdrant(专业向量数据库,支持 Docker 一键部署,ANN 近似最近邻索引,查询速度比暴力遍历快 100 倍。商品量超 1 万就该考虑)
- 🔴 挑战:实现"搜索+推荐"混合模式——搜索时附带 2 个个性化推荐商品
配套文件day16_search_recommend.py完整实现可参考学习。智谱 Embedding 文档;Redis 向量搜索:redis.io 官方文档。
本系列为梅雅达编程笔记原创,首发于 CSDN。有问题?评论区见 🍃