融合DeepSeek与Neo4j知识图谱的电商智能对话推荐系统实战(Vue+SpringBoot)
1. 项目缘起:为什么我们需要一个“会聊天”的推荐系统?
不知道你有没有过这样的购物体验?打开一个电商App,首页推荐的商品要么是你刚买过的,要么是八竿子打不着的。你想找一件适合海边度假穿的、有点设计感但不要太夸张的连衣裙,结果在搜索框里敲了半天关键词,翻了好几页,还是没找到完全符合心意的。这时候你可能会想,要是能有个懂行的“导购”聊两句就好了。
传统的推荐系统,比如协同过滤或者基于内容的推荐,它们很厉害,能根据你的历史行为算出你可能喜欢什么。但它们有个“硬伤”:不懂人话。你没法告诉它“我想要一件适合通勤、但下班后直接去约会也能穿的上衣”,这种复杂、模糊、多条件的意图,传统系统很难理解。而这,正是大语言模型(LLM)的强项。
我去年接手了一个电商平台的推荐系统优化项目,核心目标就是解决这个“最后一公里”的问题——让推荐变得更智能、更自然、更像人与人之间的对话。经过一番技术选型,我们最终敲定了 DeepSeek 作为“大脑”,Neo4j 作为“记忆库”,用 Vue3 和 SpringBoot3 搭建前后端。今天,我就把这个从零到一的实战过程,掰开揉碎了分享给你。你会发现,把前沿的AI技术和成熟的后端开发结合起来,并没有想象中那么难。
这个系统的核心价值在于,它把冰冷的算法推荐,变成了有温度的对话服务。用户不再是被动地接收算法推送的列表,而是可以通过自然语言,主动地、精确地表达自己的需求。系统则像一个专业的购物顾问,在理解你意图的基础上,从庞大的商品知识图谱中,精准定位,并给出有理有据的推荐。接下来,我就带你一步步走进这个系统的内部,看看它是如何被构建起来的。
2. 系统架构全景:大脑、记忆与手脚如何协同工作?
在开始敲代码之前,我们必须把整个系统的骨架搭清楚。一个好的架构设计,能让你在开发过程中少踩很多坑。我们这个系统,可以形象地理解为一个人:DeepSeek是大脑,负责理解和思考;Neo4j是记忆库,存储着所有商品、用户、属性之间错综复杂的关系;SpringBoot后端是神经中枢,协调各方;Vue前端是五官和手脚,负责与用户交互。
整个数据流是这样的:用户在Vue前端用自然语言提问,比如“我想给爸爸买一个生日礼物,他喜欢钓鱼,预算500元左右”。这个请求通过HTTP发送到SpringBoot后端。后端不是直接处理,而是扮演一个“调度员”的角色。它首先把用户的问题扔给DeepSeek大模型,并给出一个指令:“请从用户的这句话里,提取出与商品推荐相关的关键实体和属性。”
DeepSeek会分析并返回结构化的信息,比如:{“人物”: “爸爸”, “场景”: “生日礼物”, “兴趣”: “钓鱼”, “价格区间”: “500元”}。拿到这个“查询意图”后,SpringBoot后端会把它转换成一个或多个Cypher查询语句(这是Neo4j的查询语言)。这个转换过程是关键,我们后面会细说。
接着,后端连接Neo4j数据库,执行Cypher查询。Neo4j的知识图谱里,节点可能是“商品”、“品牌”、“品类”、“用户”,边可能是“属于”、“具有属性”、“被购买过”、“相似于”。通过查询,我们能快速找到“钓鱼”相关的商品(如鱼竿、渔具包),并且这些商品的价格在500元上下,再结合“生日礼物”这个场景,或许会优先推荐礼盒装或口碑好的品牌。Neo4j返回商品ID列表后,后端再从MySQL中获取这些商品的详情信息(图片、描述、库存等),组装成最终的回答。
但故事还没完。为了让回答更人性化,后端会再次调用DeepSeek,把商品列表和最初的用户问题一起交给它:“这是根据你的要求筛选出的商品,请生成一段友好、推荐理由充分的回复。” DeepSeek就会生成类似这样的文字:“根据您为喜欢钓鱼的爸爸挑选生日礼物的需求,我找到了以下几款500元左右的精品渔具,它们口碑不错,作为礼物很体面哦!” 最后,这段带有推荐理由的商品列表,被返回给前端,展示给用户。整个流程,形成了一个“用户提问 -> LLM理解 -> 知识图谱查询 -> LLM润色回答”的智能闭环。
3. 核心基石:用Neo4j构建商品知识图谱
知识图谱是这个系统的“记忆”所在,也是实现精准推荐的基础。为什么用Neo4j而不是传统的关系型数据库?因为商品之间的关系是网状的,而不是表格状的。一件“纯棉衬衫”,它属于“男装”品类,是“商务休闲”风格,品牌是“XX”,材质包含“棉”,适合“通勤”场景。这些关系用SQL的多表关联查询会非常复杂和低效,而用Neo4j的图查询则直观又快速。
3.1 图谱设计与数据建模
我们的图谱设计遵循“简洁实用”原则,主要包含以下几类节点和关系:
-
节点类型:
商品(Product):核心节点,属性有id, name, price, description等。品类(Category):如“上衣”、“裤装”、“电子产品”。品牌(Brand)。属性(Attribute):如“材质:棉”、“颜色:黑色”、“风格:复古”。这里我把属性和值作为一个整体节点,比如“材质:棉”,这样查询更灵活。用户(User):从业务系统同步过来的用户节点,包含基础画像。
-
关系类型:
属于(BELONGS_TO):商品 -> 品类。拥有品牌(BRAND_IS):商品 -> 品牌。具有属性(HAS_ATTRIBUTE):商品 -> 属性。购买过(BOUGHT):用户 -> 商品。浏览过(VIEWED):用户 -> 商品。相似于(SIMILAR_TO):商品 -> 商品。这个关系可以通过内容相似度算法(如基于商品标题、描述的文本向量化计算余弦相似度)预先计算好并存入图中。
在Neo4j中创建这个图谱结构非常直观。你可以先用Cypher语句手动创建,但更实际的做法是从现有的MySQL商品库中同步数据。我们写了一个SpringBoot的定时任务,每天凌晨同步增量数据。
// 示例:SpringBoot中使用Neo4j的Java Driver同步商品数据
@Repository
public class Neo4jProductRepository {
@Autowired
private Neo4jTemplate neo4jTemplate;
public void syncProduct(ProductDTO product) {
String cypher = """
MERGE (p:Product {id: $id})
SET p.name = $name, p.price = $price
WITH p
MERGE (c:Category {name: $categoryName})
MERGE (p)-[:BELONGS_TO]->(c)
// ... 类似地处理品牌、属性等关系
""";
Map<String, Object> parameters = new HashMap<>();
parameters.put("id", product.getId());
parameters.put("name", product.getName());
// ... 设置其他参数
neo4jTemplate.execute(cypher, parameters);
}
}
3.2 图谱查询:把自然语言变成Cypher语句
这是最具挑战也最有趣的一步。我们需要把DeepSeek提取的“用户意图”,动态地生成Cypher查询。我的策略是使用“模板化”的Cypher,然后由代码根据意图进行填充。
例如,用户说“我想要一件黑色的纯棉衬衫”。DeepSeek提取出属性:颜色:黑色 和 材质:棉,以及品类:衬衫。那么生成的Cypher可能是:
MATCH (p:Product)-[:BELONGS_TO]->(c:Category {name: '衬衫'})
MATCH (p)-[:HAS_ATTRIBUTE]->(a1:Attribute {value: '黑色'})
MATCH (p)-[:HAS_ATTRIBUTE]->(a2:Attribute {value: '棉'})
RETURN p
LIMIT 20
如果用户还说“价格别超过300”,那就再加上一个 WHERE p.price <= 300。
在实际代码中,我构建了一个CypherQueryBuilder类。它接收一个UserIntent对象(包含提取出的品类、属性、价格区间等字段),然后像拼乐高一样,组装出完整的MATCH、WHERE、RETURN子句。这里的关键是要处理好条件的“与”(AND)和“或”(OR)关系,以及避免查询性能问题。对于复杂的多条件查询,Neo4j的索引优化就非常重要,记得在Product.id、Category.name、Attribute.value等常用查询字段上创建索引。
4. 智能中枢:让DeepSeek成为你的对话引擎
DeepSeek在这个系统里扮演了两个核心角色:一是作为自然语言理解(NLU)模块,解析用户意图;二是作为自然语言生成(NLG)模块,润色推荐回复。用好它的关键在于“提示词工程”。
4.1 意图解析提示词设计
你不能简单地把用户问题扔给DeepSeek说“分析一下”。必须给它明确的指令和格式要求。我们设计的提示词模板如下:
你是一个电商推荐系统的智能分析引擎。请严格按以下步骤分析用户输入:
1. 识别用户提到的商品品类(如:手机、连衣裙、运动鞋)。如果没有明确提及,则输出“未知”。
2. 识别用户提到的所有商品属性,格式为“属性名:属性值”(如“颜色:白色”、“材质:纯棉”、“风格:复古”)。
3. 识别用户提到的价格区间(如“100元左右”、“不超过500”、“便宜点的”),并尝试转化为数字区间,如[0,100]。如果无法转化或未提及,输出“未知”。
4. 识别用户提到的品牌。如果未提及,输出“未知”。
5. 将以上信息以JSON格式输出,键名为:category, attributes(这是一个数组), price_range, brand。
用户输入:{user_input}
在SpringBoot中,我们通过HTTP调用DeepSeek的API(记得使用官方推荐的SDK或RestTemplate,并妥善管理API Key)。返回的JSON字符串,就是我们前面提到的UserIntent对象的最佳来源。
@Service
public class DeepSeekService {
@Value("${deepseek.api.key}")
private String apiKey;
public UserIntent parseUserIntent(String userQuery) throws IOException {
String prompt = buildIntentPrompt(userQuery); // 构建上面的提示词
String apiUrl = "https://api.deepseek.com/v1/chat/completions";
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
headers.setBearerAuth(apiKey);
Map<String, Object> requestBody = new HashMap<>();
requestBody.put("model", "deepseek-chat");
List<Map<String, String>> messages = new ArrayList<>();
messages.add(Map.of("role", "user", "content", prompt));
requestBody.put("messages", messages);
requestBody.put("temperature", 0.1); // 低随机性,保证输出稳定
HttpEntity<Map<String, Object>> request = new HttpEntity<>(requestBody, headers);
RestTemplate restTemplate = new RestTemplate();
ResponseEntity<Map> response = restTemplate.postForEntity(apiUrl, request, Map.class);
// 解析返回的JSON,提取出Assistant回复中的JSON内容
String content = (String) ((Map)((List)((Map)response.getBody()).get("choices")).get(0)).get("message");
// 这里需要从content中提取出JSON部分,可能需要进行字符串处理或使用JSON库解析
// 假设我们提取到了jsonStr
String jsonStr = extractJsonFromContent(content);
return objectMapper.readValue(jsonStr, UserIntent.class);
}
}
4.2 回复生成与“思维链”引导
当Neo4j返回商品列表后,直接扔给用户一堆商品ID和图片是不够的。我们需要DeepSeek来生成一段有说服力的推荐语。这里的提示词要引导模型进行“思维链”推理。
你是一位专业的电商导购。请根据以下用户需求和商品列表,生成一段亲切、专业的推荐回复。
用户原话:{user_input}
根据分析,用户可能在意这些点:{extracted_intent_summary}
以下是为你筛选的商品列表(JSON格式):
{product_list_json}
请按以下结构组织回复:
1. 首先,用一句话概括你根据用户的哪些需求进行了筛选。
2. 然后,重点介绍2-3款最具代表性的商品,每款商品用1-2句话说明其核心卖点,并关联用户的需求。例如:“这款黑色纯棉衬衫(商品A),正好符合您想要的‘透气舒适’要求,而且版型挺括,很适合通勤。”
3. 最后,可以给用户一些后续互动的建议,比如“如果您对某款商品的细节有更多疑问,可以随时问我”。
注意:回复语言要口语化,像朋友聊天一样,不要出现“根据算法”、“系统推荐”等机械用语。
通过这样的提示,DeepSeek生成的回复就非常自然,像一个真正的导购在说话,极大地提升了用户体验。我在实测中发现,温度参数temperature设置在0.7左右,能在创造性和稳定性之间取得很好的平衡。
5. 前后端搭建:Vue3与SpringBoot的工程实践
有了强大的“大脑”和“记忆”,我们需要为它们打造好用的“手脚”和“神经”。前端负责交互,后端负责调度和集成。
5.1 SpringBoot后端:微服务与API设计
后端采用经典的SpringBoot3架构,但有几个模块需要特别关注:
- 控制器层:提供两个核心API。
POST /api/chat/recommend:接收用户消息,触发完整的推荐流程(调用DeepSeek -> 查询Neo4j -> 再调用DeepSeek -> 返回结果)。GET /api/recommend/history:获取当前用户的对话推荐历史。
- 服务层:
ChatRecommendationService:总调度服务,串联整个流程。DeepSeekService:封装与DeepSeek API的交互。Neo4jQueryService:负责构建Cypher查询并执行,封装Neo4j Java Driver的操作。ProductService:从MySQL查询商品详情。
- 数据层:同时操作MySQL(商品详情、用户订单)和Neo4j(知识图谱)两种数据源。这里要特别注意事务管理,虽然这两个数据库的事务通常不需要强一致,但也要做好错误处理和补偿机制。
一个重要的优化点是异步处理。调用DeepSeek API和查询Neo4j都可能有网络延迟,如果用户连续快速提问,同步处理会导致阻塞。我们可以使用Spring的@Async注解,将耗时的LLM调用和图谱查询部分异步化,并通过CompletableFuture组合结果,最后通过WebSocket或前端轮询返回给用户,这样前端体验会更流畅。
@Service
public class ChatRecommendationService {
@Autowired
private DeepSeekService deepSeekService;
@Autowired
private Neo4jQueryService neo4jQueryService;
@Autowired
private ProductService productService;
@Async
public CompletableFuture<RecommendationResult> processUserQueryAsync(String sessionId, String userQuery) {
// 1. 异步解析用户意图
UserIntent intent = deepSeekService.parseUserIntent(userQuery);
// 2. 异步查询知识图谱,获取商品ID列表
List<String> productIds = neo4jQueryService.queryProductsByIntent(intent);
// 3. 异步获取商品详情
List<ProductDetail> products = productService.getDetailsByIds(productIds);
// 4. 异步生成推荐回复
String aiResponse = deepSeekService.generateRecommendationReply(userQuery, intent, products);
// 组合结果
RecommendationResult result = new RecommendationResult(aiResponse, products);
return CompletableFuture.completedFuture(result);
}
}
5.2 Vue3前端:构建流畅的对话界面
前端的目标是打造一个类似ChatGPT的对话式交互界面,但更聚焦于商品推荐。我们使用Vue3 + Composition API + Element Plus。
- 核心组件:
ChatWindow.vue:主对话窗口,包含消息列表和输入框。MessageBubble.vue:单条消息气泡,区分用户消息和AI消息。AI消息需要特殊渲染,因为它可能包含商品卡片。ProductCard.vue:用于在AI回复中嵌入商品卡片,展示图片、名称、价格和关键属性。
- 状态管理:使用Pinia来管理对话会话状态、用户信息等。一个会话Store可能包含当前消息列表、会话ID、是否正在加载等状态。
- 通信方式:对于推荐请求,我们使用普通的HTTP POST。但如果想实现更实时的流式响应(像ChatGPT那样一个字一个字出来),可以考虑使用Server-Sent Events (SSE)。不过对于推荐场景,一次性返回完整结果通常体验也足够好。
- 关键代码片段:在发送消息时,我们需要将用户输入、当前会话ID等传给后端,并处理加载状态。
// 在ChatWindow.vue的script setup中
import { ref } from 'vue';
import { useChatSessionStore } from '@/stores/chatSession';
import { recommend } from '@/api/chatApi';
const inputMessage = ref('');
const isLoading = ref(false);
const sessionStore = useChatSessionStore();
const sendMessage = async () => {
if (!inputMessage.value.trim() || isLoading.value) return;
const userMsg = inputMessage.value;
sessionStore.addMessage({ type: 'user', content: userMsg });
inputMessage.value = '';
isLoading.value = true;
try {
const response = await recommend({
sessionId: sessionStore.sessionId,
message: userMsg
});
// 假设response.data结构为 { reply: string, products: Array }
sessionStore.addMessage({
type: 'assistant',
content: response.data.reply,
products: response.data.products // 用于渲染商品卡片
});
} catch (error) {
sessionStore.addMessage({ type: 'assistant', content: '抱歉,我好像出错了,请稍后再试。' });
} finally {
isLoading.value = false;
}
};
前端界面的细节打磨很重要,比如输入框防抖、消息发送后的自动滚动到底部、商品图片的懒加载等,这些都能显著提升用户体验。
6. 踩坑与优化:从“能用”到“好用”的实战经验
项目上线后,我们遇到了不少预料之外的问题,也积累了一些宝贵的优化经验。
第一个大坑:Neo4j查询性能。 最初我们的图谱关系设计得比较细,商品节点可能关联几十个属性节点。当用户查询“休闲、舒适、200元以下的运动鞋”时,生成的Cypher语句包含了多个MATCH和WHERE条件,在数据量达到几十万级时,响应时间偶尔会超过2秒。解决方案是:1)为高频查询的属性建立复合索引;2)优化关系设计,将一些固定的、枚举值少的属性(如品牌、品类)直接作为商品节点的属性,而不是单独建节点,减少查询时的跳数;3)对复杂的查询,在业务允许的情况下,适当限制返回结果的数量。
第二个坑:DeepSeek API的稳定性与成本。 直接调用开放API,会遇到限流、响应慢的问题,而且token消耗是实打实的成本。我们做了几件事:1)在SpringBoot后端增加了请求重试机制和断路器(如Resilience4j),防止因API临时故障导致整个服务不可用;2)对用户的输入和DeepSeek返回的JSON做了严格的校验和清洗,避免无效调用;3)引入了意图缓存。我们发现很多用户问题意图相似(如“便宜的手机”),对于解析出的相同UserIntent,我们可以直接缓存其对应的Cypher查询模板参数,甚至缓存最终的推荐结果(设置较短的过期时间),这大大减少了不必要的DeepSeek调用和Neo4j查询。
第三个挑战:冷启动与推荐多样性。 新用户没有历史行为,知识图谱里的“购买过”、“浏览过”关系用不上。我们的策略是:1)在对话开始时,引导用户多表达需求(“您今天想找点什么?”);2)当意图不明确时,后端会从Neo4j中查询当前热销商品(通过“购买”关系数量排序)或高评分商品作为兜底推荐;3)在生成Cypher查询时,会有意地加入一些随机因子,比如在价格区间内随机排序,或者混合推荐不同品类的商品,避免每次结果都一样,让用户觉得系统“有探索能力”。
关于部署,我们将系统拆成了几个服务:SpringBoot主应用、Neo4j数据库、MySQL数据库分别部署。DeepSeek是外部服务。为了保证图谱数据的实时性,我们那个从MySQL同步到Neo4j的定时任务,从每天一次改成了每小时一次增量同步。监控方面,除了常规的服务器监控,我们还特别关注Neo4j的内存使用情况、DeepSeek API的调用成功率和平均响应时间,这些指标能帮助我们提前发现潜在问题。
这个项目做下来,最大的体会是,技术组合的威力在于“扬长避短”。DeepSeek长于理解与生成,但不擅长精确查询和记忆;Neo4j长于关系查询和推理,但不懂自然语言。用SpringBoot把它们粘合起来,再用Vue做出一个友好的界面,一个智能的对话推荐系统就诞生了。它也许还不是尽善尽美,比如对非常口语化、歧义大的语言理解还有偏差,但相比于传统的推荐方式,它已经向前迈出了一大步,为用户提供了一种全新的、更自然的购物探索方式。如果你正在考虑为你的产品增加一些AI交互能力,希望这篇分享能给你提供一个清晰的、可落地的实现思路。
更多推荐


所有评论(0)