☰
基于RAG与知识图谱的AI智能医疗问诊平台构建实践
2026/10/24 13:16:55 网站建设 项目流程

大模型方向做毕业设计或课程设计,最容易出现的问题不是模型选型,而是项目停留在“单接口问答 Demo”的程度:页面写死几条对话、后端只调一次模型、没有数据沉淀,也没有可展示的知识链路。AI智能医疗问诊平台这个题目之所以适合 Python AI 大模型方向,是因为它能把 RAG、LangChain、Neo4j 知识图谱、FastAPI、Vue3 串成一条完整的工程链路:用户输入症状,系统先检索医疗知识库,再从知识图谱中抽取疾病、症状、药物、科室之间的关系,最后交给大模型生成带依据的回答。这个项目既能体现你对大模型应用的理解,也能体现后端接口设计、前端交互和数据库建模的基本功。

下面按“项目定位 -> 环境搭建 -> 知识图谱建模 -> RAG 服务实现 -> 前端联调 -> 验证 -> 排错 -> 扩展”这条主线展开。文中代码用于说明实现思路,落地时请结合你自己的项目结构、依赖版本和模型服务地址调整。

1. 项目定位与整体架构设计

1.1 医疗问诊场景为什么需要 RAG 和知识图谱

先想清楚业务场景:用户输入“我最近头晕、心悸,可能是什么问题”,如果只把这个问题直接抛给大模型,模型会凭训练数据里的知识生成一段回答。这种做法有两个问题:第一,回答可能过时、不具体,甚至出现幻觉;第二,模型没有引用任何可追溯的医学资料,用户无法判断回答是否可信。

RAG 的做法是先检索再回答。系统先从内置的医学文档、指南、科普材料中检索与“头晕、心悸”相关的段落,再把检索结果作为上下文交给大模型。这样回答就有据可依,还能在页面上展示来源。知识图谱则解决另一类问题:症状和疾病之间、疾病和药物之间、疾病和科室之间存在结构化关系,这类关系用文本检索很难精确表达。例如“头晕可能关联高血压”,“高血压应该在心血管内科就诊”,这种一跳、两跳的图关系适合用 Cypher 查询表达。

RAG 负责非结构化知识,知识图谱负责结构化关系。两者结合,正好构成医疗问诊平台的知识底座。

1.2 核心技术栈的分工

这个项目涉及五个核心组件,分工可以这样理解:

组件角色要解决的问题
RAG检索增强生成机制从医疗文档中检索证据,约束大模型回答
LangChainRAG 链编排框架把加载、切分、向量化、提示词、模型调用串成管线
Neo4j图数据库存储疾病、症状、药物、科室等实体及关系
FastAPI后端 API 框架暴露问诊、知识图谱、历史会话等接口
Vue3前端框架实现对话界面、知识图谱可视化和交互逻辑

如果使用 GitHub 上的现成开源项目,要重点确认它的模型接入方式。很多项目默认接入 OpenAI 兼容接口,实际使用时会配置到本地推理服务或国内大模型 API。模型供应商、Embedding 模型、向量库类型都可能影响最终效果。

1.3 一次问诊请求的完整执行链路

整个系统一次典型请求的执行链路如下:

  1. 用户在 Vue3 页面输入症状描述,点击发送。
  2. 前端将问题通过 HTTP 请求发送到 FastAPI 后端/api/chat接口。
  3. 后端调用 RAG 检索模块,从向量库中检索相关文档片段。
  4. 后端同时调用 Neo4j 查询模块,用 Cypher 语句检索疾病、症状、药物等图谱关系。
  5. 后端把文档片段和图谱结果组装成上下文,放入 LangChain 构造的提示词中。
  6. 大模型根据上下文生成回答。
  7. 后端将回答、来源引用、图谱证据封装为统一格式,返回前端。
  8. 前端渲染回答,并在知识图谱面板中展示与问题相关的实体关系。

这条链路的每一步都有独立的验证点,这也是毕业设计答辩时最能体现工程能力的地方。

2. 环境准备与依赖安装

2.1 Python 虚拟环境与后端依赖

建议使用 Python 3.10 到 3.12 之间的版本。过旧的 Python 对 LangChain 新版本支持不好,过新的版本可能遇到依赖编译问题。

