摘要昨天搭了 AI 客服的 RAG 架构今天把同一套向量检索能力用到商品搜索和推荐上。传统搜索只认关键词用户搜夏天穿的透气鞋子就抓瞎。语义搜索把商品描述变成向量按意思相近来检索。再配合用户浏览和购买历史做个性化推荐。整套方案用 GLM Embedding免费额度 Redis 向量存储实现成本约等于零。搜好看的裙子传统搜索只能干瞪眼做过电商的都知道搜索栏是用户用得最多的功能。但传统搜索有个致命问题——它只认关键词不认意思。用户搜索好看的裙子 传统搜索匹配逻辑 ✅ 法式碎花连衣裙 好看的裙子春季新款 ← 关键词命中 ❌ 复古波点半身裙 气质百搭显瘦 ← 没命中 ❌ 轻薄雪纺长裙 夏日通勤穿搭 ← 没命中后面两个明明就是好看的裙子但关键词没对上搜索结果为空。更离谱的是用户搜索适合跑步的鞋 商品标题轻量缓震运动跑鞋 男款透气网面跑步和跑鞋差一个字关键词搜索可能就匹配不上。商品库做到几百个 SKU 后光靠 Django 的icontains模糊查询用户体验就到瓶颈了。今天搞一套真正懂人话的搜索系统。Embedding 到底是个啥大白话解释 EmbeddingEmbedding翻译过来叫嵌入。别被名字吓到其实特好理解。想象一个巨大的空间每件商品都是空间里的一个点。位置相近的点意思就相近。透气 ↑ 跑鞋 · · 运动鞋 | ← 正式 ──┼── 休闲 → | 皮鞋 · · 板鞋 ↓跑鞋和运动鞋在这个空间里挨得很近。跑鞋和皮鞋隔得远。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开头自动切到精确搜索。推荐算法基础推荐的核心逻辑“你看过 AA 和 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 clientZhipuAI(api_keyyour-api-key-here)defget_embedding(text:str)-list:把文本转成 1024 维向量responseclient.embeddings.create(modelembedding-3,# 1024 维inputtext,)returnresponse.data[0].embeddingdefcosine_similarity(v1,v2)-float:余弦相似度越接近 1 越相似越接近 0 越不相关v1,v2np.array(v1),np.array(v2)returnfloat(np.dot(v1,v2)/(np.linalg.norm(v1)*np.linalg.norm(v2)))# 验证一下vec1get_embedding(适合跑步的透气鞋子)vec2get_embedding(轻量缓震运动跑鞋)vec3get_embedding(复古真皮商务皮鞋)print(f跑鞋 vs 运动鞋{cosine_similarity(vec1,vec2):.4f})# ≈ 0.91print(f跑鞋 vs 皮鞋{cosine_similarity(vec1,vec3):.4f})# ≈ 0.35余弦相似度就是两个向量之间的夹角。夹角越小值越接近 1说明两段文字越相似。不用理解数学细节记住越接近 1 越相关。商品向量化入库把商品描述转成向量存到 Redisimportredisimportjson redis_clientredis.Redis(hostlocalhost,port6379,db0,decode_responsesTrue)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_textf{product[name]}{product[description]}{ .join(product[tags])}vectorget_embedding(search_text)product_keyfproduct:{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:int5)-list:语义搜索搜索词 → 向量 → 和所有商品算余弦相似度 → 返回 Top-Kquery_vectorget_embedding(query)product_idsredis_client.smembers(product_ids)results[]forpidinproduct_ids:product_dataredis_client.hgetall(fproduct:{pid})ifvectornotinproduct_data:continuescorecosine_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(keylambdax:x[score],reverseTrue)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:int5)-list: 个性化推荐基于用户行为的协同推荐 购买权重 2.0 浏览权重 1.0买过 更强的偏好信号 behaviorUSER_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.responseimportResponseapi_view([GET])permission_classes([AllowAny])defsearch_products(request):语义搜索 GET /api/search/?q适合跑步的透气鞋子queryrequest.query_params.get(q,).strip()ifnotquery:returnResponse({error:搜索词不能为空},status400)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_k5user_idfuser_{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 质量promptf根据商品信息生成丰富的搜索描述用于向量索引。 要求包含使用场景、适用人群、核心卖点加入用户可能搜索的关键词。100 字以内。 商品名称{name}原始描述{raw_desc}直接输出优化后的描述。responseclient.chat.completions.create(modelglm-4-flash,messages[{role:user,content:prompt}],temperature0.5,max_tokens200,)returnresponse.choices[0].message.content# 效果raw好看的衣服质量很好便宜卖enrichedenrich_product_description(raw,法式碎花连衣裙)# 优化前好看的衣服质量很好便宜卖# 优化后春夏法式复古碎花连衣裙V领收腰显瘦雪纺面料轻盈透气。# 适合约会、通勤、度假穿搭百搭气质款小个子也能驾驭。优化后包含了场景约会、通勤、卖点显瘦、透气、关键词春夏、百搭向量语义信息量大得多。学完来练练手 入门给推荐系统加最近购买权重30 天内 ×1.530 天外 ×1.0 进阶把 Redis 向量存储换成 Milvus 或 Qdrant专业向量数据库支持 Docker 一键部署ANN 近似最近邻索引查询速度比暴力遍历快 100 倍。商品量超 1 万就该考虑 挑战实现搜索推荐混合模式——搜索时附带 2 个个性化推荐商品配套文件day16_search_recommend.py完整实现可参考学习。智谱 Embedding 文档Redis 向量搜索redis.io 官方文档。本系列为梅雅达编程笔记原创首发于 CSDN。有问题评论区见
阅读完成 · 觉得有帮助?