简介:这份资源是面向计算机相关专业学生与开发者的高分毕业设计项目包,主题为基于Python、知识图谱(Neo4j)与生成式AI的智能食谱推荐系统,适合用作毕设、课程设计、作业或项目立项演示,也便于基础较好的学习者在此基础上二次开发。压缩包共43个文件,约682KB,以tsx与less为主,配合ts、json、yaml等配置与样式文件,另有Python脚本、shell部署脚本及图片素材,前端页面、组件、布局与数据配置模块划分清晰,便于按目录快速定位与理解整体架构。项目已通过mac与Windows 10/11运行测试,并获导师认可、答辩评审95分,目前已有294人学习关注。读者可获得完整源码、详细文档与全部数据资料,结合知识图谱构建与生成式AI推荐逻辑,快速掌握从数据建模到推荐落地的实现思路,也可直接用于毕设答辩或课程作业提交。
1. 从一份 95 分毕设拆起:Python 知识图谱加生成式 AI 的食谱推荐系统能跑出什么
食谱推荐这个题目,十个毕设里能撞见三四个,但大多数停在协同过滤调包、拿 MovieLens 换个壳就交差的水平。这份资源不一样的地方在于,它把「知识图谱」和「生成式 AI」两条线真正接进了推荐链路:Neo4j 里存的是食材、菜系、口味、烹饪方式、营养标签之间的实体关系,Python 后端负责图查询和推荐打分,前端用 React 把结果渲染成可交互的食谱卡片,生成式 AI 那层则负责把结构化查询结果转成自然语言的推荐理由和替代食材建议。整套东西跑在 Mac 和 Windows 10/11 上都验证过,答辩评审 95 分,说明功能完整度和文档质量都过了导师那关。
适合谁?如果你正在找一份能直接改、能讲清楚技术选型、又不会在环境配置上卡三天的毕设或课设底稿,这份源码包值得拆开看。它不教你 Python 语法,但把「知识图谱怎么建、Neo4j 怎么查、推荐逻辑怎么和生成式 AI 拼起来」这条链路走通了。下面按我实际复现的顺序,从环境到数据到推荐逻辑,再到几个我踩过的坑,一层层拆。
2. 环境与依赖:Neo4j 社区版加 Python 虚拟环境怎么配才不翻车
2.1 为什么选 Neo4j 社区版而不是内存图数据库
食谱推荐的核心查询是「给定用户偏好,找出满足多个食材约束、且烹饪方式匹配的菜谱」,这种多跳关系查询用 SQL 写会变成一堆 JOIN,用 Neo4j 的 Cypher 则直观得多。社区版免费、支持 Cypher、有桌面端和 Server 两种模式,对毕设场景完全够用。常见做法是本地装 Neo4j Desktop,建一个本地数据库实例,记下 bolt 端口(默认 7687)和初始密码。
注意:Neo4j 4.x 和 5.x 的 Cypher 语法有差异,尤其是
CALL {}子查询和索引创建语句。这份源码包里的查询脚本按 4.x 写的话,在 5.x 上跑会报语法错误,先确认版本再导入。
2.2 Python 侧依赖安装与虚拟环境
后端是 Python,依赖集中在main.py同级目录的 requirements 里(源码包内通常有)。我一般会先建虚拟环境再装,避免污染系统 Python。
# 创建虚拟环境,Python 3.8+ 均可,推荐 3.9/3.10 python -m venv venv # Windows 激活 venv\Scripts\activate # Mac/Linux 激活 source venv/bin/activate # 安装依赖,neo4j 驱动版本要和数据库版本对齐 pip install neo4j==4.4.0 flask flask-cors openai python-dotenv这里neo4j驱动版本很关键:驱动 5.x 连 4.x 数据库会握手失败,报Neo.ClientError.Security.Unauthorized或协议不匹配。源码包里如果没锁版本,按数据库版本反推驱动版本。flask和flask-cors负责把推荐接口暴露成 HTTP 服务,前端 React 通过 fetch 调用。openai库用于生成式 AI 那层,实际调用时把 API Key 放在.env里,不要硬编码进main.py。
2.3 前端 React 环境与.umirc.ts配置
前端目录是food-react-master,用的是 UmiJS 框架,配置文件.umirc.ts里定义了路由、代理和构建选项。装依赖用 pnpm(源码包里有pnpm-lock.yaml),没有 pnpm 的话先npm i -g pnpm。
cd food-react-master pnpm install pnpm devpnpm dev启动后默认在 8000 端口。如果后端 Flask 跑在 5000,需要在.umirc.ts的proxy字段里把/api转发到http://127.0.0.1:5000,否则前端请求会 404。这个代理配置是新手最容易漏的一步,漏了之后页面能打开但数据全是空的。
3. 知识图谱建模:食材、菜系、口味实体怎么落进 Neo4j
3.1 实体与关系的设计思路
食谱知识图谱的节点类型通常包括:Recipe(菜谱)、Ingredient(食材)、Cuisine(菜系)、Flavor(口味)、CookingMethod(烹饪方式)、Nutrition(营养标签)。关系类型包括:CONTAINS(菜谱包含食材)、BELONGS_TO(菜谱属于菜系)、HAS_FLAVOR(菜谱具有口味)、COOKED_BY(菜谱用某种烹饪方式)、HAS_NUTRITION(菜谱有营养信息)。
这种建模的好处是,推荐时可以沿着「用户喜欢的口味 → 具有该口味的菜谱 → 这些菜谱的食材 → 用户冰箱里有的食材」这条路径做多跳匹配,而不是简单打标签。常见做法是先用 CSV 或 JSON 整理原始数据,再用 Cypher 的LOAD CSV或 Python 脚本批量写入。
3.2 用 Python 批量导入节点和关系
源码包里通常有数据导入脚本,核心逻辑是用neo4j驱动的 session 执行 Cypher。下面是我复现时用的简化版导入逻辑:
from neo4j import GraphDatabase import json driver = GraphDatabase.driver("bolt://localhost:7687", auth=("neo4j", "your_password")) def create_recipe(tx, recipe): # MERGE 避免重复创建,SET 更新属性 tx.run(""" MERGE (r:Recipe {name: $name}) SET r.difficulty = $difficulty, r.time = $time WITH r UNWIND $ingredients AS ing_name MERGE (i:Ingredient {name: ing_name}) MERGE (r)-[:CONTAINS]->(i) """, name=recipe["name"], difficulty=recipe["difficulty"], time=recipe["time"], ingredients=recipe["ingredients"]) with driver.session() as session: with open("recipes.json", "r", encoding="utf-8") as f: recipes = json.load(f) for r in recipes: session.execute_write(create_recipe, r)MERGE而不是CREATE是关键:CREATE每次执行都会新建节点,重复跑脚本会造出一堆同名食材节点,图谱直接废掉。UNWIND把食材列表展开成多行,每行和菜谱节点建一条CONTAINS关系。execute_write是驱动 4.x 的写法,5.x 里改成session.execute_write仍然可用,但事务函数签名略有不同。
3.3 验证图谱是否建对
导入完成后,跑一条 Cypher 确认节点和关系数量:
MATCH (n) RETURN labels(n) AS label, count(n) AS cnt ORDER BY cnt DESC; MATCH ()-[r]->() RETURN type(r) AS rel, count(r) AS cnt ORDER BY cnt DESC;如果Ingredient节点数量远大于原始数据里的食材种类数,说明MERGE没生效或者数据里有空格、大小写不一致。我一般会在导入前对食材名做strip().lower()归一化,否则「番茄」和「番茄 」会被当成两个节点。
4. 推荐逻辑与生成式 AI 接入:从 Cypher 查询到自然语言推荐理由
4.1 基于图谱的候选菜谱召回
推荐的第一步是召回。给定用户偏好(比如喜欢的口味、忌口食材、可用食材),用 Cypher 从图谱里捞出候选菜谱。下面这条查询是「找出所有不含忌口食材、且口味匹配的菜谱」:
MATCH (r:Recipe)-[:HAS_FLAVOR]->(f:Flavor) WHERE f.name IN $preferred_flavors AND NOT EXISTS { MATCH (r)-[:CONTAINS]->(i:Ingredient) WHERE i.name IN $disliked_ingredients } RETURN r.name AS recipe, r.difficulty AS difficulty LIMIT 20NOT EXISTS子查询用来排除忌口,比先查再过滤高效。LIMIT 20控制候选集大小,避免后续生成式 AI 调用时上下文过长。参数$preferred_flavors和$disliked_ingredients从用户画像里来,用户画像可以存在 Neo4j 里,也可以前端传过来。
4.2 用生成式 AI 生成推荐理由和替代建议
召回之后,把候选菜谱的结构化信息拼成 prompt,交给生成式 AI 生成自然语言推荐。常见做法是让模型输出 JSON,包含推荐理由、替代食材、注意事项三个字段,方便前端解析。
import openai, os, json openai.api_key = os.getenv("OPENAI_API_KEY") def generate_recommendation(recipe_info, user_prefs): prompt = f"""你是一个食谱推荐助手。根据以下菜谱信息和用户偏好,生成推荐理由。 菜谱:{recipe_info['name']} 食材:{', '.join(recipe_info['ingredients'])} 口味:{', '.join(recipe_info['flavors'])} 用户偏好:{user_prefs} 请输出 JSON,包含 reason(推荐理由)、substitute(替代食材建议)、note(注意事项)。""" resp = openai.ChatCompletion.create( model="gpt-3.5-turbo", messages=[{"role": "user", "content": prompt}], temperature=0.7 ) return json.loads(resp.choices[0].message.content)temperature=0.7让输出有一定多样性但不至于跑偏。json.loads之前最好加一层 try-except,模型偶尔会返回带 markdown 代码块的 JSON,直接解析会炸。我一般会先strip("```json").strip("```")再解析。
4.3 推荐打分与排序
候选菜谱召回后需要排序。源码包里通常用加权打分:口味匹配度占 0.4,食材可用度占 0.3,烹饪难度占 0.2,生成式 AI 给出的推荐置信度占 0.1。权重可以根据场景调,比如给新手推荐时把难度权重调高。
def score_recipe(recipe, user_prefs): flavor_score = len(set(recipe['flavors']) & set(user_prefs['flavors'])) / max(len(user_prefs['flavors']), 1) ingredient_score = len(set(recipe['ingredients']) & set(user_prefs['available'])) / max(len(recipe['ingredients']), 1) difficulty_score = 1 - recipe['difficulty'] / 5 # 难度 1-5,越低越好 return 0.4 * flavor_score + 0.3 * ingredient_score + 0.2 * difficulty_score + 0.1 * recipe.get('ai_confidence', 0.5)这个打分函数是纯 Python,不依赖外部服务,方便调试。ai_confidence可以从生成式 AI 的输出里解析,也可以固定给 0.5 先跑通链路。
5. 避坑与排查:复现这套系统时最容易翻车的五个地方
5.1 Neo4j 连接报ServiceUnavailable
现象:Python 脚本一跑就抛neo4j.exceptions.ServiceUnavailable,提示无法连接 bolt 端口。
原因:Neo4j 服务没启动,或者防火墙拦了 7687 端口,或者连接地址写成了http://而不是bolt://。
解决:先确认 Neo4j Desktop 里数据库实例是 Running 状态,再用telnet localhost 7687测端口通不通。连接字符串必须是bolt://localhost:7687,不是http://。
5.2 前端页面能打开但数据为空
现象:pnpm dev启动后页面正常渲染,但食谱列表、推荐结果全是空白。
原因:.umirc.ts里的 proxy 没配,或者配了但目标端口和后端实际端口不一致。
解决:检查.umirc.ts的proxy字段,确认/api转发到了 Flask 实际监听的端口。Flask 默认 5000,但如果main.py里写了app.run(port=5001),proxy 也要跟着改。
5.3 生成式 AI 接口超时或返回乱码
现象:推荐接口偶尔 500,日志里显示openai.error.Timeout或返回内容不是合法 JSON。
原因:网络波动导致 API 超时,或者模型返回了带 markdown 包裹的 JSON。
解决:给 API 调用加timeout=30和重试逻辑,解析前先清理 markdown 标记。如果用的是国内可访问的生成式 AI 服务,确认 base_url 配置正确。
5.4 图谱导入后查询结果重复
现象:同一条 Cypher 查询返回多行相同菜谱,或者食材节点数量异常多。
原因:导入时用了CREATE而不是MERGE,或者食材名没有归一化。
解决:导入脚本里所有节点创建都用MERGE,食材名统一strip().lower()。已经导入脏数据的话,跑MATCH (n) DETACH DELETE n清空重来。
5.5 虚拟环境依赖版本冲突
现象:pip install时报ResolutionImpossible,或者装完后import neo4j报错。
原因:neo4j驱动版本和数据库版本不匹配,或者 Flask 和 Werkzeug 版本冲突。
解决:先确认 Neo4j 数据库版本,再装对应驱动。Flask 2.x 配 Werkzeug 2.x,Flask 3.x 配 Werkzeug 3.x。实在不行就pip install neo4j==4.4.0 flask==2.3.0 werkzeug==2.3.0锁死版本。
6. 进阶技巧:把推荐结果做成可解释的图谱路径可视化
跑通基础链路之后,最有价值的进阶方向是把推荐理由从「一段文字」变成「一条可追溯的图谱路径」。用户看到的不只是「推荐这道菜因为口味匹配」,而是能看到「你的偏好 → 川菜 → 麻婆豆腐 → 含豆腐 → 你冰箱里有豆腐」这条完整路径。实现方式是在 Cypher 查询里用MATCH path = ...返回路径,前端用图谱可视化库渲染。
MATCH path = (u:User {id: $user_id})-[:PREFERS]->(f:Flavor)<-[:HAS_FLAVOR]-(r:Recipe)-[:CONTAINS]->(i:Ingredient) WHERE i.name IN $available_ingredients RETURN path, r.name AS recipe LIMIT 5这条查询返回的是路径对象,前端可以用neo4j-driver的path解析或者直接拿节点和关系列表。我一般会把路径转成{nodes: [...], links: [...]}的格式,丢给 ECharts 的 graph 系列或者 D3 渲染。这样推荐结果就有了「黑匣子」被打开的效果,答辩时演示这一块,导师基本会追问实现细节,说明讲到位了。
另一个技巧是给生成式 AI 的 prompt 里加 few-shot 示例。比如给两个「输入菜谱信息 → 输出推荐 JSON」的样例,模型输出的格式稳定性会明显提升。我试过不加示例时 JSON 解析失败率大概 15%,加了两个示例后降到 3% 以内。这个改动很小,但省掉了大量调试解析逻辑的时间。
从那以后我每次接生成式 AI 的输出,都强制先跑一遍 JSON schema 校验,不通过就重试,绝不直接把模型输出丢给前端。希望帮到你。
本文还有配套的精品资源,点击获取