创建虚拟环境并安装依赖:

mkdir ai-medical-question cd ai-medical-question mkdir backend frontend data cd backend python -m venv venv source venv/bin/activate # Windows 下使用 venv\Scripts\activate

后端核心依赖可以放在requirements.txt中:

fastapi==0.115.* uvicorn[standard]==0.30.* langchain==0.3.* langchain-community==0.3.* langchain-huggingface==0.1.* neo4j==5.* faiss-cpu==1.8.* sentence-transformers==3.* pydantic==2.* pydantic-settings==2.* python-dotenv==1.*

安装命令:

pip install -r requirements.txt

这里有一个容易被忽略的细节:langchain、langchain-community、langchain-huggingface这三个包版本必须互相兼容。直接把langchain升到最新版,而langchain-community还停留在旧版,导入时经常报ImportError。推荐的做法是先确定主版本,再用同样的主版本号安装其他包。

2.2 Neo4j 安装与 DBeaver 连接配置

Neo4j 是这个项目的数据核心,建议直接使用 Docker 安装 Community 版,方便清理和重来:

docker run -d --name neo4j-medical \ -p 7474:7474 -p 7687:7687 \ -e NEO4J_AUTH=neo4j/你的密码 \ -v neo4j_med_data:/data \ neo4j:5.26-community

启动后访问http://localhost:7474,使用neo4j和刚才设置的密码登录。7474 是浏览器管理界面端口,7687 是 Bolt 驱动连接端口,后端代码连接时要使用 7687。

如果需要用 DBeaver 查看图数据,可以新建 Neo4j 连接,主机填localhost,端口填7687,数据库默认neo4j。DBeaver 中执行 Cypher 脚本时,经常遇到Content is not allowed in prolog这类 XML 解析错误,这通常不是 Neo4j 本身的问题,而是 DBeaver 在导入.cypher文件时按 XML 格式校验了文件头。解决办法是不要在 DBeaver 里以“导入文件”方式执行 Cypher,而是打开 SQL 编辑器,把 Cypher 语句直接粘贴进去执行;或者使用 Neo4j 自带的 Browser 执行。

2.3 Vue3 前端脚手架与 UI 依赖

前端使用 Vite 创建 Vue3 项目:

cd frontend npm create vite@latest . -- --template vue npm install

建议补充安装 Element Plus 和 ECharts。Element Plus 用于对话框、表单、按钮等组件,ECharts 用于知识图谱可视化。

npm install element-plus echarts axios

在main.js中注册 Element Plus:

import { createApp } from 'vue' import ElementPlus from 'element-plus' import 'element-plus/dist/index.css' import App from './App.vue' const app = createApp(App) app.use(ElementPlus) app.mount('#app')

对于课程设计来说,前端不需要过度复杂。核心页面至少包含三个部分:问诊对话区、来源引用区、知识图谱可视化区。

3. 医疗知识图谱建模与数据导入

3.1 实体、关系和属性的最小模型

医疗知识图谱的数据模型不用一开始就做得很庞大,建议从最小闭环开始。实体类型先定义四类:

  • 疾病 Disease
  • 症状 Symptom
  • 药物 Medicine
  • 科室 Department

关系类型定义四类:

  • (Disease)-[:HAS_SYMPTOM]->(Symptom):疾病表现出哪些症状
  • (Disease)-[:TREAT]->(Medicine):疾病用哪些药物治疗
  • (Disease)-[:REFER_TO]->(Department):疾病应挂哪个科室
  • (Medicine)-[:TARGETS]->(Disease):药物治疗哪些疾病

每种实体至少包含一个name属性,有条件的再补充description、code等字段。属性越多,后期展示越丰富,但数据整理成本也越高。

3.2 Cypher 导入数据与检索语句

用 Cypher 创建一组示例数据:

