
简介一套面向计算机相关专业毕业设计、课程设计场景的智能食谱推荐系统完整项目基于Python结合知识图谱Neo4j与生成式AI实现推荐功能适合需要完整项目参考或二次开发的学生与开发者。压缩包共43个文件以React前端代码tsx/less/ts、Python主程序、配置文件及文档为主整体约682KB结构清晰便于直接运行与修改。项目已通过mac与Windows环境测试并获得导师认可、答辩95分的高分评价具备较高参考价值。目前已有294人学习下载包含源码、详细文档及全部数据资料能够支撑从环境搭建到功能演示的全流程学习也可作为初期立项或课设作业的坚实基础。1. 从食材到推荐知识图谱加生成式AI到底在解决什么问题冰箱里有鸡蛋、番茄、豆腐但不知道组合成什么菜——这是做菜的人最日常的困扰。市面上的菜谱App只会按菜名搜索不会“按手头食材找菜”传统推荐系统靠协同过滤冷启动阶段给不出任何有解释力的结果。这份毕设源码解决的问题是先把你手头的食材映射成知识图谱里的实体再用图谱结构而不是用户行为做推荐最后让大模型把候选菜谱改写成适合你口味和忌口的完整做法。对准备毕业设计的学生来说它同时给了三个可写进论文的硬点Python后端、Neo4j知识图谱、生成式AI接口。对我这种经常要拆项目源码的人最关心的反而不是推荐准不准而是它的建图方式、查询路径和大模型接入方式能不能直接复制到别的知识图谱项目里。全文我会按“架构→建图→推荐→大模型→踩坑→验证”的顺序拆每一步都会落到代码和参数。2. 项目整体架构为什么要用Neo4j存菜谱而不是MySQL2.1 图谱选型菜谱数据天然是实体关系网食材和菜品之间的关系用关系型数据库表达很别扭。一道菜依赖多种食材一种食材可以出现在多道菜里这是典型的多对多关系。在MySQL里你得建三张表再加两张关联表查询“我有番茄、鸡蛋能做什么菜”要写三次JOIN一旦菜谱数据上千条SQL语句的可读性和维护成本都会明显上升。Neo4j把这个需求改成了图遍历。食材、菜谱、口味、季节、做法是节点它们之间的关系是边。比如“番茄炒蛋”节点有一条requires边指向“番茄”节点还有一条has_taste边指向“咸鲜”节点。查询时从“番茄”节点沿着requires反向找菜只需要一次图遍历逻辑清楚且查得快。对这个项目来说选Neo4j的另一个理由是论文好看知识图谱本身就是计算机专业毕设的热门主题答辩时可以讲实体抽取、关系构建、图查询内容密度比普通CRUD项目高得多。源码里是有Python主程序加上前端的React工程的。前端部分用了UmiJS框架展示菜谱卡片和知识图谱的可视化关系后端Python负责接收前端请求、调用Neo4j查询、再对接大模型接口。整体调用链是用户在界面选食材 → 前端通过HTTP请求打到Python后端 → 后端组合Cypher查询去Neo4j里取候选菜 → Python再把这些候选菜信息拼进提示词 → 大模型返回生成式结果 → 后端把结果返回前端渲染。2.2 前后端与数据流节点、关系、生成式输出的分工建图是整个系统的地基。项目里的数据资料包含一批菜谱原始数据我拆完后看到的核心逻辑是先把这些数据清洗成三张结构清晰的表食材表、菜谱表、关联关系表。食材表有name、category、season等属性菜谱表有name、method、ingredients、steps等属性关联关系表则记录了每个菜谱引用了哪些食材。然后通过Python脚本把表和表之间的关系写入Neo4j。写入时用的不是Neo4j的Python驱动直接逐条CREATE而是先收集节点再构建关系。常见做法是批量提交避免每插入一条数据都和数据库做一次往返。项目中主食料和菜谱的关系最重要因为推荐系统全靠它支撑。口味、适合人群这类属性可以作为节点的额外属性存起来不需要额外建关系这样能减少图谱的复杂度查询时也好写Cypher。大模型部分在整个项目里承担的是最后一步“生成式改写”。它不做推荐决策推荐决策由Neo4j的图查询结果决定。大模型拿到的是已经排序好的候选菜谱和用户选定的食材、忌口信息把它翻译成更自然的展示文案。这种分工非常重要我见很多类似的毕设把大模型当成推荐引擎结果就是模型乱推荐、用户根本没法用。这个项目把“图谱负责召回大模型负责润色”分得很清楚可维护性比较好。对答辩来说这也是一个可以展开讲的亮点召回和排序在图谱里做生成只负责表达。3. 建图步骤从CSV到Neo4j的完整导入与常用查询3.1 准备数据清洗、编码与去重数据是以CSV或者JSON格式存放在资料包里的直接用Neo4j的LOAD CSV导入是最省事的方式。但导入之前必须做三件事统一编码为UTF-8、去掉重复菜谱、把“西红柿”和“番茄”这类同义词合并。我在实际导入时遇到过编码问题Windows下生成的CSV常见的是GBK或ANSI编码Neo4j不认识中文字符会变成乱码所以第一步先用Python脚本把所有CSV转成UTF-8。项目里菜谱去重我一般以菜名加做法作为联合唯一键比单独用菜名可靠。同义词合并可以放在食材表里加一列alias比如“番茄”的别名写上“西红柿”查询的时候先匹配别名再返回标准名。下面是数据预处理脚本的简化版本import pandas as pd df pd.read_csv(recipes_raw.csv, encodinggbk) df[name] df[name].str.strip() df df.drop_duplicates(subset[name, method]) alias_map {西红柿: 番茄, 马铃薯: 土豆, 青椒: 甜椒} df[ingredient_std] df[ingredient].replace(alias_map) df.to_csv(recipes_clean.csv, indexFalse, encodingutf-8-sig) print(df.shape)这个脚本先按GBK读入原始数据——Windows导出的常见编码清理菜名首尾空格用菜名加做法去重然后对食材做同义词替换。utf-8-sig编码写出的文件带BOM头Neo4j的LOAD CSV能正确识别UTF-8内容比纯utf-8更稳妥。注意去重时不要只按菜名中式菜谱里同名不同做法的菜很常见比如“蒸蛋”和“炒蛋”食材差不多但做法不同如果只按菜名去重会丢失数据。3.2 导入Neo4j节点先行关系后建数据干净之后就可以导入Neo4j。我一般会先建约束再导节点最后建关系顺序不能反。没有唯一约束直接导数据很容易在反复测试时插入重复节点等发现时图谱已经乱掉了。先在Neo4j浏览器里跑一段约束创建语句CREATE CONSTRAINT recipe_name IF NOT EXISTS FOR (r:Recipe) REQUIRE r.name IS UNIQUE; CREATE CONSTRAINT ingredient_name IF NOT EXISTS FOR (i:Ingredient) REQUIRE i.name IS UNIQUE;这段语句给Recipe和Ingredient两种节点分别加上唯一约束。这样后续无论跑多少遍导入脚本相同的菜名和食材名都只会存在一个节点重复执行导入不会产生脏数据。IF NOT EXISTS是防止约束已经存在时报错第一次建库和后续维护都是安全的。节点导入完成后再建关系。食材到菜谱的关系类型是REQUIRES方向从菜谱指向食材表达“这道菜需要这个食材”。在Cypher里这样写LOAD CSV WITH HEADERS FROM file:///recipes_clean.csv AS row MATCH (r:Recipe {name: row.name}) MATCH (i:Ingredient {name: row.ingredient_std}) MERGE (r)-[rel:REQUIRES]-(i)先MATCH出已经存在的菜谱节点和食材节点再MERGE建关系。这里必须用MERGE而不是CREATE因为两批数据可能交叉引用CREATE会造成重复关系。跑完后可以用这段查询快速验证图谱规模MATCH (r:Recipe)-[rel:REQUIRES]-(i:Ingredient) RETURN r.name AS recipe, collect(i.name) AS ingredients LIMIT 10;collect函数把一道菜的所有食材聚合成一个列表方便人工检查导入是否完整。如果某个菜谱的食材列表明显不全说明源数据清洗时漏掉了一些行需要回去检查CSV。3.3 图谱查询召回候选菜谱的Cypher模板建图的最终目的是查询。推荐系统的核心查询可以拆成两步先按用户选定的食材找菜再按食材覆盖比例排序。比如用户选了“番茄、鸡蛋、豆腐”系统要找出能用这些食材做的菜。这个查询用Cypher表达非常自然MATCH (r:Recipe)-[:REQUIRES]-(i:Ingredient) WHERE i.name IN [番茄, 鸡蛋, 豆腐] WITH r, collect(i.name) AS matched_ingredients WHERE size(matched_ingredients) 2 RETURN r.name AS recipe, matched_ingredients, size(matched_ingredients) AS match_count ORDER BY match_count DESC LIMIT 10;WHERE i.name IN [...]把用户选的食材作为过滤条件collect(i.name)收集每道菜匹配到的食材名size(matched_ingredients) 2过滤掉只匹配一种食材的菜最后按匹配数量排序。这段查询体现的是图谱的优势不需要提前设计复杂的索引关系就是索引。匹配两样食材以上的菜优先展示完全符合“尽量用完手头食材”的用户直觉。如果要加入忌口过滤比如用户不吃辣可以在菜谱节点上增加spicy_level属性再在查询条件中加上r.spicy_level 不辣。这个扩展体现的是属性与关系分离的设计思路属于建Schema时就该考虑好的边界。4. 推荐链路实现从Neo4j查询结果到前端推荐理由生成4.1 后端查询封装Python驱动Neo4j参数化查询Python后端使用Neo4j官方驱动连接数据库。这段代码是项目里调用最频繁的部分直接决定了页面响应时间。先把连接和查询封装成独立模块避免在每个接口里重复创建驱动实例。所有进入Neo4j的查询参数一律走参数化方式不拼接字符串防止注入的同时也避免了中文引号转义问题from neo4j import GraphDatabase class RecipeGraph: def __init__(self, uri, user, password): self.driver GraphDatabase.driver(uri, auth(user, password)) def close(self): self.driver.close() def recommend_by_ingredients(self, ingredients, min_match2, limit10): cypher MATCH (r:Recipe)-[:REQUIRES]-(i:Ingredient) WHERE i.name IN $ingredients WITH r, collect(i.name) AS matched WHERE size(matched) $min_match RETURN r.name AS recipe, matched, size(matched) AS match_count ORDER BY match_count DESC LIMIT $limit with self.driver.session() as session: result session.run(cypher, ingredientsingredients, min_matchmin_match, limitlimit) return [{recipe: rec[recipe], matched: rec[matched], count: rec[match_count]} for rec in result]session.run的第二参数是参数字典Cypher里的$ingredients、$min_match、$limit分别从Python变量的值传入。注意LIMIT也是参数化的实际测试中占位符在LIMIT后是合法的不需要拼接整数。查询结果从result里迭代取出每条记录包含菜名、匹配的食材列表和匹配数量。这个封装类可以在任何需要推荐的地方复用。一个常见的错误是每次请求都新建一个GraphDatabase.driver实例项目并发一高就会出现“连接数已达上限”的报错。正确做法是把RecipeGraph对象初始化一次在Flask或FastAPI启动时创建请求结束时只关闭session不关闭driver。4.2 生成式AI接入推荐结果如何变成人话Neo4j返回的是结构化候选集直接丢给前端就暴露了“机器味”用户看到的是菜名和匹配食材列表不知道这道菜怎么做、为什么适合自己。这一步就轮到生成式AI。项目里的做法是把推荐候选集构造进提示词让大模型基于候选信息生成带做法和推荐理由的完整回答。提示词模板是核心。模板里需要包含四个要素用户选了哪些食材、候选菜列表、用户的忌口或偏好、输出格式要求。构造提示词的代码如下def build_prompt(ingredients, candidates, restrictions): candidate_text \n.join( [f- {c[recipe]}需要{、.join(c[matched])} for c in candidates] ) prompt f 你是智能食谱推荐助手。 用户手头有食材{, .join(ingredients)} 用户的忌口或偏好{restrictions if restrictions else 无} 系统从知识图谱中召回以下候选菜谱 {candidate_text} 请从这些候选菜中挑选最合适的2-3道菜每道菜说明推荐理由 并给出简要的做法步骤。如果所有候选都不合适请直接说明原因。 return promptcandidate_text把候选菜结构和匹配到的食材拼成可读列表大模型看到的是结构清晰的上下文。restrictions为空时模板自动回退到“无”保证即使用户没填忌口也能正常生成。提示词末尾加了“如果所有候选都不合适请直接说明原因”这是防止大模型在候选结果较差时强行编造菜谱的兜底逻辑。生成式AI的接口接入要看项目里用的是哪种大模型。如果用的是国内大模型直接通过HTTP调API即可如果是跑在本地的模型则需要起一个本地推理服务。我拆源码时注意到这部分的接口层做得比较清晰换成其他模型只需要改动一个方法。调用完成之后后端把生成的文本包一层JSON返回前端前端在菜谱详情页里展示。4.3 前端展示知识图谱可视化与推荐理由页面的配合前端是React工程主要页面包括食材选择页、推荐结果页和图谱展示页。食材选择页用卡片形式展示常见食材用户点击选中再点“开始推荐”就把食材列表POST到后端。推荐结果页除了显示菜名和做法还展示了“知识图谱匹配到哪些食材”这个字段来自图谱查询不是大模型编的。图谱展示页用关系图组件渲染食材和菜谱的节点关系让答辩评委一眼看懂图谱结构。前后端联调时要注意接口路径必须和.umirc.ts里的代理配置一致。开发环境前端跑在8000端口后端跑在5000端口需要配置代理转发。如果代理配置没生效浏览器会报跨域错误而单独访问后端接口又是正常的这个现象很典型。5. 避坑清单Neo4j安装、中文乱码、数据重复与模型幻觉5.1 Neo4j社区版连接不上的五种表现最常见的问题是Neo4j启动后Python连接超时。现象是session.run抛ServiceUnavailable但Neo4j浏览器显示数据库正常运行。我遇到过三种原因第一种是Neo4j版本和Python驱动版本不兼容社区版4.x配neo4j驱动4.x系列没问题换成5.x驱动就会握手失败第二种是数据库默认只监听localhost远程访问时bolt端口没有开启需要在neo4j.conf里配置server.bolt.listen_address0.0.0.0:7687第三种是Python环境里同时装了多个neo4j驱动版本导致调用的类来自旧版本。排查顺序我建议是先看后端的连接URI和密码有没有写错再看驱动的major版本和Neo4j的major版本是否一致最后检查防火墙和监听地址。多数毕设项目里连的是本机所以前两项的命中率最高。5.2 CSV中文变乱码编码与Import目录双坑用LOAD CSV导入中文数据时出现“”或“锟斤拷”基本都是编码问题。Neo4j的file:///导入目录是安装目录下的import文件夹CSV必须放在这个文件夹里才能被读取。文件确实放在import目录但中文乱码时在Windows上十有八九是CSV本身是ANSI编码需要转成UTF-8。转编码的方式建议用支持批量转换的工具或Python的codecs模块。如果数据量不大直接把CSV另存为UTF-8编码也行。导入之前还要检查CSV的表头是不是英文Neo4j的LOAD CSV WITH HEADERS要求表头不准有中文和空格否则row.name这种取值语法会拿不到值。5.3 重复节点导致推荐结果虚高跑了几遍导入脚本之后图谱里出现了大量重复的菜谱节点。现象是查询同一种食材能召回几十道“相同”的菜推荐结果页面出现多条一模一样的记录。原因很简单没有先建唯一约束就反复执行导入脚本。解决方法是重建约束加MERGE重跑。如果图谱已经乱了优先删掉整个图谱文件重新导入不要试图在脏数据上修补。从项目交付的角度看数据干净比数据多更重要答辩时跑一条查询出现重复菜名印象分会打折扣。5.4 大模型推荐了图谱里不存在的菜图谱召回阶段返回的是数据库里真实存在的菜但大模型在生成时可能为了“凑数”编一道不在候选列表里的菜。现象是用户看到推荐菜品很合理但点进详情页发现没有做法数据。这是典型的幻觉问题原因是大模型被赋予了太高的自由度。解决方法是限制大模型的角色它只能从候选列表里选不能自己新造菜名。把系统提示词改成“你只能从给定的候选菜中挑选禁止创造新的菜名”并且要求输出必须包含候选菜结构里的原始字段前端才可以关联到详情页。我在项目源码里看到详情数据的读取确实依赖候选菜的原始name做关联所以这一步尤其要注意。5.5 Python环境与Neo4j Desktop的版本兼容在Windows上安装Neo4j Desktop时需要JDK支持如果本机装的是OpenJDK 17而Neo4j版本还是4.x启动时可能会报“Unsupported Java version”。解决办法就是直接装Neo4j 5.x配合JDK 17或者保留两个JDK版本并按Neo4j的要求切换JAVA_HOME环境变量。我见过一个同学的机器上装了三个JDK版本Neo4j启动报错半天查不出来最后发现是JAVA_HOME指向了JDK 8。这类问题和环境绑定和项目代码本身没有太大关系但会在答辩演示时突然冒出来务必提前清理好环境变量。6. 验收技巧三分钟验证图谱建对了演示才不会翻车拿到这个项目之后不要急着看代码逻辑先做一轮数据层验证。启动Neo4j在浏览器里依次跑三条查询第一条MATCH (n:Recipe) RETURN count(n)确认菜谱数量符合数据资料规模第二条随机抽一个菜谱看食材关系是否完整第三条用几个常见食材手动跑一遍推荐查询看返回的菜是不是符合常识。如果这三条都对了说明建图脚本和数据本身没问题是可信的。验证推荐效果的时候我习惯用边界食材做测试。选“番茄、鸡蛋”看能不能召回番茄炒蛋再试“面粉、猪肉”普通家常菜库里不一定有匹配结果此时要看系统是否给出空状态提示而不是报错。这个场景非常值得在答辩前测试很多点评老师会故意问“用户选了冷门食材怎么办”项目能给出合理的空状态回复比硬推一道完全不相关的菜更能说明设计考虑是完整的。演示阶段有两个建议。第一个建议是提前准备一组固定的食材组合并在本地把推荐结果跑通不要现场临时换食材尤其不要换太冷门的食材生成式AI接口的网络状况和模型响应时间都是不确定因素第二个建议是准备图谱可视化页面的截图或实时展示这个大屏化的关系图谱在答辩时特别出效果能直观展示知识图谱的价值。备份也是必做功课。Neo4j的数据目录默认在数据库安装目录下的data/databases/里演示前手动备份整个neo4j数据库文件夹。如果不小心把数据跑乱了直接把备份文件拷回来就能恢复。从那以后我每次做知识图谱项目的演示都强制走一遍“备份数据 → 重启Neo4j → 跑三条验收查询”的流程这个习惯帮我躲过好几次现场翻车。希望这份拆解能帮你把这个项目跑通也祝你答辩顺利。本文还有配套的精品资源点击获取