公司动态
Spring AI + RAG + Neo4j医疗知识图谱:智能问诊全栈搭建指南
如果是冲着毕业设计选型来的这个标题已经把核心链路说得很清楚LLM大模型做问答、RAG做知识库检索、Spring AI做后端集成、Neo4j做医疗知识图谱、微信小程序做前端入口。我第一次跑这种全栈项目时最大的感受不是模型效果难调而是模块之间怎么串起来最容易被卡住。很多免费源码能打开但换到自己的环境就是启动失败、对话不回复、图谱查不到数据。所以这篇文章不按源码目录讲而是按“从最小闭环到完整平台”的搭建顺序拆一遍讲清楚每一步为什么这么做、怎么验证、报错了先查哪里。1. 先理解这个平台到底要解决什么问题1.1 医疗问诊的核心场景不是“聊天”很多人一看到“AI智能医疗问诊”就以为做一个ChatGPT套壳页面就能交差。实际上这个项目要解决的不是“能不能聊”而是“能不能给出有依据、有结构的就医参考”。用户输入“咳嗽三天浑身没劲”系统如果只返回一段通用话术那这个系统没有任何工程价值。真正的预问诊场景包含几个具体动作把症状归因到可能疾病、给出推荐科室、提示是否需要就诊、回答关于药品和禁忌的常识问题。这些动作需要模型有医学常识同时又不能凭记忆随便乱。这就是为什么要引入RAG和知识图谱。RAG负责从可信医疗资料里检索片段Neo4j负责把症状、疾病、科室、药品之间的关系结构化。两者不是二选一而是互补非结构化文档用RAG结构化关系用图谱。1.2 RAG和知识图谱分别承担什么角色RAG的全称是检索增强生成。简单说就是先根据用户问题去知识库里检索相关片段再把检索到的内容拼到大模型的提示词里让模型参考这些材料生成答案。这个项目的RAG知识库一般放的是医疗指南、科普文章、常见病问答。这些内容适合向量化然后用向量数据库做相似度检索。比如用户问“高血压能不能吃柚子”系统先去知识库里找到关于高血压和柚子相互作用的段落再交给模型生成回答。知识图谱则负责另一种知识“咳嗽”和“呼吸道感染”是什么关系“呼吸道感染”应该挂“呼吸内科”还是“全科医学科”。这种三元组关系用向量检索很难表达清楚但用图数据库Neo4j查询非常直接。所以这个项目的完整链路是先识别用户问题中的关键实体通过Neo4j得到结构化关系再从向量库中召回相关文档片段最后把两类结果一起交给Spring AI调用的大模型生成答案。1.3 系统能力边界和答辩定位这类项目最容易在答辩时被追问一个问题“你这个和直接打开ChatGPT有什么区别”我的建议是把定位收窄为“面向导诊和预问诊的辅助平台”不是AI医生更不能替代诊断。平台做的是帮患者梳理症状给出就医方向并且把回答依据展示出来。这个定位非常关键既符合医疗合规要求也能把RAG和知识图谱的优势讲清楚。毕业设计展示的是工程能力不是医学能力。你不需要训练医学模型也不需要保证诊断准确率。你需要展示的是大模型如何接入、检索如何做、知识图谱如何查询、前后端如何联动。这才是评分老师真正关注的点。2. 技术选型和运行环境先确认版本再动手2.1 SpringBoot4和Spring AI2.0的真实落地处理标题里写的是SpringBoot4和Spring AI2.0。实际搭建时我建议你打开Spring Initializr看一下当前能拉到的最新稳定版本。如果还是Spring Boot 3.x也不必硬卡在4上。相比Spring Boot的版本号Spring AI的接口变化更值得关注不同版本里ChatClient、ChatModel的创建方式可能不同。先不要为“标题写4我本地是3”这种问题焦虑。毕业设计看的是实现质量和链路完整性代码能跑、逻辑清楚比版本数字重要得多。如果指导老师指定了版本就用指定版本如果没有指定就用当前稳定版本并在论文里写清楚你的版本环境。Spring AI的Maven依赖通常是这样引入的dependency groupIdorg.springframework.ai/groupId artifactIdspring-ai-starter-model-openai/artifactId !-- 版本以Maven仓库实际release为准 -- /dependency不同模型厂商还可能有对应的starter比如通义、Ollama、智谱等等。具体API以官方文档为准不要照抄网上的旧代码。2.2 Neo4j的安装方式本地版本和DockerNeo4j安装与配置是这个项目里最常出问题的环节之一。很多同学把Neo4j下载下来结果连端口都起不来。其实常用的两种方式都很稳定。第一种是用Neo4j Desktop适合Windows或macOS本地可视化操作。安装后创建一个本地数据库记住默认端口7474是网页管理端7687是Bolt协议连接端口。后端连接时用的是7687不是7474。第二种是用Docker适合不想污染本机环境的场景。一个简单的docker-compose配置可以这样写services: neo4j: image: neo4j:5-community container_name: medical_neo4j ports: - 7474:7474 - 7687:7687 environment: NEO4J_AUTH: neo4j/testpassword volumes: - ./neo4j/data:/data注意这里给了社区版镜像毕业设计完全够用。NEO4J_AUTH设置了初始账号neo4j和密码testpassword实际项目里要改成你自己的密码。启动后访问http://localhost:7474输入账号密码就能看到管理界面。2.3 前端微信小程序和Vue3到底怎么分工这个项目标题同时出现微信小程序和Vue3第一次接触的同学容易搞混。微信小程序原生页面用的是WXML和WXSS和Vue3不是一回事。但很多项目会选择用uni-app或Taro这类跨端框架开发小程序这样页面就能用Vue3语法编写。如果项目里同时还有一个Web端管理后台那Vue3可以负责管理后台页面小程序端单独用原生或uni-app实现。我建议先确定你们项目的小程序端用什么方式开发再决定代码结构。不要一开始就想着把Vue3和小程序强行绑在一起。不管用哪种方式你都需要准备这几样微信开发者工具Node.js环境后端接口地址小程序AppID测试可以用测试号本地联调时微信开发者工具默认不允许访问http://localhost接口需要在“详情-本地设置”里勾选“不校验合法域名”。这一步不配置前端永远调不通后端。2.4 资源底线低配能不能跑先说结论如果使用云端大模型API本机不需要GPU8GB内存的开发机也能跑。RAG和Neo4j的负载都不高。Spring Boot启动占几百MB内存Neo4j社区版占1GB到2GB小程序开发工具再占一部分合计大概3GB到4GB。如果你的机器是8GB内存建议只启动后端和Neo4j不要同时开太多服务16GB就舒服很多。如果题目里的LLM要求是本地部署模型那就另说。本地跑一个7B参数模型至少需要6GB以上显存没有独立显卡的同学不要硬试。我的建议是优先使用云端API把精力放在业务链路上。3. 搭建顺序先跑通“问一句答一句”再加RAG和图谱3.1 第一步先搭一个能回答问题的Spring Boot接口不要一上来就把所有模块都接上。第一步只做一件事创建Spring Boot项目引入Spring AI写一个POST接口接收问题返回大模型回答。这一步的目的是验证整条链路的基础是通的模型API能用、Spring AI配置正常、接口能返回文本。如果这一步都跑不通后面加RAG和图谱只会让排查更难。一个最简的Controller示例类似下面这样RestController RequestMapping(/api/consult) public class ConsultController { private final ChatClient chatClient; public ConsultController(ChatClient chatClient) { this.chatClient chatClient; } PostMapping public String ask(RequestBody String question) { return chatClient.prompt() .user(question) .call() .content(); } }ChatClient是Spring AI里偏封装层的对象不同版本的创建方式会有差别但核心思路一致把用户问题传给模型拿到返回内容。验证方式很简单用Postman或Apipost发一个POST请求body里放字符串“你好介绍一下你自己”能收到模型回复就算通过。如果返回500先看日志里是密钥无效、网络不通还是版本API不匹配。3.2 第二步小程序调通接口确认请求链路后端接口通了之后再写小程序页面。不要先画一堆漂亮界面而是先用最简单的输入框和按钮把请求发出去把回答显示出来。小程序端使用wx.request发起请求wx.request({ url: http://localhost:8080/api/consult, method: POST, data: { question: this.data.question }, header: { content-type: application/json }, success: (res) { this.setData({ answer: res.data }); }, fail: (err) { this.setData({ answer: 请求失败请检查后端服务 }); } });注意data这里需要和后端Controller接收的参数对应。如果你后端接收的是一个JSON字符串前端就要额外处理如果你用一个QuestionRequest对象接收字段名就要严格一致。这一步最容易踩的坑是小程序访问不到本机后端。界面里记得勾选“不校验合法域名”后端启动地址也要确认监听的是0.0.0.0而不是只有localhost。3.3 第三步接入RAG让回答基于材料当“普通问答”跑通后再开始做RAG。RAG的核心是先把医疗文档切块、向量化、存入向量库用户提问时把问题向量化从向量库中取TopK条最相关片段最后把片段拼到提示词里。这里先不要追求复杂的检索策略先把一个最小流程跑通。可以选择Spring AI支持的向量数据库实现也可以单独使用一个向量数据库服务。关键流程是一样的准备医疗文档比如“流感防治指南”“高血压日常注意事项”。把文档切块每块500到1000字块之间重叠50到100字。用Embedding模型把每个块转成向量。用户提问时计算问题向量检索TopK片段。将片段放入Promot中的参考资料区。让模型结合资料回答。这一步的验证标准是你问“咳嗽要不要吃抗生素”回答里能出现知识库里“抗生素不适用于普通病毒性感冒”这类内容而不是模型自己凭空发挥。3.4 第四步加入Neo4j知识图谱查询结果RAG跑通后再加知识图谱。先不要设计复杂的图结构而是用一个小案例验证Neo4j里存了“咳嗽-呼吸道感染-呼吸内科”这条关系后端通过Cypher查询把关系结果返回给模型。最简单的方式是在后端查询Neo4j把结果格式化成“根据知识图谱咳嗽可能与呼吸道感染相关建议科室为呼吸内科”这样的文本然后拼入大模型Prompt。MATCH (s:Symptom {name: 咳嗽})-[:RELATED_TO]-(d:Disease) OPTIONAL MATCH (d)-[:DEPARTMENT]-(dept:Department) RETURN d.name AS disease, dept.name AS department LIMIT 5;这样做的价值在于让模型回答时不只是依赖通用知识还能引用项目里实际建好的图谱数据。答辩时你可以直接展示这道回答里有一部分来自知识图谱有一部分来自医疗文档检索。到这里最小闭环才算完整小程序输入问题后端先查图谱再查向量库最后把结果交给大模型返回给小程序。4. RAG流程里最容易出问题的点切块、检索和提示词4.1 切块策略怎么选RAG效果不好很多原因不是模型不行而是切块策略不对。固定长度切块是最简单的比如每500字切一块切出来的块可能把一句话拦腰截断导致语义不完整。医疗指南里症状、诊断、治疗经常是分章节的按标题和自然段来切往往比按固定字符切更有效。我建议先做一组基线测试。用一个几百字的医疗文档分别按固定长度、按段落、按Markdown标题结构来切再拿同一批问题去测看哪个策略召回的内容更完整。不要一上来就迷信某种“最优方案”RAG效果和你的语料结构强相关。如果文档长短差异大可以设置重叠窗口比如每块1000字重叠100字避免关键内容刚好被切断。如果问题涉及多章节知识可以考虑语义切分把语义相近的段落合并到一起。切块是RAG项目的核心调优点值得多花时间。4.2 检索效果怎么判断判断RAG好不好不能只看最终回答有没有“看起来很像”。你要单独看检索回来的是什么。一个有效的验证方式是把召回片段展示在一个调试面板里或者在日志里打印出来。然后你人工检查用户问“高血压患者能不能剧烈运动”Top3召回片段里有没有包含“高血压”“运动”“注意事项”这些关键实体。如果召回片段里根本没有关键实体说明切块有问题、问题Embedding换一下、或者相似度阈值设置太高。如果召回片段有内容但模型没用好那才是提示词的问题。一定要把这两个环节拆开排查。常见指标是TopK命中率和上下文完整性。比如用户问题包含两个实体召回片段至少覆盖其中一个如果一个问题需要跨多个资料片段回答还要看这些片段能不能同时被召回。4.3 医疗场景的提示词设计医疗场景和普通聊天不太一样提示词里必须加边界约束。我常用的系统提示词思路是你是一名智能导诊助手。只能基于提供的知识库片段和知识图谱结果回答。不能给出具体药品剂量。如果信息不足建议用户前往医院就诊。回答最后提示“以上信息仅供参考不能替代医生诊断”。这样写的好处有三个减少模型幻觉让项目更合规答辩时也能体现需求分析做得细。模型输出如果不稳定先检查系统提示词不要急着换大模型。4.4 从RAG到Agentic RAG什么时候值得升级普通RAG的流程是“检索一次生成一次”。Agentic RAG则会让模型自己判断要不要先搜知识库要不要查图谱如果第一次搜得不准要不要改写问题再搜一次搜索热词里经常出现agentic rag这类方案确实能给项目加分但我不建议一上来就做。Agent模式会明显增加调试难度尤其是模型在多次调用后可能会丢失上下文你需要额外处理工具调用日志。对于毕业设计先把普通RAG做到稳定再有余力时引入Spring AI Alibaba的Graph或Agent能力。答辩时把这个作为扩展方向讲比硬做一个半成品Agent更有说服力。5. Neo4j医疗知识图谱建模、导入和查询设计5.1 本体设计节点和关系比实体数量更重要构建知识图谱第一件事不是找大量数据而是设计本体。对这个项目来说最基础的节点类型是Symptom症状比如咳嗽、发热Disease疾病比如呼吸道感染、高血压Department科室比如呼吸内科、心血管内科Drug药品比如阿莫西林、布洛芬Check检查项目比如血常规、胸片关系可以这样设计Symptom - [RELATED_TO] - DiseaseDisease - [DEPARTMENT] - DepartmentDisease - [DRUG] - DrugDisease - [CHECK] - Check不需要做几千个节点做一个能覆盖典型问诊场景的小图谱就够。比如呼吸科10个症状、8种疾病、5个科室、6种药已经足够演示。图谱的价值在于结构不在数量。评委问“你这个图规模多大”时你要回答“我设计了哪些实体和关系为什么这样设计”而不是说“我导入了10万条数据”。5.2 导入数据的方式数据导入常用的有几种Cypher手动创建、LOAD CSV批量导入、Neo4j Data Importer可视化导入、Python驱动写入。我建议用CSV LOAD CSV因为数据文件可以反复修改导入逻辑也容易讲解。假设你有一个symptoms.csv文件第一行是表头后续行是症状名称。导入示例LOAD CSV WITH HEADERS FROM file:///symptoms.csv AS row CREATE (:Symptom {name: row.name});Neo4j默认只能读取导入目录下的文件也就是Neo4j安装目录中的import文件夹。这个细节很多新手会踩坑报错“Couldnt load the external resource”时先检查文件位置。如果使用Docker方式需要把本地目录挂载到容器的/import路径这样CSV才能被读到。具体挂载配置和Neo4j版本有关建议参考官方文档确认路径。5.3 查询示例从“咳嗽”出发找相关疾病和科室图谱建好之后可以用Cypher查询验证。比如用户说“咳嗽”你要返回可能疾病和推荐科室MATCH (s:Symptom {name: 咳嗽})-[r:RELATED_TO]-(d:Disease) OPTIONAL MATCH (d)-[:DEPARTMENT]-(dept:Department) RETURN d.name AS disease, dept.name AS department, r.level AS level ORDER BY r.level DESC LIMIT 5;这里的r.level可以表示相关强度是数值型关系属性。如果没有这个属性去掉即可。把查询结果转成一个结构化文本再拼到Prompt里。后端可以用Spring Data Neo4j也可以直接使用Neo4j Java Driver。如果只是为了快速跑通用Driver查询然后手动拼字符串更直接。try (Session session driver.session()) { ListMapString, Object result session.run(query).list(); // 将result格式化为文本 }注意要处理查询结果为空的情况不能因为图谱没数据就让接口崩掉。5.4 图谱和RAG的融合方式融合方式有一个最容易上手的方案先查图谱再把结果转成自然语言拼进Prompt的“知识库”部分。举个例子用户问“咳嗽挂什么科”。系统先解析出实体“咳嗽”通过Cypher查到科室“呼吸内科”然后Prompt里加入一段“知识图谱返回咳嗽可能关联呼吸道感染推荐就诊科室为呼吸内科。” 模型看到这段话后回答就更准确。更高级的方案是把图谱结果作为过滤条件再用向量检索召回与这些实体相关的文档片段。这样能把“咳嗽”先限定到呼吸道疾病再检索对应科普文章效果会更好但复杂度也更高。针对毕业设计先用第一种方案把链路打通在论文里提一句“未来可基于图谱增强RAG召回”完全足够。6. 小程序端和联调体验会话、流式和异常处理6.1 会话管理医疗问诊天然是多轮对话。用户先问“咳嗽怎么办”接着说“还有点发烧”如果后端不保存历史第二句话就没有上下文模型无法理解“还有点发烧”是在原有咳嗽基础上补充的信息。最简单的会话管理方案是前端把历史消息一起传给后端。后端把本轮消息和之前几轮消息拼接成Prompt。也可以用后端Memory机制维护会话状态。一个低成本的实现是后端创建一个sessionId用ConcurrentHashMap保存每个session最近N条消息。生产系统当然不该这么干但毕业设计足够用。答辩时老师如果问“内存保存会丢失怎么办”你就回答“生产环境应使用Redis”。小程序端请求时可以带上sessionId{ sessionId: abc123, question: 还有点发烧 }6.2 流式返回还是等完整返回如果模型回答很长一次性等待完整返回会让用户觉得卡住了。流式返回可以实现“打字机”效果看起来更智能。但流式会增加不少工作。后端要用SSE或WebSocket小程序端要用对应方式接收数据流还要处理半截文本的显示状态。如果目前还没把主流程跑完建议先做非流式请求保证功能稳定。等主流程稳定后再考虑流式。Spring AI支持流式调用返回FluxString之类的结果小程序端可以使用wx.request配合enableChunked或者改用WebSocket。具体实现取决于你的后端框架和小程序框架。6.3 请求超时、后端报错和空回答联调阶段最常见的现象是前端一直转圈最后提示“请求超时”。这时不要急着改前端先按顺序排查后端控制台有没有收到请求模型API调用需要多久Neo4j查询是否阻塞向量库检索是否返回了空结果前端要写友好的错误处理逻辑。网络失败、后端500、模型返回空、回答内容过长这几种情况要有不同提示。不要直接把异常堆栈抛给用户。我一般会在后端加一个统一响应体结构类似{ code: 0, message: success, data: { answer: 根据您描述的症状建议前往呼吸内科就诊, sources: [指南标题1, 知识图谱节点] } }前端根据code字段判断是否成功再展示data里的内容。这样出错时排查链路很清晰。6.4 使用微信开发者工具进行网络调试调试小程序请求直接用微信开发者工具的Network面板就够了不需要额外抓包工具。打开“模拟操作”里的Network标签可以看到每个请求的URL、Header、请求体和响应体。如果后端返回错误响应体里一般有异常信息。要注意开发者工具的Network只显示小程序进程发出的请求本地后端如果没收到请求可能是URL配错、域名未校验、或者用了127.0.0.1但后端监听的是IPv6。可以先在工具里用curl验证后端地址是否能访问再回到小程序里排查。正式发布的小程序要求所有请求域名必须是HTTPS并且在小程序后台配置合法域名。毕业设计阶段可以做本地演示但论文里要写明部署方案。7. 从能跑到能答辩演示用例、排错路径和未来优化7.1 准备一条完整演示主流程答辩演示最忌讳临时输入一段问题结果模型半天不回答或者回答得和医疗场景没关系。你应该提前准备一条主流程。我的建议是这样的演示链路用户进入小程序首页点击“智能问诊”。输入“咳嗽三天有痰要不要去医院”。系统先通过Neo4j查询到咳嗽可能关联呼吸道感染推荐呼吸内科。系统再从RAG知识库召回“咳嗽护理与就医建议”文档片段。大模型综合图谱和文档返回一段结构化回答并附上“仅供参考”提示。演示时最好把“召回片段”和“图谱查询结果”在后台调试页面或日志里展示出来。这比只展示聊天内容更能体现工作量。7.2 常见报错排查顺序项目能跑之后不要以为就结束了。一旦换环境、换版本、换数据各种报错都会冒出来。我整理一个通用的排查顺序先看后端控制台日志有没有异常堆栈。确认Spring Boot启动成功端口没有被占用。确认Neo4j服务启动浏览器能打开7474页面。确认Neo4j里真的有数据执行一次Cypher查询。确认模型API Key有效并且账户有调用额度。确认向量库集合存在并且不是空集合。确认小程序请求的地址、请求方法、请求头正确。确认前端字段名和后端接收字段名完全一致。很多同学一报错就改代码改来改去发现是Neo4j没启动。先看环境再改代码是节省时间的关键。7.3 安全和合规医疗内容必须有的东西医疗类系统不能忽略合规哪怕只是毕业设计也要有这个意识。系统要在页面显眼位置和模型提示词里声明“这不构成诊疗意见”。如果用户输入了个人症状不应该把真实姓名、身份证号等敏感信息存入数据库。大模型输出可能不稳定实际落地时还需要对输出内容做过滤比如禁止生成具体药品剂量、禁止给出“停药”“自行治疗”等危险建议。在提示词里可以约束但不能完全依赖模型自觉。答辩时如果把合规和隐私保护作为设计考虑会明显加分。7.4 可以继续扩展的方向如果主流程已经非常稳定还有余力可以往这几个方向扩展一是接入Spring AI Alibaba的Graph组件让Agent自动决定是查图谱还是查文档。二是做用户反馈收集用户点击“有帮助/没帮助”后把反馈记录下来用于后续优化检索。三是做知识图谱的增量补全当系统发现用户问题中的实体不在图谱里时记录待补充。四是接入语音输入让患者可以用语音描述症状。这些方向不需要全部实现选一个做成“后续展望”写入论文即可。真正的重心还是把现有链路打磨到“每一步都能讲清楚”。最后留一个建议这个项目最值得花时间的不是把UI做得花哨也不是把模型参数调得很高而是把从问题到检索、再到图谱查询、再到模型生成的完整链路跑通。只要链路清晰每一层的数据都能被验证这个毕设就已经超过很多只停留在界面层面的项目了。