CREATE (d1:Disease {code: "D001", name: "高血压", description: "以体循环动脉血压升高为主要表现的慢性疾病"}), (d2:Disease {code: "D002", name: "心律失常", description: "心脏冲动的频率、节律或传导异常"}), (s1:Symptom {name: "头晕"}), (s2:Symptom {name: "心悸"}), (s3:Symptom {name: "胸闷"}), (m1:Medicine {name: "氨氯地平", usage: "口服,每日一次"}), (dep1:Department {name: "心血管内科"}); CREATE (d1)-[:HAS_SYMPTOM]->(s1), (d1)-[:HAS_SYMPTOM]->(s2), (d2)-[:HAS_SYMPTOM]->(s2), (d2)-[:HAS_SYMPTOM]->(s3), (d1)-[:TREAT]->(m1), (d1)-[:REFER_TO]->(dep1), (d2)-[:REFER_TO]->(dep1);

查询“哪些疾病有头晕症状”:

MATCH (d:Disease)-[:HAS_SYMPTOM]->(s:Symptom) WHERE s.name CONTAINS $keyword RETURN d.name AS disease, collect(s.name) AS symptoms LIMIT 20;

这里要注意CONTAINS对中文按精确字符匹配,不会做模糊语义匹配。用户输入“头有点晕”时,匹配不到“头晕”。可以通过两段处理解决:第一段在 Python 层做关键词归一化,比如把“头有点晕”归一到“头晕”;第二段使用全文索引或向量检索。

Neo4j 5.x 支持全文索引,可以执行:

CREATE FULLTEXT INDEX symptomNameIndex FOR (s:Symptom) ON EACH [s.name];

查询时使用db.index.fulltext.queryNodes:

CALL db.index.fulltext.queryNodes("symptomNameIndex", "头晕") YIELD node RETURN node.name;

3.3 Python 连接 Neo4j 并封装查询

后端使用neo4j官方驱动。连接信息统一放在.env文件中:

NEO4J_URI=bolt://localhost:7687 NEO4J_USER=neo4j NEO4J_PASSWORD=你的密码

封装一个GraphService:

from neo4j import GraphDatabase from typing import List, Dict class GraphService: def __init__(self, uri: str, user: str, password: str): self.driver = GraphDatabase.driver(uri, auth=(user, password)) def close(self): self.driver.close() def query_diseases_by_symptom(self, keyword: str) -> List[Dict]: cypher = """ MATCH (d:Disease)-[:HAS_SYMPTOM]->(s:Symptom) WHERE s.name CONTAINS $keyword RETURN d.name AS disease, collect(s.name) AS symptoms LIMIT 10 """ with self.driver.session() as session: result = session.run(cypher, keyword=keyword) return [{"disease": row["disease"], "symptoms": row["symptoms"]} for row in result]

这个服务类在 FastAPI 启动时初始化一次,不要在每个请求里创建新的 Driver 连接。GraphDatabase.driver内部自带连接池,重复创建会造成资源浪费。

4. FastAPI 服务与 RAG 链路实现

4.1 后端项目目录结构

后端代码建议按功能分层:

backend/ ├── app/ │ ├── main.py # FastAPI 入口 │ ├── config.py # 读取环境变量 │ ├── api/ │ │ └── routes.py # 路由定义 │ ├── core/ │ │ ├── llm.py # 大模型初始化 │ │ ├── rag.py # RAG 检索链 │ │ └── graph.py # Neo4j 查询封装 │ └── schemas/ │ └── chat.py # Pydantic 请求响应模型 ├── data/ │ ├── documents/ # 医疗文档 │ └── vector_store/ # 向量库持久化目录 ├── requirements.txt └── .env

config.py使用 pydantic-settings 读取配置:

from pydantic_settings import BaseSettings class Settings(BaseSettings): app_name: str = "AI智能医疗问诊平台" neo4j_uri: str = "bolt://localhost:7687" neo4j_user: str = "neo4j" neo4j_password: str = "12345678" llm_base_url: str = "http://localhost:8001/v1" llm_api_key: str = "EMPTY" llm_model: str = "qwen2-7b" embedding_model: str = "BAAI/bge-m3" vector_store_path: str = "data/vector_store" class Config: env_file = ".env" settings = Settings()

这里的llm_base_url指向兼容 OpenAI 协议的推理服务。实际项目中可能换成本地部署的 llama.cpp 服务,也可能换成云端大模型 API,取决于你使用的模型服务。

4.2 文档加载、切分与向量化

医疗知识库可以包含 txt、md、pdf 文件。先把文档切分为适合检索的片段,再做向量化。

