商品推荐最容易做成两种极端:要么纯规则(if预算<500推A,僵化),要么纯大模型(让模型直接"推荐一款耳机",模型可能编造不存在的产品)。可靠的智能推荐是三层结构:大模型负责理解和表达,规则系统负责约束正确性,各干各擅长的事。
一、第一层:约束提取——把自然语言变成结构化条件
用户说"想找个跑步戴的、500块以内、续航长点的耳机",大模型用函数调用(Function Calling)输出结构化约束:使用场景=运动、价格上限=500、偏好权重={续航:高}。
约束分两类:硬约束(必须满足:价格≤500、运动场景适用)和软约束(尽量满足:续航越长越好、偏好黑色)。这个区分决定后两层的处理方式——硬约束用于过滤,软约束用于排序。约束提取允许跨轮累积:用户第一轮只说"跑步用",第二轮补"预算500内",程序合并两轮约束而不是重新开始。
二、第二层:过滤与排序——规则保证正确性
过滤阶段用硬约束在商品库做确定性筛选:价格区间、类目、规格有货。这一层绝不让大模型参与——产品价格库存是硬事实,模型"推荐"一个499元实际卖699元的产品就是事故。过滤后的候选集通常有5-20个。
排序阶段对候选集按软约束打分:续航权重0.4、品牌匹配0.3、用户历史偏好0.3,加权算分取Top3。打分函数是确定性的,大模型不参与——保证同一用户同一条件每次推荐结果一致,不会出现"上午问和下午问推荐不同"的诡异体验。
三、第三层:推荐解释——大模型只做表达
Top3产品确定后,大模型才介入:根据用户画像和约束权重,为每个推荐生成"为什么适合你"的解释。解释不是复述参数表,是把参数翻译成用户价值——"续航40小时"翻译成"一周跑5次不用充电"。
解释生成的输入是结构化的推荐结果(产品+分数+命中的用户偏好),输出严格基于这些事实。模型不允许添加产品库以外的信息——这是防止幻觉的边界。
三层架构对照
层级 | 执行者 | 输入 | 输出 |
|---|---|---|---|
约束提取 | 大模型 | 自然语言 | 硬/软约束结构 |
过滤排序 | 规则系统 | 约束+商品库 | Top3产品 |
推荐解释 | 大模型 | Top3+用户画像 | 自然语言推荐理由 |
三层推荐实现
TOOLS = [{ "name": "submit_constraints", "description": "提交用户的选购条件", "parameters": { "type": "object", "properties": { "hard": {"type": "object", "description": "必须满足的条件:预算/类目/规格"}, "soft": {"type": "object", "description": "偏好权重:续航/外观/品牌等"} }, "required": ["hard"] } }] class ProductRecommender: def extract(self, dialog_history): """第一层:跨轮累积的约束提取""" result = llm_function_call( messages=dialog_history, tools=TOOLS) new_constraints = result.arguments # 与历史约束合并,而非覆盖 merged = merge_constraints( self.saved_constraints, new_constraints) self.saved_constraints = merged return merged def filter_and_rank(self, constraints): """第二层:确定性过滤+打分""" # 硬约束过滤 candidates = db.query_sql(""" SELECT * FROM products WHERE category = ? AND price <= ? AND stock > 0""", [constraints["hard"]["category"], constraints["hard"]["budget"]]) # 软约束加权排序 for p in candidates: p["score"] = ( 0.4 * self.score_battery(p, constraints["soft"]) + 0.3 * self.score_brand(p, constraints["soft"]) + 0.3 * self.score_pref(p, self.user_history)) return sorted(candidates, key=lambda x: x["score"], reverse=True)[:3] def explain(self, top3, constraints, profile): """第三层:基于事实生成解释,禁止添加库外信息""" facts = [{"name": p["name"], "price": p["price"], "specs": p["specs"], "matched": self.matched_prefs(p, constraints)} for p in top3] return llm_chat( system="根据以下推荐结果数据解释为什么适合该用户," "只能使用数据中存在的参数,不得编造功能或价格", user=f"用户偏好:{constraints['soft']}," f"画像:{profile},推荐数据:{json.dumps(facts)}") def recommend(self, wxid, dialog): c = self.extract(dialog) # 硬约束不全→追问,不硬推荐 if not self.hard_complete(c): return self.ask_missing(c) top3 = self.filter_and_rank(c) if not top3: return self.nearest_alternative(c) # 无货时推荐替代 return self.explain(top3, c, self.profile(wxid))落地建议
三层架构的核心原则是"事实归规则,表达归模型"——过滤和排序绝不让模型碰,模型只在首尾做理解和表达,幻觉风险就被关在笼子里。约束跨轮累积是体验关键,用户分几次说出的条件要能合并。硬约束不完整时宁可追问也不要放宽条件硬推——推荐了不满足预算的产品比不推荐更伤信任。商品库数据通过电商系统API同步,微信侧的对话收发基于Eyun这类个人微信API接口实现,两端打通就是完整的智能导购闭环。