尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

基于RAG与知识图谱的AI医疗问诊平台系统搭建指南

基于RAG与知识图谱的AI医疗问诊平台系统搭建指南 AI智能医疗问诊平台系统是我见过比较适合 Python AI 方向毕业设计和技术演示的一类项目。它的核心不是写一个聊天机器人界面而是把 RAG 检索增强生成、LangChain 编排、Neo4j 知识图谱、FastAPI 后端、Vue3 前端完整串起来患者输入症状或问题系统先从医学知识库里检索相关片段再通过知识图谱找到疾病、症状、科室、药品之间的关系最后由大模型组织成一段带依据的回答。这个链路正是当前企业级 RAG 应用最常见的落地形态。这篇内容我会按实际搭建顺序来拆先说清楚每层组件解决什么问题再讲环境准备、后端接口、前端页面、联调测试和答辩演示。适合正在准备毕业设计、课程设计或者想完整做一个 RAG 实战项目的人参考。1. 先拆解技术栈RAG、LangChain、Neo4j、FastAPI、Vue3 各负责什么不少人在拿到这个题目时第一反应是“技术栈太多不知道从哪入手”。其实只要把每个组件在链路里的位置搞清楚事情就不复杂。1.1 它到底解决什么问题传统问答系统有两种常见做法一种是纯规则匹配写死在代码里换一批问题就失效另一种是直接问大模型回答流畅但没有依据医疗场景下还可能一本正经地编造疾病。这个项目把两种做法折中了一下先用 RAG 把自有知识库里的医学资料变成可检索内容再用 Neo4j 知识图谱把疾病、症状、科室、药品之间的关系结构化。用户提问进来后系统不是直接让大模型自由发挥而是先看知识库里有没有相关证据再看图谱里有没有对应关系最后才让大模型组织回答。所以它在答辩时的亮点不是“能聊天”而是“有检索、有依据、有结构化展示”。这一点要提前想清楚。1.2 LangChain 在链路中的位置LangChain 是编排层不是模型。它负责把文档加载、文本切分、向量化、检索、Prompt 模板、大模型调用、输出解析这些环节串起来。很多初学者误以为 LangChain 本身能做问答实际上它更像一个“管道工”。你要给它文档加载器它才知道读什么文件你要给它 embedding 模型它才知道怎么把文本转成向量你要给它大模型接口它才知道怎么生成回答。这个项目里LangChain 最核心的贡献是把 RAG 流程做成可配置、可替换的链条。比如你想把本地向量库从 FAISS 换成 Chroma或者把大模型从在线 API 换成本地部署模型LangChain 提供了一层统一接口不需要把业务代码全部重写。1.3 Neo4j 知识图谱为什么值得加进来纯 RAG 的问题在于向量检索只能找到“字面上相似”的内容很难回答关系型问题。比如患者问“发热伴头痛应该挂什么科”如果知识库里没有一篇文档直接写这句话向量检索可能返回一堆相关性不高的片段。这时候知识图谱更有价值。Neo4j 是按节点和关系存储数据的图数据库你可以把“感冒”建为疾病节点“发热”建为症状节点“呼吸内科”建为科室节点然后用关系表示“感冒表现为发热”“感冒建议挂呼吸内科”。用户问发热时通过 Cypher 查询可以快速找到相关疾病和科室。知识图谱还有一个额外优势它能把实体关系变成可视化图。前端展示一张“疾病—症状—科室—药品”的关系图谱比单纯的文字回答更容易让评委记住。1.4 FastAPI 和 Vue3 的角色FastAPI 是后端接口层负责接收前端请求、调用 LangChain 链路、查询 Neo4j、返回统一 JSON。它自带交互式文档/docs调试接口很方便也不用额外写 Swagger 配置。Vue3 是前端界面层负责展示对话、症状输入、结果卡片和知识图谱。选 Vue3 不是因为 Vue2 不行而是因为 Vue3 的组合式 API 在组织聊天逻辑、接口请求、状态管理时更清晰项目结构也更符合当前企业主流技术方向。整体数据流是用户问题 - Vue3 前端 - FastAPI 接口 - LangChain 链路 - 向量库检索 - Neo4j 图谱查询 - 大模型生成回答 FastAPI 组装结果 - Vue3 展示答案 知识图谱理解了这个流程后面写代码顺序就顺了。2. 环境准备先把 Neo4j、Python、Vue3 这三层地基搭好“能跑”永远是第一优先级。很多项目看起来功能多结果在环境环节就卡了半天后面全被打乱。这里建议按“Neo4j 先跑通、Python 后端再启动、Vue3 最后联调”的顺序来。2.1 版本和运行方案怎么选我建议 Python 使用 3.10 或 3.11。太老的版本对 LangChain 和 Pydantic 的支持都不够友好太新的 Python 版本有时候会遇到某个依赖还没适配的情况。Node.js 建议 18 以上Vue3 和 Vite 对 Node 版本有要求太低会启动报错。Neo4j 建议使用 Community 版本社区版免费足够课程设计和毕业设计演示。版本选 5.x 或 4.x 都可以关键是要保证 Neo4j 驱动neo4jPython 包和服务器版本兼容。大模型部分有两种方案在线 API 方案调用开放平台的大模型接口需要网络和 API Key优点是本地不需要 GPU。本地模型方案用 Ollama、llama.cpp 等方式跑开源模型优点是数据不出内网但需要一定内存和显存。我的建议是课程设计先走在线 API把链路跑通如果论文里想强调本地部署再把模型切换到本地。不要一上来就卡在模型下载上。2.2 安装 Neo4j 并导入基础医学图谱最快的方式是用 Docker 跑 Neo4j Communitydocker run -d \ --name neo4j-demo \ -p 7474:7474 -p 7687:7687 \ -e NEO4J_AUTHneo4j/yourpassword \ -e NEO4J_PLUGINS[apoc] \ neo4j:5-community浏览器访问http://localhost:7474用neo4j和设置好的密码登录。看到 Neo4j Browser 就能继续了。如果你不用 Docker也可以下载 Neo4j Desktop 或安装包。安装后要注意两点一是记住初始密码二是确认 Bolt 端口是7687HTTP 端口是7474。后面 FastAPI 连接用的基本都是 Bolt 协议。启动后先在 Neo4j Browser 里执行一段最简单的 Cypher创建几个节点CREATE (d:Disease {name: 感冒, description: 常见上呼吸道感染疾病}); CREATE (s:Symptom {name: 发热}); CREATE (dep:Department {name: 呼吸内科}); CREATE (m:Medicine {name: 对乙酰氨基酚}); CREATE (d)-[:HAS_SYMPTOM]-(s); CREATE (d)-[:SUGGEST_DEPT]-(dep); CREATE (d)-[:CAN_USE]-(m);这段不是完整知识库只是让你确认 Neo4j 写入和查询没有问题。真实项目里可以把公开医学教材、医学百科里的结构化内容整理成 CSV再用 Cypher 批量导入。2.3 准备 Python 后端环境创建虚拟环境并安装依赖python -m venv venv source venv/bin/activate # Windows 上使用 venv\Scripts\activate pip install fastapi uvicorn langchain langchain-community langchain-openai pip install neo4j sentence-transformers faiss-cpu pydantic python-dotenv不同 LangChain 版本的导入路径变化较大。新版本里很多组件已经拆到langchain-community和langchain-openai不要只装langchain一个大包就以为全部可用。遇到ModuleNotFoundError时优先检查是哪个包没装不要盲目升级版本。2.4 初始化 Vue3 前端项目用 Vite 创建 Vue3 项目npm create vuelatest frontend cd frontend npm install npm install axios element-plus vue-router pinia echarts前端项目结构大致为frontend/ src/ api/ # axios 请求封装 views/ # 问诊页面、登录页面、知识库管理页面 components/ # 消息列表、知识图谱展示组件 router/ # 路由配置2.5 联调前先确认三个点每次我搭建这种前后端分离项目都会在写业务代码前先做三个最小验证Neo4j 浏览器能打开Cypher 能执行成功。FastAPI 启动后访问http://localhost:8000/docs能显示接口文档。Vue3 项目npm run dev能正常打开默认页面。这三个点全通过说明环境没有问题。后面万一报错就可以把重心放到业务逻辑和参数配置上。3. 后端核心实现RAG 链路、Cypher 查询和 FastAPI 接口后端是整个项目的核心。最好按“知识库处理 - 向量检索 - 大模型问答链 - 图谱查询 - 接口组装”的顺序逐步写完。3.1 医学知识文档的加载和切分RAG 第一步是把医学知识文档读进来并切分成适合检索的片段。常见文件有txt、md、pdf课程设计阶段用 Markdown 或纯文本最省事。from langchain_community.document_loaders import DirectoryLoader from langchain_community.document_loaders import TextLoader from langchain.text_splitter import RecursiveCharacterTextSplitter loader DirectoryLoader(knowledge_base/, glob**/*.md, loader_clsTextLoader) documents loader.load() splitter RecursiveCharacterTextSplitter( chunk_size500, chunk_overlap80, separators[\n\n, \n, 。, , , , ], ) chunks splitter.split_documents(documents)chunk_size表示每个片段最大字符数chunk_overlap表示相邻片段重叠长度。为什么要有重叠因为如果一句话刚好被切在边界上检索时可能丢失关键信息。中文医疗文本我一般习惯切得小一点500 到 800 字符比较稳。太大检索精度下降太小上下文不完整。3.2 向量化与检索embedding 模型怎么选切分后的文档不能直接给大模型要转成向量存入向量库。项目里可以选择在线 embedding 接口也可以选择本地模型。在线方式from langchain_openai import OpenAIEmbeddings embeddings OpenAIEmbeddings(modeltext-embedding-3-small)本地方式from sentence_transformers import SentenceTransformer from langchain_community.embeddings import HuggingFaceEmbeddings embeddings HuggingFaceEmbeddings( model_nameBAAI/bge-small-zh-v1.5 )本地 embedding 的好处是不需要频繁调用外部接口但第一次运行会下载模型可能很慢。如果网络条件不好建议提前把模型下载好放到本地目录。向量库用 FAISS 或 Chroma 都可以。FAISS 轻量适合演示Chroma 多了持久化能力适合后续扩展。from langchain_community.vectorstores import FAISS vectorstore FAISS.from_documents(chunks, embeddings) vectorstore.save_local(data/faiss_index)检索时调用as_retrieverretriever vectorstore.as_retriever(search_kwargs{k: 4})k表示召回多少片段。不要调太大4 到 6 个片段足够生成回答调太大会让回答变得冗长还容易引入无关信息。3.3 用 LangChain 组装一条 RAG 问答链有了检索器再配上 Prompt 和大模型就能组成完整的问答链路。新版本 LangChain 更推荐用 LCEL 方式from langchain_openai import ChatOpenAI from langchain.prompts import ChatPromptTemplate from langchain_core.output_parsers import StrOutputParser from langchain_core.runnables import RunnablePassthrough llm ChatOpenAI(modelgpt-4o-mini, temperature0.2) prompt ChatPromptTemplate.from_messages([ (system, 你是一个医疗知识助手。请根据检索资料回答问题。如果资料不足要明确告诉用户无法确认并建议及时就医。回答最后加一句本回答仅供参考不能代替医生诊断。), (human, 检索资料\n{context}\n\n用户问题\n{question}), ]) rag_chain ( {context: retriever, question: RunnablePassthrough()} | prompt | llm | StrOutputParser() )调用时直接传入问题result rag_chain.invoke(发热伴头痛应该挂什么科)这里context来自检索器question来自用户输入。temperature调低一点医疗场景的回答会更保守、更稳定。3.4 用 Cypher 查询 Neo4j 知识图谱RAG 链路负责找文本证据知识图谱负责找结构化关系。两者可以并行执行最后一起返回前端。用 Neo4j Python 驱动直接执行 Cypher 最简单from neo4j import GraphDatabase driver GraphDatabase.driver( bolt://localhost:7687, auth(neo4j, yourpassword) ) def query_disease_by_symptom(symptom: str): cypher MATCH (d:Disease)-[:HAS_SYMPTOM]-(s:Symptom) WHERE s.name $symptom RETURN d.name AS disease, d.description AS description LIMIT 5 with driver.session() as session: result session.run(cypher, symptomsymptom) return [record.data() for record in result]如果想让项目更强一点可以把查询链路封装成“先提取实体再查关系”。比如用户说“发热伴头痛”可以先用大模型提取“发热”“头痛”两个症状再分别查关联疾病。不过这一步会增加复杂度和耗时前期可以先不做。3.5 FastAPI 接口设计和统一返回格式接口层建议统一返回结构这样前端解析方便也显得后端设计规范。from fastapi import FastAPI from fastapi.middleware.cors import CORSMiddleware from pydantic import BaseModel app FastAPI(titleAI 医疗问诊平台) app.add_middleware( CORSMiddleware, allow_origins[*], allow_credentialsTrue, allow_methods[*], allow_headers[*], ) class ChatRequest(BaseModel): question: str session_id: str default class ChatResponse(BaseModel): code: int message: str data: dict | None app.post(/api/chat, response_modelChatResponse) def chat(req: ChatRequest): try: answer rag_chain.invoke(req.question) graph_data query_disease_by_symptom(req.question) return { code: 0, message: ok, data: { answer: answer, graph: graph_data, }, } except Exception as e: return { code: 500, message: str(e), data: None, }启动命令uvicorn main:app --reload --host 0.0.0.0 --port 8000--reload只在开发时用生产环境不要开。前端如果需要通过http://localhost:8000/docs查看接口FastAPI 会自动生成。4. Vue3 前端页面问诊对话框、知识图谱可视化、接口联调后端接口通了以后前端工作其实不难难的是把交互做得自然、把图谱展示清楚。4.1 页面功能拆解一个完整的问诊页面至少包含消息列表展示用户消息和 AI 回答。输入区域支持输入症状描述或问题。回答卡片把 AI 回答、相关科室、建议科室用卡片展示。知识图谱区域展示疾病、症状、科室、药品关系。历史会话列表可以回看之前的问答。页面不用做得很复杂但尽量把“回答 图谱”放在同一屏方便演示。4.2 对接后端接口用 axios 封装一个请求模块import axios from axios const api axios.create({ baseURL: http://localhost:8000, timeout: 30000, }) export function sendChat(question, sessionId default) { return api.post(/api/chat, { question, session_id: sessionId, }) }在组件里调用async function handleSend() { const question inputText.value.trim() if (!question) return messages.value.push({ role: user, content: question }) inputText.value const res await sendChat(question) const data res.data.data messages.value.push({ role: assistant, content: data.answer, graph: data.graph, }) }如果担心大模型接口耗时较长可以把 axios 超时时间调到 60 秒。演示时网络不稳定很常见不要一超时就以为代码写错了。4.3 用 ECharts 渲染知识图谱后端返回的graph通常是节点和边的数组前端需要转换成 ECharts 关系图能用的格式。const nodes graph.map(item ({ id: item.disease, name: item.disease, category: 0, })) const links graph.map(item ({ source: item.disease, target: item.description ? description : 科室, }))实际项目里节点可能是疾病、症状、科室、药品混合。最好在后端就把节点和关系统一成{ nodes: [ {id: 感冒, category: 疾病}, {id: 发热, category: 症状} ], links: [ {source: 感冒, target: 发热, relation: HAS_SYMPTOM} ] }前端用 ECharts 的graph系列展示启动force布局echarts.init(dom).setOption({ series: [{ type: graph, layout: force, data: graphData.nodes, links: graphData.links, roam: true, label: { show: true }, }], })这样展示出来的可视化比单纯文字有说服力得多也是毕设演示时的加分项。4.4 可选登录权限和知识库管理页面如果项目要求更完整可以加一个简单登录模块后端用 JWT 返回 token。前端登录后把 token 存在 Pinia 或 localStorage。Vue Router 配置全局守卫未登录跳转登录页。还可以做一个知识库管理页让管理员上传 Markdown 或 TXT 文档调用后端接口重新切分、更新向量库。这个功能会让项目从“演示 Demo”变成“可维护系统”但要注意工作量。课程设计阶段先保证核心问答链路再做这些扩展。5. 单条问诊跑通之后批量测试、常见报错和排查顺序代码写完后最容易被忽略的是“稳定验证”。我在带学生做类似项目时发现很多人只跑了一两条成功案例就以为完成了结果答辩现场换一个问题就卡住。5.1 先跑通一条最小问诊建议先准备一个固定的测试问题例如“发热伴头痛应该挂什么科”。判断成功的标准有三个后端接口返回code0没有异常。回答内容不是空字符串并且包含“建议就医”等提示。Neo4j 查询结果不为空前端能展示图谱。只有这三个条件同时满足才能算这条问诊真正跑通。5.2 批量测试、并发和失败重试项目要稳定不能只测一条。我一般会准备 20 到 50 条测试问题写一个脚本依次请求后端接口记录每个问题的耗时、返回状态、回答长度和非空概率。import requests import time questions [ 感冒了可以吃什么药, 头痛应该挂什么科, 发热伴咳嗽怎么办, 胃痛需要做胃镜吗, ] for q in questions: start time.time() try: r requests.post(http://localhost:8000/api/chat, json{question: q}, timeout60) data r.json() cost time.time() - start print(q, data[code], round(cost, 2), len(data.get(data, {}).get(answer, ))) except Exception as e: print(q, ERROR, str(e))测试时不要一上来就开大并发。RAG 链路里涉及向量检索和大模型调用并发过高容易导致超时。先逐个跑再尝试三五条并发最后才考虑是否需要引入消息队列。5.3 常见问题排查顺序下面是这个项目里最常见的几类问题和排查顺序。现象优先排查位置常见原因Neo4j 连不上Neo4j 服务状态、账号密码、端口服务没启动密码错误Bolt 端口不是 7687LangChain 导入报错pip 包列表、导入路径LangChain 新版本拆包缺少langchain-community向量检索结果为空文档加载路径、切分结果、embedding 模型文档路径不对切分后 chunks 为空向量库未保存成功前端跨域FastAPI CORS 配置后端未添加CORSMiddlewareallow_origins未包含前端地址接口超时大模型调用耗时、网络、temperature和上下文长度问题较长检索片段太多大模型接口响应慢回答内容为空后端日志、Prompt、LLM 返回大模型返回了空字符串输出解析器出错API Key 无效我自己的排查顺序是先看接口返回的message再看后端控制台日志然后依次确认 Neo4j 是否连通、向量库是否加载成功、大模型是否真的返回内容。不要一上来就改代码先定位是哪一层出问题。5.4 医疗场景要注意的边界这个项目本质上是技术演示。系统里的医学知识可能来自网络资料或公开教材可能存在不准确、不完整的情况不能把它当真实诊断工具。建议在页面底部、接口返回里都加上“本回答仅供参考不能代替医生诊断”的提示。演示时也要主动说明项目重点在于 RAG、知识图谱、前后端分离的技术实现而不是医学专业性。这样反而显得你思路清晰。6. 毕业设计答辩和后续扩展从“跑通”到“讲清楚”对课程设计和毕业设计来说系统能跑只是基础。真正拉开差距的是你能不能把项目讲清楚以及有没有可扩展的方向。6.1 答辩演示怎么走更稳建议按下面的顺序演示不要上来就敲代码先展示系统架构图说明数据从哪来、经过哪些处理、最终到哪展示。再演示一段问诊输入“发热伴头痛应该挂什么科”。展示回答内容同时把知识图谱区域展示出来说明“症状关联到了哪些疾病、科室”。然后打开 Neo4j Browser执行一条 Cypher证明知识图谱数据是真实存在、可以查询的。最后打开 FastAPI 的/docs页面说明接口设计。这套路径最大的好处是让评委看到“前端 - 后端 - 向量库 - 知识图谱 - 大模型”的完整闭环而不是只看到一个聊天框。6.2 讲解重点为什么这样设计答辩时至少要想清楚这几个问题为什么用 RAG 而不是微调大模型因为医疗知识更新快微调成本高RAG 可以直接替换知识库不重训模型。为什么加 Neo4j 知识图谱因为医疗实体关系适合图结构图谱能提供结构化证据和可视化展示。为什么用 FastAPI因为类型校验、自动文档、异步支持做接口很顺畅。为什么用 Vue3因为组件化开发效率高Composition API 更适合管理聊天状态。不要只背概念。最好能结合自己的代码“这里context是检索器返回的内容这里graph是 Neo4j 查出来的结果”这种讲解比空谈“技术选型”更有说服力。6.3 代码组织优化建议如果希望项目在代码规范上更符合“毕设标准”建议做这几件事把环境变量放到.env文件例如 Neo4j 地址、密码、API Key、模型名称。把后端拆成api、services、models、data等模块不要全部堆在main.py。为每个接口写请求参数和返回结构示例。写一个清晰的README.md说明技术栈、运行步骤、测试问题、常见问题。这些不是炫技而是让项目具备“可复现性”。评委如果照着 README 能跑起来项目可信度会大幅提升。6.4 可以继续扩展的方向如果你的项目还想做得更深可以考虑用 LangGraph 把问诊流程变成多轮节点先收集症状再做鉴别诊断。加入多轮记忆让大模型记住用户之前提到过的病史。增加语音输入前端录音后端做语音转文字。使用更完整的医学知识图谱数据把节点数从几十个扩展到几千个。把向量库统一用同一个服务保存支持增量更新。扩展方向不用全做选一个做深就够了。比如加上多轮记忆或者加上 LangGraph 流程控制都是不错的论文切入点。我最后想说的是这个项目真正考验你的不是某个组件的高级用法而是你能不能让整条链路稳定跑起来并且在答辩时讲清楚每一步为什么存在。能做到这一点毕业设计就已经成功了一大半。
返回列表