from langchain_community.document_loaders import DirectoryLoader, TextLoader from langchain.text_splitter import RecursiveCharacterTextSplitter from langchain_huggingface import HuggingFaceEmbeddings from langchain_community.vectorstores import FAISS def build_vector_store(doc_dir: str, save_path: str): loader = DirectoryLoader(doc_dir, glob="**/*.txt", loader_cls=TextLoader) docs = loader.load() splitter = RecursiveCharacterTextSplitter( chunk_size=500, chunk_overlap=50, separators=["\n\n", "\n", "。", "!", "?", " ", ""] ) chunks = splitter.split_documents(docs) embeddings = HuggingFaceEmbeddings( model_name=settings.embedding_model ) vector_store = FAISS.from_documents(chunks, embeddings) vector_store.save_local(save_path) return vector_store

chunk_size和chunk_overlap是 RAG 效果的关键参数。chunk_size=500表示每段约 500 个字符,chunk_overlap=50表示相邻片段重叠 50 个字符,避免信息被截断。医疗文档经常出现“如果出现……,应立即就诊”这类关联内容,分段太小容易丢失上下文。

HuggingFaceEmbeddings会从 Hugging Face Hub 下载模型,首次运行需要网络。如果网络受限,可以改用本地 Embedding 模型或 API 方式。这里还要注意,bge-m3这类模型对内存有一定要求,课程设计机器建议先使用小模型如paraphrase-multilingual-MiniLM-L12-v2验证流程。

4.3 知识图谱增强的检索生成链

RAG 链路核心思路是“双路检索”:一路查向量库,一路查知识图谱,最后合并结果。

from langchain.prompts import ChatPromptTemplate from langchain_core.output_parsers import StrOutputParser from langchain_core.runnables import RunnablePassthrough from langchain_openai import ChatOpenAI def retrieve_context(question: str): vector_docs = vector_store.similarity_search(question, k=3) graph_data = graph_service.query_diseases_by_symptom(question) return { "question": question, "docs": vector_docs, "graph_data": graph_data } prompt = ChatPromptTemplate.from_messages([ ("system", """你是医疗问诊平台的智能助手。请依据检索到的医学文档和知识图谱信息回答用户问题。回答需要给出判断依据;如果信息不足,请明确说明无法判断。本系统仅用于健康科普和预问诊辅助,不构成医学诊断。"""), ("human", """用户问题:{question} 检索到的文档片段: {docs} 知识图谱结构化证据: {graph_data} 请组织答案,并列出参考来源。""") ]) llm = ChatOpenAI( base_url=settings.llm_base_url, api_key=settings.llm_api_key, model=settings.llm_model, temperature=0.2 ) def build_rag_chain(): chain = ( RunnablePassthrough.assign(context=retrieve_context) | prompt | llm | StrOutputParser() ) return chain

temperature=0.2是医疗问答场景的常用设置。温度越低,模型生成内容越保守,越倾向于依照检索上下文作答。如果设置到 0.7 以上,回答会更发散,这在医疗场景中不是好事。

检索时k=3表示取最相似的 3 个片段作为证据。片段太少会导致证据不足,片段太多会超过模型上下文窗口,也会引入无关内容。实际调优时可以在 3 到 8 之间测试。

4.4 接口定义与统一响应格式

毕业设计里,接口返回格式统一是很容易加分的点。定义如下格式:

{ "code": 0, "message": "success", "data": { "answer": "回答内容", "sources": [ {"type": "document", "title": "高血压科普.txt", "score": 0.82}, {"type": "graph", "disease": "高血压", "relation": "HAS_SYMPTOM"} ] } }

Pydantic 模型:

from pydantic import BaseModel, Field from typing import Optional, List class ChatRequest(BaseModel): question: str = Field(..., min_length=1, max_length=500) session_id: Optional[str] = None class SourceItem(BaseModel): type: str title: Optional[str] = None disease: Optional[str] = None relation: Optional[str] = None class ChatData(BaseModel): answer: str sources: List[SourceItem] class ApiResponse(BaseModel): code: int = 0 message: str = "success" data: Optional[ChatData] = None

FastAPI 路由:

