大家好我是Java1234_小锋老师分享一套锋哥原创的AI智能医疗问诊平台系统((RAGLangChainNeo4j知识图谱FastAPIVue3)。项目介绍随着人工智能技术在医疗健康领域的深入应用传统线下问诊模式在可达性、即时性与健康科普覆盖面等方面逐渐暴露出不足。针对基层群众“就医难、咨询慢、信息不对称”等问题本文设计并实现了一套“AI智能医疗问诊平台系统”。系统采用前后端分离架构后端基于Python语言与FastAPI框架构建高性能异步接口服务前端采用Vue3与Element Plus实现患者端、医生端与管理端三套业务界面业务数据存储于MySQL数据库库名db_ai_medical表名统一以t_开头非结构化医疗知识通过LangChain完成文档解析、文本分块与检索增强生成RAG编排向量数据持久化于Chroma向量数据库结构化疾病—症状—药品—科室关系存入Neo4j知识图谱大语言模型选用阿里云通义千问qwen完成流式对话生成。系统实现了用户注册登录含确认密码、AI智能问诊SSE流式输出、症状自查、在线咨询、预约挂号、健康档案、健康科普以及后台数据统计、知识库管理、知识图谱可视化等功能。测试结果表明系统功能完整、交互流畅能够为患者提供可追溯的知识引用型健康咨询并为医生与管理员提供高效的业务协同与运营支撑。本课题将大模型、向量检索与知识图谱进行融合具有较好的工程落地价值与本科毕业设计示范意义。源码下载链接: https://pan.baidu.com/s/1Zkp5yXoew0_RFXEeosB3gg?pwd1234提取码: 1234系统展示核心代码批量向量化示例知识库文档 import sys from pathlib import Path sys.path.insert(0, str(Path(__file__).parent.parent)) from db.session import SessionLocal from models.knowledge import KnowledgeFile from services.rag_service import get_rag_service from core.config import settings def init_knowledge(): 将 docs_seed 目录下的文档向量化入库 seed_dir Path(__file__).parent.parent / docs_seed if not seed_dir.exists(): print(docs_seed 目录不存在) return db SessionLocal() rag get_rag_service() for file_path in seed_dir.glob(*): if file_path.suffix.lower() not in (.txt, .md, .pdf, .doc, .docx): continue existing db.query(KnowledgeFile).filter(KnowledgeFile.file_name file_path.name).first() if existing: print(f跳过已存在: {file_path.name}) continue record KnowledgeFile( file_namefile_path.name, file_typemarkdown if file_path.suffix.lower() in (.md, .markdown) else file_path.suffix[1:], file_sizefile_path.stat().st_size, file_pathstr(file_path).replace(\\, /), upload_by1, upload_roleadmin, ) db.add(record) db.commit() db.refresh(record) try: count rag.process_file(db, record) print(f向量化完成: {file_path.name} - {count} 块) except Exception as e: print(f向量化失败: {file_path.name} - {e}) db.close() print(知识库初始化完成) if __name__ __main__: init_knowledge()script setup import { ref, onMounted, onUnmounted } from vue import { ElMessage, ElMessageBox } from element-plus import { Upload } from element-plus/icons-vue import request from /utils/request import { formatDateTime, parseListData } from /utils/format const list ref([]) const loading ref(false) const uploading ref(false) /** 搜索关键词 */ const keyword ref() /** 分页参数 */ const page ref(1) const pageSize ref(10) const total ref(0) /** 状态轮询定时器 */ let pollTimer null /** 判断是否存在待处理或处理中的文件 */ function hasPendingFiles(items) { return items.some((item) item.vector_status 0 || item.vector_status 1) } /** 获取列表请求参数 */ function getListParams() { return { page: page.value, page_size: pageSize.value, keyword: keyword.value.trim(), } } /** 启动状态轮询向量化完成后自动停止 */ function startPolling() { if (pollTimer) return pollTimer setInterval(() { refreshList() }, 3000) } /** 停止状态轮询 */ function stopPolling() { if (pollTimer) { clearInterval(pollTimer) pollTimer null } } /** 加载知识库列表 */ async function loadList(silent false) { if (!silent) loading.value true try { const res await request.get(/knowledge/list, { params: getListParams() }) list.value parseListData(res) total.value res.data?.total ?? list.value.length if (hasPendingFiles(list.value)) { startPolling() } else { stopPolling() } } catch { list.value [] total.value 0 stopPolling() } finally { if (!silent) loading.value false } } /** 静默刷新列表轮询时使用避免 loading 闪烁 */ async function refreshList() { try { const res await request.get(/knowledge/list, { params: getListParams() }) list.value parseListData(res) if (!hasPendingFiles(list.value)) { stopPolling() } } catch { /* 轮询失败时忽略下次继续 */ } } /** 搜索知识库文件 */ function handleSearch() { page.value 1 loadList() } /** 重置搜索条件 */ function handleReset() { keyword.value page.value 1 loadList() } /** 分页切换 */ function handlePageChange(p) { page.value p loadList() } /** 上传知识文件 */ async function handleUpload({ file }) { uploading.value true const formData new FormData() formData.append(file, file) try { await request.post(/knowledge/upload, formData, { headers: { Content-Type: multipart/form-data }, }) ElMessage.success(上传成功正在向量化处理) page.value 1 await loadList() } catch { /* */ } finally { uploading.value false } } /** 删除知识库文件 */ async function handleDelete(row) { try { await ElMessageBox.confirm( 确定删除知识库文件「${row.file_name}」吗删除后不可恢复。, 提示, { type: warning }, ) await request.delete(/knowledge/${row.id}) ElMessage.success(删除成功) if (list.value.length 1 page.value 1) { page.value - 1 } await loadList() } catch { /* */ } } onMounted(loadList) onUnmounted(stopPolling) /script template div div classpage-header h2 classpage-title知识库管理/h2 el-upload :show-file-listfalse :http-requesthandleUpload accept.txt,.pdf,.doc,.docx,.md el-button typeprimary classgradient-btn :loadinguploading :iconUpload 上传文件 /el-button /el-upload /div div classmodern-card !-- 搜索栏 -- div classsearch-bar el-input v-modelkeyword placeholder搜索文件名 / 文件类型 clearable stylewidth: 280px keyup.enterhandleSearch / el-button typeprimary clickhandleSearch搜索/el-button el-button clickhandleReset重置/el-button /div el-table :datalist v-loadingloading classadaptive-table stripe el-table-column propid labelID min-width60 / el-table-column propfile_name label文件名 min-width200 show-overflow-tooltip / el-table-column propfile_type label类型 min-width100 template #default{ row } el-tag sizesmall{{ row.file_type || row.type || - }}/el-tag /template /el-table-column el-table-column propfile_size label大小 min-width100 template #default{ row } {{ row.file_size ? (row.file_size / 1024).toFixed(1) KB : (row.size ? (row.size / 1024).toFixed(1) KB : -) }} /template /el-table-column el-table-column propvector_status label状态 min-width100 template #default{ row } el-tag :typerow.vector_status 2 ? success : row.vector_status 3 ? danger : info sizesmall {{ row.vector_status 2 ? 已向量化 : row.vector_status 3 ? 失败 : row.vector_status 1 ? 处理中 : 已上传 }} /el-tag /template /el-table-column el-table-column propcreate_time label上传时间 min-width170 template #default{ row }{{ formatDateTime(row.create_time || row.created_at) }}/template /el-table-column el-table-column label操作 min-width100 fixedright template #default{ row } el-button link typedanger clickhandleDelete(row)删除/el-button /template /el-table-column /el-table el-empty v-if!loading !list.length description暂无知识库文件 / !-- 分页 -- div v-iftotal 0 classpagination-wrap el-pagination v-model:current-pagepage :page-sizepageSize :totaltotal layouttotal, prev, pager, next background current-changehandlePageChange / /div /div /div /template style scoped .page-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px; } .search-bar { display: flex; align-items: center; gap: 12px; margin-bottom: 16px; } .pagination-wrap { display: flex; justify-content: flex-end; margin-top: 16px; } /style