
每年毕业设计季医疗类AI选题都是“重灾区”。不是因为题目不好而是太多人把“AI医疗”做成了“调一个接口、套一个UI”的演示品输入症状返回一段通用建议既没有医学知识支撑也没有可追溯的推理路径。这种系统交上去答辩老师一问“你的知识从哪来”基本就答不上来了。本文要写的这套“AI智能医疗问诊平台系统”核心是用RAG检索增强生成 LangChain Neo4j 知识图谱 FastAPI Vue3搭一个真正能把“知识”和“问答”串起来的完整项目。直接给出我的判断如果你是 Python 方向的 AI 毕业设计或课程设计这个技术组合是目前性价比最高的选题之一。它既避开了纯算法调参的深度瓶颈也避开了纯 CRUD 前端开发的低区分度而是把一个真实业务场景导诊、预问诊、知识问答用主流的大模型工程化手段落地。这篇文章会讲清楚三件事为什么医疗问诊场景必须用 RAG 和知识图谱而不是直接裸调大模型这套系统的完整架构和核心流程怎么拆从环境安装到后端接口再到前端页面怎么一步步跑通并写出能过答辩的代码。1. 这篇文章真正要解决的问题先看一个常见场景用户输入“我嗓子疼、有点发烧该挂哪个科”普通大模型会给你一段几百字的科普最后含糊地说“建议就医”。这不是问诊是百度百科。真正的医疗问诊系统需要做到能识别症状实体咽喉痛、发热。能关联科室耳鼻喉科、发热门诊和疾病上呼吸道感染、扁桃体炎。能给出“考虑为/建议去”这样的临床分诊逻辑。最关键的是系统能说清楚这个结论来自哪条医学知识或关系路径。这就是 RAG 和知识图谱结合的原因。RAG 负责从文档知识库里检索高相关片段增强大模型的回答质量Neo4j 知识图谱负责把实体和关系存储为图结构让“症状—疾病—科室—药物”之间的多跳关联可以被查询和推理。如果你不看这些只是用openai.ChatCompletion接一个对话框系统是“可用”的但没有任何可解释性和医学边界。答辩时老师问你“如何避免模型胡说八道”你给出“我做了 RAG 和知识图谱关联过滤”这个答案加分是完全不同级别的。所以本文的核心不是教你堆代码而是帮你建立一条从数据到图谱、从图谱到检索、从检索到大模型回答、从后端到前端的完整链路。2. 核心概念RAG、知识图谱、LangChain 到底在系统里扮演什么角色2.1 RAG 不是“把资料喂给模型”而是“带着资料回答问题”RAGRetrieval-Augmented Generation检索增强生成的思路可以这样理解大模型像一个记忆力有限、还容易编故事的专家。你直接问它医学问题它可能凭训练时的记忆回答而这些记忆未必准确。RAG 的做法是在生成答案之前先从外部知识库检索一组相关文档拼进提示词里让模型只能依据这些文档回答。在实际系统中RAG 通常包含三个环节文档加载与切分medical_docs.md 或 PDF 切分成片段。向量化存储将片段用 Embedding 模型转成向量存入向量数据库。检索增强用户提问时先向量检索相似片段再与大模型调用合并。在医疗场景里这个设计直接决定系统的可信度。知识库里有“上呼吸道感染通常对症治疗、建议多休息、发热超过38.5℃可使用退热药”当问到“发热怎么办”时系统检索出这条知识大模型再按照它组织回答输出就会稳定得多。2.2 知识图谱负责“关系”向量数据库负责“语义”RAG 解决的是“找出一段相关文字”的问题但很多医疗问答本质上不是文字匹配而是关系推理。比如“糖尿病”和“酮症酸中毒”之间是什么关系“胰岛素”除了降血糖还有什么作用“高血压患者不能使用哪类感冒药”这些关系如果拆成一段段文档量会非常大。但用知识图谱可以很自然地表达为(症状:发热)-[属于]-(疾病:上呼吸道感染) (疾病:糖尿病)-[并发症]-(疾病:酮症酸中毒) (药品:阿司匹林)-[禁忌]-(疾病:胃溃疡)Neo4j 是目前最流行的图数据库它用节点和关系存储数据查询语言是 Cypher。相比关系型数据库的多表 joinCypher 写多跳关系查询非常直观MATCH (s:Symptom {name: 发热})-[:所属]-(d:Disease)-[:推荐科室]-(dep:Department) RETURN d.name, dep.name这条查询的含义是找出“发热”对应的疾病再找出这些疾病的推荐科室。如果用 MySQL可能需要三张表来回 join而在图数据库中这是最基本的路径查询。2.3 LangChain 的工作是“编排”LangChain 在这个系统里更像调度中心连接三个东西大模型、向量数据库、知识图谱。它负责把用户问题拆成“先检索、再查图谱、最后生成回答”的流程也负责维护对话历史。需要说明的是LangChain 版本迭代很快API 变动频繁。本文的示例会尽量使用稳定通用写法重点是给大家演示链路逻辑。实际开发时如果遇到 API 变化以你安装版本的官方文档为准。2.4 FastAPI 和 Vue3 解决的是“能否落地”FastAPI 承担后端接口层把 RAG 链路包装成 REST APIVue3 承担前端界面负责用户输入、结果展示、科室推荐卡片等交互。很多做算法的同学容易忽略这一层但在毕设答辩中一个能交互的前端界面比一堆 Jupyter Notebook 输出有说服力得多。3. 系统整体架构设计在写代码之前先用一张图把整个系统串起来本文不画 Mermaid用文字描述用户输入症状 ↓ Vue3 前端页面 ↓ HTTP JSON FastAPI 后端 ├── 调用 RAG 检索从向量数据库找相似医学文档 ├── 调用 Neo4j 查询从知识图谱找疾病、科室、药品关联 └── 拼接 Prompt 调用大模型 ↓ 返回结构化 JSON回答 推荐科室 相关疾病 知识来源 ↓ Vue3 展示回答卡片与推荐结果这里面最重要的是一个判断不是所有问题都要走完整链路。我们在实际系统中做了一层简单的意图分类通用寒暄类“你好”直接走大模型会话。症状问诊类“我嗓子疼”同时走 RAG 检索和 Neo4j 图谱查询。医学知识类“高血压能喝酒吗”只走 RAG 检索。这样设计既减少调用成本也让系统行为更可控。架构层面我建议将系统拆成三个模块backend/FastAPI 项目。frontend/Vue3 项目。data/知识文档与图谱构建脚本。目录结构如下medical-platform/ ├── backend/ │ ├── app.py # FastAPI 主入口 │ ├── rag_engine.py # RAG 检索封装 │ ├── graph_engine.py # Neo4j 查询封装 │ ├── llm_service.py # 大模型调用封装 │ └── requirements.txt ├── frontend/ │ ├── src/ │ │ ├── views/Home.vue │ │ ├── api/chat.js │ │ └── App.vue │ └── package.json └── data/ ├── medical_docs.md # 医学知识文档 ├── build_graph.py # 构建知识图谱脚本 └── graph_data.csv # 实体关系数据这样的结构在论文里也非常好画架构图。4. 环境准备与前置条件以下是推荐的环境组合版本以实际安装为准。组件作用推荐版本Python后端与算法3.10Node.js前端构建18Neo4j Community图数据库4.x 或 5.xFastAPIWeb 框架最新稳定版LangChain编排框架最新稳定版Vue3 Vite前端最新稳定版大模型 API 或本地模型生成回答根据你的预算选择这里有个现实问题毕业设计环境很难都跑在 GPU 服务器上。更稳妥的方案是使用在线大模型 API比如 OpenAI、智谱 GLM、通义千问等。如果你没有付费账号也可以用开源的本地模型比如 Qwen2-7B 配合 Ollama 或 llama.cpp 启动一个 OpenAI 兼容接口。注意无论使用哪种都要保证你的调用凭证不被硬编码到前端代码中。4.1 安装 Neo4jNeo4j 的安装方式有两种桌面版和 Docker。推荐使用 Docker因为卸载方便、版本可控。docker run -d \ --name neo4j \ -p 7474:7474 -p 7687:7687 \ -e NEO4J_AUTHneo4j/yourpassword \ neo4j:5-community启动后打开http://localhost:7474用neo4j / yourpassword登录即可。如果使用桌面版直接创建数据库并记住连接地址。4.2 初始化 Python 环境建议用虚拟环境隔离项目依赖。mkdir medical-platform cd medical-platform python -m venv venv source venv/bin/activate # Windows 下使用 venv\Scripts\activate pip install fastapi uvicorn langchain langchain-community openai neo4j pypdf chromadb注意LangChain 从 0.1 开始将很多集成拆成了langchain-community所以上面安装的是主流组合。如果你的 LangChain 版本是 0.0.x可以不用安装langchain-community。具体以你安装后的导入报错为准。4.3 前端项目初始化使用 Vite 创建 Vue3 项目。npm create vitelatest frontend -- --template vue cd frontend npm install npm install axios到这里前后端骨架已经准备好了。5. 核心流程拆解从数据到问答5.1 第一步准备医学知识文档我们先准备一个简单的medical_docs.md文件内容不用多但必须覆盖常见科室和疾病的描述。例如# 医学知识库 ## 呼吸内科 上呼吸道感染通常表现为鼻塞、流涕、咳嗽、咽痛和发热。治疗以对症治疗为主注意休息多饮水。发热超过38.5℃可使用解热镇痛药。 肺炎常见症状包括发热、咳嗽、咳痰、胸痛严重时出现呼吸困难。如出现持续高热或呼吸困难应立即就医。 ## 消化内科 急性胃肠炎通常表现为腹痛、腹泻、恶心呕吐。建议清淡饮食防止脱水可口服补液盐。若出现严重腹泻、血便或高热请及时就医。 胃溃疡的典型症状是上腹痛多在餐后加重。需要幽门螺杆菌检测规范治疗。 ## 神经内科 偏头痛多为单侧搏动性头痛伴恶心、畏光。避免诱因急性发作可服用非甾体抗炎药。 脑卒中的预警信号包括突发口角歪斜、一侧肢体无力、言语不清出现上述症状应立即拨打急救电话。这个文档就是 RAG 的外部知识源。在实际毕设中你可以花更多时间扩展但不要一开始就追求海量数据先跑通链路再扩充。5.2 第二步构建 Neo4j 知识图谱构建图谱前需要把实体关系整理成结构化数据。我们用一个更简单的graph_data.py来演示而不直接用 CSV因为 Python 脚本更容易和后续流程联动。python # data/build_graph.py from neo4j import GraphDatabase driver GraphDatabase.driver(bolt://localhost:7687, auth(neo4j, yourpassword)) cypher MERGE (s:Symptom {name: $symptom}) MERGE (d:Disease {name: $disease}) MERGE (s)-[:SYMPTOM_OF]-(d) MERGE (d)-[:RECOMMEND_DEPT]-(dep:Department {name: $department}) data [ {symptom: 发热, disease: 上呼吸道感染, department: 呼吸内科}, {symptom: 咳嗽, disease: 上呼吸道感染, department: 呼吸内科}, {symptom: 咽痛, disease: 上呼吸道感染, department: 呼吸内科}, {symptom: 腹痛, disease: 急性胃肠炎, department: 消化内科}, {symptom: 腹泻, disease: 急性胃肠炎, department: 消化内科}, {symptom: 头痛, disease: 偏头痛, department: 神经内科}, {symptom: 胸痛, disease: 肺炎, department: 呼吸内科}, {symptom: 上腹痛, disease: 胃溃疡, department: 消化内科}, {symptom: 肢体无力, disease: 脑卒中, department: 神经内科}, ] with driver.session() as session: for item in data: session.run(cypher, **item) driver.close() print(知识图谱构建完成)执行这个脚本前先确认 Neo4j 已经启动并且密码正确。执行后可以在 Neo4j Browser 里查看节点与关系。5.3 第三步实现 RAG 检索这里我们使用向量数据库来存储文档片段。为了减少配置复杂度我选择 ChromaDB 作为向量数据库因为它支持本地持久化不需要额外服务。rag_engine.py的核心逻辑如下# backend/rag_engine.py from langchain_community.document_loaders import TextLoader from langchain_text_splitters import CharacterTextSplitter from langchain_community.embeddings import OpenAIEmbeddings # 或其它 embedding from langchain_community.vectorstores import Chroma class RAGEngine: def __init__(self, docs_path, embedding_modeltext-embedding-ada-002): loader TextLoader(docs_path, encodingutf-8) documents loader.load() splitter CharacterTextSplitter(chunk_size300, chunk_overlap50) texts splitter.split_documents(documents) self.vectorstore Chroma.from_documents(texts, OpenAIEmbeddings(modelembedding_model), persist_directory./chroma_db) def search(self, query, k3): return self.vectorstore.similarity_search(query, kk) def build_prompt_with_context(self, query): docs self.search(query) context \n.join([doc.page_content for doc in docs]) prompt f请根据以下的医学知识库内容回答问题不要编造知识库之外的信息。 知识库内容 {context} 用户问题{query} 请给出准确、简洁的回答。如果知识库中没有足够信息请明确说“该问题需要进一步咨询专业医生”。 return prompt这里需要注意OpenAIEmbeddings需要你配置OPENAI_API_KEY环境变量。如果使用其它模型提供商可以替换成对应的 Embeddings 类。很多国产模型也提供兼容接口只需改一下openai_api_base。5.4 第四步实现 Neo4j 图谱查询图谱查询模块负责从 Neo4j 中获取实体关系。比如输入症状查询可能匹配的疾病与推荐科室。# backend/graph_engine.py from neo4j import GraphDatabase class GraphEngine: def __init__(self, uri, user, password): self.driver GraphDatabase.driver(uri, auth(user, password)) def query_disease_by_symptom(self, symptom): cypher MATCH (s:Symptom {name: $symptom})-[:SYMPTOM_OF]-(d:Disease) OPTIONAL MATCH (d)-[:RECOMMEND_DEPT]-(dep:Department) RETURN d.name AS disease, dep.name AS department with self.driver.session() as session: result session.run(cypher, symptomsymptom) return [{disease: record[disease], department: record[department]} for record in result] def close(self): self.driver.close()5.5 第五步FastAPI 接口层FastAPI 是把前面的模块组合在一起的关键。我们设计两个接口POST /api/chat处理对话返回回答、推荐科室、相关疾病。GET /api/health健康检查。app.py如下# backend/app.py import os from fastapi import FastAPI from pydantic import BaseModel from rag_engine import RAGEngine from graph_engine import GraphEngine from llm_service import LLMService app FastAPI(titleAI智能医疗问诊平台) class ChatRequest(BaseModel): message: str session_id: str default class ChatResponse(BaseModel): answer: str diseases: list[str] [] departments: list[str] [] related_docs: list[str] [] # 初始化引擎 rag RAGEngine(docs_path../data/medical_docs.md) graph GraphEngine(bolt://localhost:7687, neo4j, yourpassword) llm LLMService() app.post(/api/chat, response_modelChatResponse) def chat(req: ChatRequest): message req.message.strip() if not message: return ChatResponse(answer请输入有效的症状描述或问题。) # 1. 查询知识图谱 graph_result graph.query_disease_by_symptom(message) diseases [item[disease] for item in graph_result if item[disease]] departments list(set([item[department] for item in graph_result if item[department]])) # 2. RAG 检索增强 prompt rag.build_prompt_with_context(message) # 3. 调用大模型生成回答 answer llm.chat(prompt) # 4. 返回结果 return ChatResponse( answeranswer, diseasesdiseases, departmentsdepartments, related_docs[], )llm_service.py使用 LangChain 调用大模型。为了同时兼容在线 API 和本地模型我们可以这样写# backend/llm_service.py import os from langchain_openai import ChatOpenAI class LLMService: def __init__(self, modelgpt-4o-mini): # 如果设置 OPENAI_API_BASE可以指向本地模型服务 self.llm ChatOpenAI( modelmodel, api_keyos.getenv(OPENAI_API_KEY, sk-demo), base_urlos.getenv(OPENAI_API_BASE), ) def chat(self, prompt: str) - str: response self.llm.invoke(prompt) return response.content5.6 第六步Vue3 前端页面前端页面只需要一个聊天交互界面和一个结果展示卡片。Home.vue的核心部分如下!-- frontend/src/views/Home.vue -- template div classchat-container h1AI 智能医疗问诊/h1 div classchat-box div v-for(msg, idx) in messages :keyidx :classmsg.role {{ msg.content }} /div /div div classresult-card v-iflastResult pstrong推荐科室/strong {{ lastResult.departments.join(、) }}/p pstrong相关疾病/strong {{ lastResult.diseases.join(、) }}/p p classanswer{{ lastResult.answer }}/p /div input v-modelinputText keyup.entersendMessage placeholder请输入症状如发热、咳嗽 / button clicksendMessage发送/button /div /template script setup import { ref } from vue import axios from axios const messages ref([{ role: assistant, content: 您好请描述您的症状我会为您推荐科室并给出建议。 }]) const inputText ref() const lastResult ref(null) async function sendMessage() { if (!inputText.value.trim()) return messages.value.push({ role: user, content: inputText.value }) const { data } await axios.post(http://localhost:8000/api/chat, { message: inputText.value }) lastResult.value data messages.value.push({ role: assistant, content: data.answer }) inputText.value } /script前端代码里把后端地址写死成localhost:8000开发环境足够用。如果部署到服务器需要使用环境变量管理。6. 完整示例与效果验证上面每个模块的代码已经可以组成一个最小可运行系统。下面按顺序把所有服务跑起来。6.1 启动 Neo4j检查 Neo4j 容器状态docker ps | grep neo4j如果没有启动执行之前提到的docker run命令。6.2 构建知识图谱cd data python build_graph.py看到输出“知识图谱构建完成”后去 Neo4j Browser 执行MATCH (n) RETURN n LIMIT 25你能看到“发热”“咳嗽”“上呼吸道感染”等节点说明图谱数据写入成功。6.3 启动 FastAPI 后端cd backend export OPENAI_API_KEY你的密钥 # Windows 使用 set uvicorn app:app --reload --port 8000启动后可以在浏览器打开http://localhost:8000/docs看到 Swagger 文档。这本身就说明 FastAPI 接口层没有问题。点击POST /api/chat用如下 JSON 测试{ message: 发热 }预期返回中departments至少包含“呼吸内科”diseases至少包含“上呼吸道感染”。如果这里返回空大概率是 Neo4j 中症状名称匹配不上或图谱未构建成功。6.4 启动 Vue3 前端cd frontend npm run dev浏览器访问http://localhost:5173在输入框输入“发热”点击发送。等待几秒后页面会展示模型回答和推荐科室。此时整个链路已通。6.5 判断成功的标准后端日志无异常。前端能收到answer字段并且回答内容与知识库相关。departments数组非空。如果模型回答中出现知识库之外的详细药理建议说明 RAG 提示词约束没有生效需要调整rag_engine.py中的提示词。7. 常见问题与排查思路以下是这套系统最容易出现的几个问题。问题现象可能原因排查方式解决方案Neo4j 连接失败密码错误、端口未开放检查 Docker 容器、尝试用 Browser 登录重新设置NEO4J_AUTH确认 uri 是bolt://localhost:7687图谱查询返回空症状名称拼写不一致在 Neo4j Browser 执行MATCH (s:Symptom) RETURN s.name统一实体名或使用模糊匹配如CONTAINSChroma 持久化报错多个进程同时写向量库查看后端日志是否有锁文件删除./chroma_db后重启生产环境使用独立向量数据库服务大模型返回无关内容提示词约束太弱打印最终发送给模型的 prompt在提示词中增加“只使用知识库内容不要补充”langchain导入失败版本差异查看导入错误信息根据错误调整安装包例如langchain_community前端跨域报错FastAPI 未开启 CORS查看浏览器 console在 FastAPI 中添加CORSMiddleware其中跨域是最容易忽略的。给 FastAPI 加上 CORS 配置from fastapi.middleware.cors import CORSMiddleware app.add_middleware( CORSMiddleware, allow_origins[*], allow_methods[*], allow_headers[*], )8. 最佳实践与工程建议如果你要拿这套系统参加答辩或作为课程设计建议从以下角度提升质量。8.1 数据设计是系统灵魂目前示例的图谱只有 9 条关系仅供演示。真实做毕设时建议至少覆盖 20 种常见症状、10 个科室、30 种疾病。数据来源可以整理公开医学百科内容但注意版权和准确度。更重要的是每个数据都要标出来源这在答辩时非常加分。8.2 不要把 API Key 写进前端很多同学为了演示方便把大模型 API 密钥直接放在 Vue 的.env里这是安全大忌。正确的做法是密钥只存在于后端环境变量中前端永远不接触。部署到公网时还需要为后端接口增加简单的认证鉴权。8.3 设计一个“拒绝回答”策略医疗领域合规要求高。没有足够把握时系统必须说“该问题需要进一步咨询专业医生”而不是硬编一个答案。在提示词里明确这一规则再在后端加一层判断如果知识库检索出的文本片段相关度低于阈值直接返回模板话术。实现方式可以在rag_engine.py中读取相似度分数。8.4 日志与可解释性在llm_service.py的chat方法里加入日志记录检索到的文档片段、最终 prompt 和模型输出。这样一旦结果异常你可以快速定位是检索问题、提示词问题还是模型问题。import logging logging.basicConfig(levellogging.INFO) logger logging.getLogger(__name__) def chat(self, prompt: str): logger.info(Prompt: %s, prompt[:500]) response self.llm.invoke(prompt) return response.content8.5 前后端分离但不要割裂很多毕设项目最后变成前后端两个独立 Demo数据库只用来登录注册。这套系统建议把前端的结果卡片认真做一下至少展示“推荐科室”“相关疾病”“可能的注意事项”三个模块。你可以让后端一次性返回结构化字段前端用卡片展示视觉和逻辑上都会完整很多。8.6 考虑用 LangGraph 替代纯 LangChain Chain如果你是学有余力的进阶选手可以关注 LangGraph。LangChain 的 Chain 是线性流程而 LangGraph 能构建有环、有状态、可分支的 Agent 工作流。医疗问诊很适合先分诊再追问再判断是否急症最后给出建议。不过这是加分项不一定需要塞进基础毕设里。9. 总结与后续学习方向这篇文章从零拆解了“AI智能医疗问诊平台系统”的完整实现路径核心链路是医学文档 → 向量库RAG 检索 Neo4j 知识图谱 → LangChain 编排 → FastAPI 接口 → Vue3 展示。你真正需要理解的关键点有两个RAG 让大模型基于知识库回答知识图谱让多跳医学关系可查询两者结合能大幅提升医疗问答的可信度和可解释性。下一步建议你按以下顺序继续完善先跑通本文的完整代码确保链路正常运行。扩充医疗知识库和图谱数据让系统看起来“懂更多”。加入多轮对话记忆处理“咳嗽三天”“现在还发热”这类上下文。增加用户确诊记录和症状严重程度评估让推荐结果更有分层感。有条件的话把大模型替换为本地部署模型并写出一份部署文档。毕业设计不需要做得多“大”而是要把链条走完把每个环节讲清楚。这套 RAG 知识图谱的组合正好能体现你对大模型工程化的理解也足够支撑一份高质量的中期报告和毕业论文。趁现在把核心流程跑通你就有充分的时间去打磨细节。