from fastapi import FastAPI from fastapi.middleware.cors import CORSMiddleware from app.core.rag import build_rag_chain, vector_store from app.core.graph import GraphService from app.schemas.chat import ChatRequest, ApiResponse, ChatData, SourceItem from app.config import settings app = FastAPI(title=settings.app_name) app.add_middleware( CORSMiddleware, allow_origins=["http://localhost:5173"], allow_credentials=True, allow_methods=["*"], allow_headers=["*"], ) graph_service = GraphService( settings.neo4j_uri, settings.neo4j_user, settings.neo4j_password ) rag_chain = build_rag_chain() @app.post("/api/chat", response_model=ApiResponse) async def chat(req: ChatRequest): answer = rag_chain.invoke(req.question) sources = [ SourceItem(type="document", title="医疗知识库"), SourceItem(type="graph", disease="高血压", relation="HAS_SYMPTOM") ] return ApiResponse(data=ChatData(answer=answer, sources=sources))

跨域配置要特别注意allow_origins的值。前端 Vite 开发服务器默认端口是 5173,如果你把端口改成 5174,这里也要同步修改。生产环境还应考虑允许的来源域名范围,不能直接写*。

5. Vue3 前端问诊界面与联调

5.1 页面结构、路由和状态管理

前端页面推荐拆成三个视图:

  • ChatView.vue:主问诊界面。
  • GraphView.vue:知识图谱浏览界面。
  • HistoryView.vue:历史问诊记录。

在router/index.js中定义路由:

import { createRouter, createWebHistory } from 'vue-router' import ChatView from '../views/ChatView.vue' import GraphView from '../views/GraphView.vue' const router = createRouter({ history: createWebHistory(), routes: [ { path: '/', component: ChatView }, { path: '/graph', component: GraphView } ] }) export default router

课程设计阶段会话状态可以放在前端的Piniastore 中,也可以先用一个ref数组维护。不要过早引入复杂的状态管理,先把主流程跑通。

5.2 问诊对话组件

对话组件是整个前端的核心。用户发送问题后,先追加一条用户消息,再调用后端接口,拿到结果后追加一条助手消息。

<template> <div class="chat-container"> <div class="chat-messages"> <div v-for="msg in messages" :key="msg.id" :class="['message', msg.role]"> <div class="bubble">{{ msg.content }}</div> <div v-if="msg.sources && msg.sources.length" class="sources"> 参考来源: <span v-for="(s, i) in msg.sources" :key="i"> {{ s.type === 'document' ? s.title : s.disease }} </span> </div> </div> </div> <div class="chat-input"> <el-input v-model="question" placeholder="请输入你的症状描述,例如:最近头晕心悸" @keyup.enter="sendMessage" /> <el-button type="primary" :loading="loading" @click="sendMessage"> 发送 </el-button> </div> </div> </template> <script setup> import { ref } from 'vue' import { chatApi } from '../api/chat' const messages = ref([]) const question = ref('') const loading = ref(false) async function sendMessage() { const text = question.value.trim() if (!text || loading.value) return messages.value.push({ id: Date.now(), role: 'user', content: text }) question.value = '' loading.value = true try { const res = await chatApi(text) messages.value.push({ id: Date.now(), role: 'assistant', content: res.data.answer, sources: res.data.sources }) } catch (e) { messages.value.push({ id: Date.now(), role: 'assistant', content: '服务暂时不可用,请稍后重试。' }) } finally { loading.value = false } } </script>

这里有个需要注意的地方:chatApi返回的字段要和后端接口对应。如果后端把结果包在data.data.answer,前端拿res.data.answer就会是undefined。联调前要先确认后端返回的 JSON 层级。

5.3 知识图谱可视化

知识图谱可视化推荐用 ECharts 的 graph 系列。使用/api/graph/search接口返回节点和关系:

import * as echarts from 'echarts' function renderGraph(data) { const chart = echarts.init(document.getElementById('graphChart')) const option = { tooltip: {}, series: [{ type: 'graph', layout: 'force', data: data.nodes.map(node => ({ name: node.name, category: node.category })), links: data.links.map(link => ({ source: link.source, target: link.target })), categories: [ { name: '疾病' }, { name: '症状' }, { name: '药物' }, { name: '科室' } ], force: { repulsion: 200 } }] } chart.setOption(option) }

图可视化这种功能,在课程设计答辩中非常抢眼。但它依赖后端返回良好的结构化数据,所以后端要先把 Cypher 查询结果转换成nodes和links两个数组,前端不能直接渲染 Cypher 原始结果。

6. 启动顺序、验证清单与答辩演示

6.1 启动顺序与验证清单

项目启动顺序很重要,顺序错了会出现“前端打不开”“后端连不上 Neo4j”等连锁问题。

推荐启动顺序:

# 1. 启动 Neo4j docker start neo4j-medical # 2. 启动模型推理服务或确保远端 API 可用 # 3. 启动后端 cd backend source venv/bin/activate uvicorn app.main:app --reload --host 0.0.0.0 --port 8000 # 4. 启动前端 cd frontend npm run dev

启动后按以下清单逐项验证:

验证项检查方式预期结果
Neo4j 管理界面浏览器访问 7474能登录并在 Browser 中执行 Cypher
后端健康检查访问/api/health或 Swagger 文档/docs返回正常 JSON,Swagger 能列出接口
问诊接口POST/api/chat发送测试问题返回code=0且包含回答和来源
知识图谱接口GET/api/graph/search?keyword=头晕返回节点和关系数组
前端页面访问 5173 端口页面正常渲染,能发送问题并显示回答
来源引用查看回答下方的来源区域展示文档标题或图谱疾病名

6.2 后端接口测试的预期结果

使用 curl 测试问诊接口:

curl -X POST http://localhost:8000/api/chat \ -H "Content-Type: application/json" \ -d '{"question": "我最近头晕心悸,可能是什么问题?"}'

正常响应示例:

{ "code": 0, "message": "success", "data": { "answer": "根据你描述的“头晕、心悸”症状,可能涉及高血压、心律失常等疾病。建议先到心血管内科就诊,同时注意血压监测。以上内容来自健康科普知识库,不能替代专业诊断。", "sources": [ {"type": "document", "title": "高血压健康科普.txt", "score": 0.82}, {"type": "graph", "disease": "高血压", "relation": "HAS_SYMPTOM"} ] } }

如果响应不是这个结构,先检查后端服务日志,再检查retrieve_context函数是否正常返回了图谱数据。

6.3 毕业设计演示脚本

演示环节建议准备一条 5 分钟的主线脚本:

  1. 展示系统架构图,说明哪些模块是本次独立完成。
  2. 进入前端页面,输入“头晕心悸”,展示回答。
  3. 展开来源引用,说明回答不是凭空生成,而是来自知识库和图谱证据。
  4. 切换到知识图谱页面,搜索“头晕”,展示疾病、症状、科室之间的图关系。
  5. 讲解 RAG 链路中 LangChain 如何编排检索和生成。
  6. 讲解遇到的一个真实问题以及如何排查,这比一帆风顺更有说服力。

演示时最怕出现网络波动或模型服务未启动,所以要准备一套“降级方案”:如果模型服务不可用,后端可以临时返回一条预设回答,保证页面流程完整。这个逃生通道在答辩现场非常有用。

7. 常见问题排查与解决方案

7.1 Neo4j 连接与数据导入问题

问题现象常见原因检查方式处理建议
后端连接 Neo4j 超时Neo4j 容器未启动或端口映射错误docker ps查看容器状态确认容器运行,检查 7687 端口映射
DBeaver 执行 Cypher 报 PROLOG 错误以文件导入方式执行非 XML 内容查看错误弹窗中的文件路径改用编辑器粘贴执行
浏览器管理界面打不开Neo4j 服务未启动访问 7474 端口重建容器,确认日志无异常
中文乱码数据文件不是 UTF-8 编码查看文件编码转存为 UTF-8 后重新导入

7.2 依赖版本不匹配问题

LangChain 0.3 系列中,很多组件被拆分到langchain-community。只安装langchain却不安装langchain-community,导入TextLoader时会报错。解决办法是统一版本号:

pip install "langchain==0.3.*" "langchain-community==0.3.*" "langchain-huggingface==0.1.*"

另外,ChatOpenAI的base_url参数在不同版本中可能叫openai_api_base。如果你的代码报got an unexpected keyword argument,检查当前 LangChain 版本的类签名。这种情况不要死记参数名,直接看安装包里的源码或 IDE 提示。

7.3 RAG 检索效果不理想

检索效果不好的表现通常有三种:

  • 回答完全无关:retrieve_context可能没有把文档内容拼进提示词,或提示词位置不对。先打印最终发送给模型的完整提示词,确认上下文是否真正进入。
  • 检索到无关片段:chunk_size过大导致片段语义混杂,或文档本身包含过多噪声。尝试缩小到 300-500,增加重叠。
  • 图谱证据没有参与回答:先单独调用GraphService.query_diseases_by_symptom,确认返回结果非空;如果为空,检查关键字归一化逻辑。

调优顺序建议是:先看检索结果,再调参数,最后改提示词。不要一上来就换大模型,否则问题定位会非常困难。

7.4 前端跨域、构建和打包问题

开发阶段最常见的三个问题:

  1. 后端未配置 CORS。浏览器控制台报Access-Control-Allow-Origin错误。解决方法是 FastAPI 中加CORSMiddleware,允许前端来源。
  2. Vite 端口变化后未同步后端配置。改了vite.config.js端口,但后端allow_origins还是旧地址。
  3. 打包后页面空白。检查vite.config.js中base是否设置为'./',否则部署到子目录时资源路径错误。
// vite.config.js import { defineConfig } from 'vite' import vue from '@vitejs/plugin-vue' export default defineConfig({ plugins: [vue()], base: './', server: { port: 5173, proxy: { '/api': { target: 'http://localhost:8000', changeOrigin: true } } } })

配置代理后,前端请求路径写/api/chat即可,不需要写完整的http://localhost:8000/api/chat。这样打包部署时也不需要修改前端代码中的接口地址。

8. 从课程设计到生产环境的扩展建议

8.1 可复用清单:系统上线前逐项检查

这套结构不止能用在医疗问诊,换成法律问答、产品客服、论文解读都能复用。上线前重点检查以下清单:

  • 环境变量是否外置,是否包含明文密钥。
  • Neo4j 连接是否使用连接池,是否在应用关闭时释放。
  • 向量库文件是否随项目正确配置,启动时是重新构建还是加载已有文件。
  • 大模型接口是否设置超时、重试和异常兜底。
  • 检索结果是否限制数量,避免一次请求把大量 token 塞进上下文。
  • 回答是否带来源引用,是否有“无法判断”的兜底逻辑。
  • 前端是否处理接口异常,页面是否有加载状态。
  • 日志是否覆盖模型调用、检索耗时、图谱查询耗时等关键环节。

8.2 生产环境要补齐的能力

课程设计能跑通主流程就够了,但如果项目要放到生产环境,需要补齐以下能力:

  • 认证与权限:当前接口是匿名访问,生产环境要加入用户登录、会话管理和基于角色的权限控制。
  • 数据安全:医疗数据属于敏感数据,历史问诊记录要加密存储,接口要限流和脱敏。
  • 日志与监控:记录每次问诊的检索内容、模型响应、耗时,用于效果分析和问题定位。
  • 测试:为 RAG 链补充单元测试,至少覆盖文档加载、图谱查询、接口返回格式三个部分。
  • 部署:使用 Docker Compose 统一编排 Neo4j、后端、前端,减少环境差异。
  • 模型管理:把模型服务独立部署,后端通过 API 调用,避免 LangChain 直接依赖本地模型进程。

8.3 后续扩展方向

这个项目有三条清晰的扩展路线:

  1. 从单轮问答升级为多轮对话:引入 LangChain 的对话记忆模块,让系统能根据前文追问症状细节。
  2. 从知识图谱浏览升级为智能诊断路径推荐:在图谱中增加距离计算和路径搜索,回答“这个疾病应该去哪个科室”时,给出一条可解释的路径。
  3. 从文本问答升级为多模态输入:允许用户上传检查报告图片,先用 OCR 抽取文本,再进入 RAG 链路。

对课程设计而言,第一条路线投入产出比最高。多轮对话能让应用体验更真实,也能在答辩时展示你对 LangChain 记忆机制的理解。建议在基础链路跑通后,优先补充对话历史和追问引导功能。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询