大家好,我是专注于分享AI与数据技术实战经验的博主。在医疗健康领域,如何让AI系统不仅能回答简单问题,还能像专家一样进行逻辑推理和诊断辅助,是当前技术落地的核心挑战。传统的检索增强生成(RAG)在处理复杂的、关联性强的医学知识时,常常力不从心。本文将手把手带你实现一个基于知识图谱(Neo4j)和大语言模型(LLM)的图检索增强(GraphRAG)医疗健康知识诊断智能问答系统。这个项目非常适合作为计算机专业的毕业设计,它融合了图数据库、LLM应用和智能问答三大热门方向,既有理论深度,又有完整的工程实践。
通过本文,你将掌握从零搭建一个GraphRAG系统的全流程,包括Neo4j知识图谱的构建、LLM的集成调用、GraphRAG的核心检索逻辑,以及一个完整的Web问答界面。无论你是想深入学习图数据库与LLM的结合应用,还是急需一个高质量、有亮点的毕业设计项目,这篇文章都能为你提供清晰的路径和可运行的代码。
1. 项目背景与核心概念解析
在深入代码之前,我们有必要厘清几个核心概念,理解它们如何协同工作来解决医疗问答的难题。
1.1 为什么需要GraphRAG?
传统的基于向量数据库的RAG(Retrieval-Augmented Generation)系统,其核心是“语义相似度检索”。它将文档切成块,转换成向量,然后根据用户问题检索最相似的文本块喂给LLM生成答案。这种方法在处理事实性、描述性知识时效果很好。
然而,医疗诊断是一个高度依赖逻辑推理和关系网络的过程。例如,用户提问:“我最近咳嗽、发烧,并且感觉全身乏力,可能是什么问题?” 简单的语义检索可能会返回关于“咳嗽”、“发烧”、“乏力”的独立描述段落。但一个医生会立刻在脑海中构建一个关系图:咳嗽 -> 可能是 -> 呼吸道感染 -> 常见症状包括 -> 发烧、乏力。这种实体间的关联关系,是向量检索难以直接捕捉的。
GraphRAG正是为了解决这一问题而生。它利用知识图谱来存储实体(疾病、症状、药品、检查项)和它们之间丰富的关系(导致、表现为、治疗、禁忌)。当用户提问时,系统不是检索相似的文本片段,而是:
- 从问题中抽取实体(如“咳嗽”、“发烧”)。
- 在图数据库中进行图遍历查询,找到与这些实体相关联的其他实体和路径(例如,找到同时连接“咳嗽”和“发烧”的疾病节点)。
- 将查询到的子图结构(包含实体和关系)作为上下文提供给LLM。 这样,LLM获得的不是零散的文本,而是一个结构化的“知识片段”,极大提升了推理的准确性和可解释性。
1.2 技术栈组成与角色
我们的系统主要由以下部分组成:
- Neo4j: 作为图数据库,存储结构化的医疗知识图谱。它是我们系统的“知识大脑”。
- 大语言模型 (LLM): 作为系统的“推理引擎”。我们使用其完成:1) 从自然语言问题中抽取实体;2) 根据图谱检索到的结构化信息,生成流畅、专业的诊断建议和回答。本文将使用 OpenAI 的 GPT 系列模型作为示例,你也可以替换为国内可访问的模型(如文心一言、通义千问的API)。
- GraphRAG 检索层: 这是系统的“连接器”。它接收用户问题,调用LLM进行实体抽取,构造Cypher查询语句(Neo4j的查询语言)在图谱中检索相关子图,并将子图信息格式化后送入LLM生成最终答案。
- 后端框架 (FastAPI): 提供RESTful API,处理前端请求,协调GraphRAG检索层与LLM的交互。
- 前端界面 (Streamlit): 提供一个简单直观的Web界面,供用户输入问题并查看回答和背后的知识图谱证据。Streamlit非常适合快速构建数据科学应用。
2. 环境准备与项目搭建
工欲善其事,必先利其器。我们先来准备好开发环境并创建项目结构。
2.1 软件与环境要求
- 操作系统: Windows 10/11, macOS, 或 Linux (Ubuntu 20.04+)。本文演示以 macOS/Linux 命令行环境为主,Windows 用户建议使用 WSL2 或 Git Bash。
- Python: 版本 3.8 - 3.11。推荐使用 3.9 或 3.10,兼容性最好。使用
python --version检查。 - Neo4j 数据库: 我们将使用Neo4j AuraDB(免费的云托管版本)或Neo4j Desktop(本地图形化管理工具)。对于毕业设计,AuraDB免费版足够使用,无需在本地安装数据库服务。
- 代码编辑器: VS Code 或 PyCharm。
2.2 创建项目与虚拟环境
首先,创建一个清晰的项目目录结构。
# 1. 创建项目目录 mkdir medical_graphrag_qa cd medical_graphrag_qa # 2. 创建虚拟环境 (以 venv 为例) python -m venv venv # 3. 激活虚拟环境 # macOS/Linux: source venv/bin/activate # Windows: # venv\Scripts\activate # 4. 创建必要的项目文件 touch requirements.txt touch .env.example mkdir src mkdir data激活虚拟环境后,你的命令行提示符前会出现(venv)字样。
2.3 安装依赖包
编辑requirements.txt文件,填入以下内容:
# 核心依赖 neo4j==5.20.0 openai==1.12.0 langchain==0.1.0 langchain-community==0.0.10 langchain-openai==0.0.8 # Web框架与工具 fastapi==0.104.1 uvicorn[standard]==0.24.0 streamlit==1.28.0 # 工具类 python-dotenv==1.0.0 pydantic==2.5.0 pydantic-settings==2.1.0然后安装所有依赖:
pip install -r requirements.txt关键依赖说明:
neo4j: Neo4j 官方的 Python 驱动。openai,langchain: 用于调用和编排 LLM。LangChain 提供了构建链(Chain)的高层抽象,让GraphRAG流程更清晰。fastapi,uvicorn: 用于构建高性能后端 API。streamlit: 快速构建交互式前端。python-dotenv: 管理环境变量(如数据库密码、API密钥)。
2.4 配置 Neo4j 数据库
我们使用Neo4j AuraDB免费云服务,它提供了512MB存储,足够毕业设计使用。
注册与创建实例:
- 访问 Neo4j Aura 官网注册账号。
- 在控制台点击 “Create Database”,选择 “Free” 套餐。
- 设置数据库名称(如
medical-kg),选择区域,点击创建。 - 等待几分钟,实例状态变为 “Running”。
获取连接信息:
- 实例创建成功后,点击 “Open” 下拉菜单,选择 “Connection details”。
- 你会看到
NEO4J_URI(格式如neo4j+s://xxxx.databases.neo4j.io),NEO4J_USERNAME(默认neo4j), 和NEO4J_PASSWORD。务必保存好密码,它只显示一次。
本地环境变量配置:
- 复制
.env.example文件为.env。 - 编辑
.env文件,填入你的 AuraDB 信息和 OpenAI API Key。
- 复制
# .env 文件内容 NEO4J_URI=neo4j+s://your-aura-db-id.databases.neo4j.io NEO4J_USERNAME=neo4j NEO4J_PASSWORD=your-strong-password-here OPENAI_API_KEY=sk-your-openai-api-key-here重要安全提示:.env文件包含敏感信息,务必将其添加到.gitignore文件中,切勿提交到版本控制系统(如Git)。
3. 构建医疗知识图谱
知识图谱是系统的基石。我们需要将非结构化的医疗文本(或结构化数据)转化为图数据库中的节点和关系。
3.1 设计图谱Schema
一个简化的医疗知识图谱可以包含以下类型的节点和关系:
- 节点 (Node):
Disease(疾病): 如感冒,肺炎。Symptom(症状): 如咳嗽,发烧,头痛。Drug(药品): 如阿莫西林,布洛芬。Examination(检查): 如血常规,胸部X光。
- 关系 (Relationship):
HAS_SYMPTOM: 疾病->症状。感冒 -[:HAS_SYMPTOM]-> 咳嗽。COMMON_DRUG: 疾病->药品。肺炎 -[:COMMON_DRUG]-> 阿莫西林。NEEDS_EXAM: 疾病->检查。肺炎 -[:NEEDS_EXAM]-> 胸部X光。ACCOMPANY_WITH: 症状->症状。咳嗽 -[:ACCOMPANY_WITH]-> 咽痛。BELONGS_TO: 疾病->科室。感冒 -[:BELONGS_TO]-> 呼吸内科。
3.2 准备与导入数据
我们可以从公开的医疗数据集中提取,或者为了演示,手动创建一个小型数据集。我们在data/目录下创建一个medical_kg_data.json文件。
// data/medical_kg_data.json { "diseases": [ { "name": "感冒", "desc": "普通感冒是一种常见的急性上呼吸道病毒性感染性疾病。", "department": "呼吸内科", "symptoms": ["咳嗽", "流鼻涕", "打喷嚏", "咽痛", "发烧"], "common_drugs": ["感冒灵颗粒", "对乙酰氨基酚"], "needs_exam": ["血常规"] }, { "name": "肺炎", "desc": "肺炎是指终末气道、肺泡和肺间质的炎症,可由细菌、病毒等病原体引起。", "department": "呼吸内科", "symptoms": ["咳嗽", "咳痰", "胸痛", "发烧", "呼吸困难"], "common_drugs": ["阿莫西林", "左氧氟沙星"], "needs_exam": ["血常规", "胸部X光", "痰培养"] }, { "name": "高血压", "desc": "高血压是一种动脉血压升高的慢性病,是心脑血管病的主要危险因素。", "department": "心血管内科", "symptoms": ["头晕", "头痛", "心悸", "耳鸣"], "common_drugs": ["硝苯地平", "卡托普利"], "needs_exam": ["血压测量", "心电图", "肾功能检查"] } ] }3.3 编写数据导入脚本
接下来,我们编写一个Python脚本,读取上面的JSON数据,并将其导入到Neo4j中。创建文件src/init_kg.py。
# src/init_kg.py import json from neo4j import GraphDatabase from dotenv import load_dotenv import os # 加载环境变量 load_dotenv() class Neo4jConnection: def __init__(self): self.uri = os.getenv("NEO4J_URI") self.user = os.getenv("NEO4J_USERNAME") self.password = os.getenv("NEO4J_PASSWORD") self.driver = None def connect(self): self.driver = GraphDatabase.driver(self.uri, auth=(self.user, self.password)) return self.driver def close(self): if self.driver is not None: self.driver.close() def clear_database(tx): # 清空现有图谱(谨慎操作!仅用于演示和初始化) tx.run("MATCH (n) DETACH DELETE n") print("已清空数据库。") def create_disease_node(tx, disease): # 创建疾病节点 query = """ MERGE (d:Disease {name: $name}) SET d.description = $desc, d.department = $dept RETURN d """ tx.run(query, name=disease['name'], desc=disease['desc'], dept=disease['department']) def create_symptom_relationship(tx, disease_name, symptom_list): # 创建症状节点并建立关系 for symptom in symptom_list: query = """ MERGE (s:Symptom {name: $symptom_name}) WITH s MATCH (d:Disease {name: $disease_name}) MERGE (d)-[:HAS_SYMPTOM]->(s) """ tx.run(query, symptom_name=symptom, disease_name=disease_name) def create_drug_relationship(tx, disease_name, drug_list): # 创建药品节点并建立关系 for drug in drug_list: query = """ MERGE (dr:Drug {name: $drug_name}) WITH dr MATCH (d:Disease {name: $disease_name}) MERGE (d)-[:COMMON_DRUG]->(dr) """ tx.run(query, drug_name=drug, disease_name=disease_name) def create_exam_relationship(tx, disease_name, exam_list): # 创建检查节点并建立关系 for exam in exam_list: query = """ MERGE (e:Examination {name: $exam_name}) WITH e MATCH (d:Disease {name: $disease_name}) MERGE (d)-[:NEEDS_EXAM]->(e) """ tx.run(query, exam_name=exam, disease_name=disease_name) def main(): conn = Neo4jConnection() driver = conn.connect() # 读取数据 with open('data/medical_kg_data.json', 'r', encoding='utf-8') as f: data = json.load(f) with driver.session() as session: # 清空数据库(可选,第一次运行或需要重置时使用) # session.execute_write(clear_database) for disease in data['diseases']: print(f"正在导入疾病: {disease['name']}") # 1. 创建疾病节点 session.execute_write(create_disease_node, disease) # 2. 创建症状及关系 session.execute_write(create_symptom_relationship, disease['name'], disease['symptoms']) # 3. 创建药品及关系 session.execute_write(create_drug_relationship, disease['name'], disease['common_drugs']) # 4. 创建检查及关系 session.execute_write(create_exam_relationship, disease['name'], disease['needs_exam']) conn.close() print("知识图谱数据导入完成!") if __name__ == "__main__": main()运行此脚本,将数据导入Neo4j:
python src/init_kg.py导入成功后,你可以登录 Neo4j AuraDB 控制台,使用内置的Bloom或Browser工具可视化你的图谱。执行一个简单的查询,如MATCH (n) RETURN n LIMIT 25,就能看到初步构建的图谱。
4. 核心组件开发:GraphRAG检索与问答链
这是系统的核心逻辑。我们将使用 LangChain 来编排整个流程。
4.1 构建图检索器 (Graph Retriever)
图检索器的任务是:接收用户问题,从中提取实体,并构造Cypher查询,从Neo4j中获取相关的子图信息。创建文件src/graph_retriever.py。
# src/graph_retriever.py from langchain.chains import GraphCypherQAChain from langchain_community.graphs import Neo4jGraph from langchain_openai import ChatOpenAI from langchain.prompts import PromptTemplate from dotenv import load_dotenv import os load_dotenv() class MedicalGraphRetriever: def __init__(self): # 1. 连接Neo4j图数据库 self.graph = Neo4jGraph( url=os.getenv("NEO4J_URI"), username=os.getenv("NEO4J_USERNAME"), password=os.getenv("NEO4J_PASSWORD") ) # 2. 初始化LLM self.llm = ChatOpenAI( model="gpt-3.5-turbo", temperature=0, # 温度设为0,使输出更确定 openai_api_key=os.getenv("OPENAI_API_KEY") ) # 3. 定义图谱Schema,帮助LLM理解如何生成Cypher self.graph_schema = self.graph.get_schema # 4. 定制Cypher生成提示词 self.cypher_generation_prompt = PromptTemplate.from_template(""" 你是一个专业的Neo4j Cypher查询生成器。 给定一个输入问题,基于以下图谱Schema,将其转换成一个Cypher查询语句。 不要解释,只返回Cypher查询。 图谱Schema: {schema} 问题: {question} """) # 5. 定制答案生成提示词 self.qa_prompt = PromptTemplate.from_template(""" 你是一个专业的医疗健康助手。请基于以下上下文信息,用中文专业、清晰、有条理地回答用户的问题。 如果上下文信息不足以回答问题,请如实告知你不知道,不要编造信息。 在回答中,可以引用上下文中的疾病、症状、药品和检查项。 上下文信息: {context} 问题: {question} 请给出回答: """) def retrieve(self, question: str) -> str: """核心检索方法:根据问题检索图谱并返回文本化上下文""" try: # 步骤1:让LLM根据问题生成Cypher查询 cypher_prompt = self.cypher_generation_prompt.format( schema=self.graph_schema, question=question ) cypher_query = self.llm.invoke(cypher_prompt).content.strip() print(f"生成的Cypher查询: {cypher_query}") # 步骤2:在Neo4j中执行查询,获取结果 graph_result = self.graph.query(cypher_query) print(f"图谱查询结果: {graph_result}") # 步骤3:将图谱结果格式化为文本上下文 # 这里简单地将结果中的节点和关系信息拼接成字符串 context_text = self._format_graph_result(graph_result) return context_text except Exception as e: print(f"图检索过程中发生错误: {e}") return f"检索知识图谱时出错: {str(e)}" def _format_graph_result(self, result): """将Neo4j查询结果格式化为易读的文本""" if not result: return "未在知识图谱中找到相关信息。" formatted_lines = [] for record in result: # record 是一个字典,键是Cypher查询中定义的变量名 for key, value in record.items(): if isinstance(value, list): formatted_lines.append(f"{key}: {', '.join([str(v) for v in value])}") else: formatted_lines.append(f"{key}: {value}") return "\n".join(formatted_lines) def answer_question(self, question: str) -> str: """完整的问答流程:检索 + 生成""" context = self.retrieve(question) if "出错" in context or "未找到" in context: # 如果检索失败,直接让LLM基于常识回答(需谨慎) final_prompt = f"用户问:{question}。我未能在知识库中找到确切信息,请你基于通用医学知识谨慎回答,并说明这并非来自特定知识库。" else: final_prompt = self.qa_prompt.format(context=context, question=question) answer = self.llm.invoke(final_prompt).content return answer, context # 返回答案和用于解释的上下文 # 简单测试 if __name__ == "__main__": retriever = MedicalGraphRetriever() test_question = "感冒有哪些症状?" answer, context = retriever.answer_question(test_question) print("=== 检索到的上下文 ===") print(context) print("\n=== 生成的答案 ===") print(answer)运行测试:
python src/graph_retriever.py你应该能看到LLM生成的Cypher查询(类似MATCH (d:Disease {name: ‘感冒’})-[:HAS_SYMPTOM]->(s:Symptom) RETURN d.name as disease, collect(s.name) as symptoms),查询结果,以及最终生成的答案。
4.2 构建FastAPI后端服务
现在,我们将检索和问答能力封装成REST API。创建文件src/api.py。
# src/api.py from fastapi import FastAPI, HTTPException from pydantic import BaseModel from src.graph_retriever import MedicalGraphRetriever import uvicorn from dotenv import load_dotenv load_dotenv() app = FastAPI( title="医疗健康GraphRAG智能问答系统API", description="基于Neo4j知识图谱和LLM的医疗诊断问答系统", version="1.0.0" ) # 全局检索器实例 retriever = MedicalGraphRetriever() class QuestionRequest(BaseModel): question: str class AnswerResponse(BaseModel): answer: str retrieved_context: str status: str = "success" @app.get("/") def read_root(): return {"message": "医疗健康GraphRAG智能问答系统API已就绪"} @app.post("/ask", response_model=AnswerResponse) async def ask_question(request: QuestionRequest): """ 接收用户问题,返回基于知识图谱的答案。 """ try: if not request.question or request.question.strip() == "": raise HTTPException(status_code=400, detail="问题不能为空") print(f"接收到问题: {request.question}") answer, context = retriever.answer_question(request.question) return AnswerResponse( answer=answer, retrieved_context=context ) except Exception as e: print(f"API处理错误: {e}") raise HTTPException(status_code=500, detail=f"服务器内部错误: {str(e)}") @app.get("/health") def health_check(): """健康检查端点""" try: # 可以添加对Neo4j和OpenAI连接的健康检查 return {"status": "healthy", "database": "connected", "llm": "available"} except Exception as e: return {"status": "unhealthy", "error": str(e)}, 503 if __name__ == "__main__": uvicorn.run(app, host="0.0.0.0", port=8000)使用以下命令启动后端服务:
cd medical_graphrag_qa python src/api.py服务将在http://localhost:8000启动。你可以访问http://localhost:8000/docs查看自动生成的交互式API文档(Swagger UI),并直接在那里测试/ask接口。
4.3 构建Streamlit前端界面
最后,我们创建一个简单的前端,让用户可以通过网页交互。创建文件src/frontend.py。
# src/frontend.py import streamlit as st import requests import json # 页面配置 st.set_page_config( page_title="医疗健康智能诊断助手", page_icon="🏥", layout="wide" ) # 标题和描述 st.title("🏥 基于知识图谱与AI的医疗健康问答系统") st.markdown(""" 这是一个演示系统,它结合了**Neo4j知识图谱**和**大语言模型**,能够理解症状并基于医学知识进行推理。 **请注意**:本系统提供的信息仅供参考,不能替代专业医生的诊断。如有健康问题,请及时就医。 """) # 初始化会话状态 if 'chat_history' not in st.session_state: st.session_state.chat_history = [] # 侧边栏 - 显示知识图谱信息 with st.sidebar: st.header("ℹ️ 系统信息") st.markdown(""" **技术栈**: - 后端: FastAPI - 图数据库: Neo4j AuraDB - 大语言模型: OpenAI GPT - 前端: Streamlit **知识图谱包含**: - 疾病、症状、药品、检查等实体 - 实体间的诊断、治疗、检查关系 """) if st.button("清空对话历史"): st.session_state.chat_history = [] st.rerun() # 主界面 col1, col2 = st.columns([3, 2]) with col1: st.subheader("💬 开始咨询") user_question = st.text_area( "请输入您的症状或健康问题(例如:咳嗽发烧可能是什么病?感冒应该吃什么药?):", height=100, key="input_question" ) if st.button("提交问题", type="primary"): if user_question.strip(): with st.spinner("正在查询知识图谱并生成回答..."): try: # 调用后端API api_url = "http://localhost:8000/ask" # 确保后端正在运行 payload = {"question": user_question} response = requests.post(api_url, json=payload, timeout=30) if response.status_code == 200: result = response.json() answer = result.get("answer", "未收到答案") context = result.get("retrieved_context", "无上下文") # 保存到对话历史 st.session_state.chat_history.append({ "question": user_question, "answer": answer, "context": context }) else: st.error(f"请求失败: {response.status_code} - {response.text}") except requests.exceptions.ConnectionError: st.error("无法连接到后端服务,请确保 `src/api.py` 正在运行。") except Exception as e: st.error(f"发生错误: {str(e)}") else: st.warning("请输入问题再提交。") # 显示对话历史 st.subheader("📜 对话历史") if st.session_state.chat_history: for i, chat in enumerate(reversed(st.session_state.chat_history[-5:]), 1): # 只显示最近5条 with st.expander(f"Q{i}: {chat['question'][:50]}..."): st.markdown(f"**问题**: {chat['question']}") st.markdown(f"**回答**: {chat['answer']}") # 可以隐藏详细的上下文,点击展开 with st.expander("查看检索到的知识图谱上下文"): st.code(chat['context'], language='text') else: st.info("暂无对话历史。请在上方提出问题。") with col2: st.subheader("🔍 知识图谱检索演示") st.markdown(""" 当你提交问题时,系统会: 1. **理解问题**:LLM提取关键医学实体(如“咳嗽”、“发烧”)。 2. **图谱查询**:生成Cypher语句,在Neo4j中查找关联的疾病、症状、药品。 3. **生成回答**:LLM结合检索到的结构化知识,生成专业回答。 **示例查询**: - “感冒有哪些症状?” - “肺炎需要做什么检查?” - “咳嗽和发烧同时出现可能是什么病?” - “高血压常用药有哪些?” """) # 可以添加一个示例按钮,快速填充问题 if st.button("加载示例问题: ‘感冒有哪些症状?’"): st.session_state.input_question = "感冒有哪些症状?" st.rerun() # 页脚 st.markdown("---") st.caption(""" ⚠️ **免责声明**: 本系统为学术演示项目,其生成内容可能存在不准确或过时之处,绝不能用于实际医疗诊断或治疗决策。所有健康问题请咨询合格医疗人员。 """)启动前端应用:
streamlit run src/frontend.pyStreamlit 会自动在浏览器中打开页面(通常是http://localhost:8501)。现在,你就可以通过这个界面与你的GraphRAG医疗问答系统交互了!
5. 系统优化与高级功能
基础系统搭建完成后,我们可以从以下几个方面进行优化,使其更健壮、更智能。
5.1 优化Cypher查询生成
上面的简单提示词可能生成不准确或低效的Cypher。我们可以提供更详细的示例(Few-Shot Prompting)来引导LLM。
# 在 graph_retriever.py 的 cypher_generation_prompt 中改进 self.cypher_generation_prompt = PromptTemplate.from_template(""" 你是一个Neo4j Cypher专家。根据以下图谱Schema和示例,将用户问题转化为一个精确的Cypher查询。 只返回Cypher语句,不要有其他内容。 图谱Schema: {schema} 示例: 问题: “感冒有哪些症状?” Cypher: MATCH (d:Disease {{name: '感冒'}})-[:HAS_SYMPTOM]->(s:Symptom) RETURN d.name as disease, collect(s.name) as symptoms 问题: “什么病会导致咳嗽和发烧?” Cypher: MATCH (d:Disease)-[:HAS_SYMPTOM]->(s:Symptom) WHERE s.name IN ['咳嗽', '发烧'] WITH d, collect(s.name) as matched_symptoms WHERE size(matched_symptoms) = 2 RETURN d.name as disease, matched_symptoms 问题: “肺炎需要做什么检查?” Cypher: MATCH (d:Disease {{name: '肺炎'}})-[:NEEDS_EXAM]->(e:Examination) RETURN d.name as disease, collect(e.name) as examinations 现在,请为以下问题生成Cypher查询: 问题: {question} Cypher: """)5.2 增加多轮对话记忆
当前的系统是无状态的。为了实现多轮对话(如用户追问),需要引入对话记忆。我们可以使用LangChain的ConversationBufferMemory。
# src/advanced_chain.py from langchain.memory import ConversationBufferMemory from langchain.chains import ConversationalRetrievalChain from langchain_community.chat_message_histories import StreamlitChatMessageHistory # 在Streamlit前端初始化记忆 msgs = StreamlitChatMessageHistory(key="langchain_messages") memory = ConversationBufferMemory(memory_key="chat_history", chat_memory=msgs, return_messages=True) # 将记忆整合到检索链中(需使用LangChain的特定Chain类) # 注意:这需要调整之前的GraphCypherQAChain或使用更灵活的LCEL(LangChain Expression Language)重新构建链。5.3 处理模糊查询与答案验证
当用户问题模糊或图谱中无直接答案时,系统应妥善处理。
- 实体链接消歧: 如果LLM抽取出“苹果”,需要区分是水果还是公司。在医疗领域,“心脏”可能指器官也可能指疾病“心脏病”。可以通过在提示词中强调医疗上下文,或使用更专业的医学实体识别模型来改善。
- 置信度与回退: 在图检索后,可以计算返回结果的“丰富度”(如关联路径的条数、节点的度中心性)。如果结果太稀疏,则触发“回退”机制,让LLM明确告知用户“知识库中未找到确切信息,以下是一些一般性建议...”,而不是强行编造。
6. 常见问题与排查指南
在开发和部署过程中,你可能会遇到以下问题:
| 问题现象 | 可能原因 | 解决思路 |
|---|---|---|
| 连接Neo4j失败 | 1. URI、用户名或密码错误。 2. AuraDB实例未启动或已暂停。 3. 本地网络问题或防火墙阻止。 | 1. 检查.env文件中的NEO4J_URI格式是否正确(neo4j+s://)。2. 登录AuraDB控制台确认实例状态为 “Running”。 3. 尝试在浏览器中打开AuraDB提供的“Browser”链接,用相同凭证登录测试。 |
| OpenAI API调用失败 | 1. API Key无效或过期。 2. 账户余额不足。 3. 请求超时或网络问题。 | 1. 在OpenAI平台检查API Key状态和余额。 2. 尝试在命令行用 curl或简单脚本测试API连通性。3. 考虑增加超时时间或使用代理(需合规)。 |
| LLM生成的Cypher语法错误 | 1. 提示词不够清晰。 2. LLM对图谱Schema理解有偏差。 3. 问题过于复杂。 | 1. 优化cypher_generation_prompt,提供更具体的示例。2. 在提示词中更清晰地描述节点标签和关系类型。 3. 增加一个“Cypher验证”步骤,尝试执行查询,如果失败则让LLM重试或简化问题。 |
| 检索结果为空 | 1. 知识图谱中确实没有相关信息。 2. 实体抽取错误,导致查询条件不匹配。 3. Cypher查询逻辑过于严格。 | 1. 在Neo4j Browser中手动执行生成的Cypher查询验证。 2. 打印出LLM抽取的实体,检查是否正确。 3. 考虑使用模糊匹配 CONTAINS或=~,或查询更广泛的关联路径。 |
| Streamlit前端无法连接后端 | 1. 后端API服务 (src/api.py) 未启动。2. 前端代码中的API地址 ( http://localhost:8000) 错误。3. 端口被占用。 | 1. 确保在运行streamlit run前,已在一个终端运行python src/api.py。2. 检查后端服务是否在预期的IP和端口上运行。 3. 使用 netstat -an | grep 8000(Linux/macOS) 或Get-NetTCPConnection -LocalPort 8000(Windows PowerShell) 查看端口占用。 |
| 导入数据时出现编码错误 | JSON文件包含非UTF-8字符。 | 确保JSON文件以UTF-8编码保存。在open()函数中明确指定encoding=‘utf-8’。 |
7. 项目扩展与毕业设计提升建议
一个基础的GraphRAG系统已经完成。要让其成为出色的毕业设计,可以考虑以下扩展方向:
丰富知识图谱:
- 数据源: 从权威医学网站(如默沙东诊疗手册)、公开数据集(如CHIP、CMeKG)或医学文献中爬取或解析更多数据。
- 关系深化: 添加更多关系类型,如
COMPLICATION_WITH(并发症)、CONTRAINDICATION(禁忌症)、PREVENTION(预防措施)等。 - 属性完善: 为节点添加更多属性,如疾病的
发病率、高危人群,药品的用法用量、副作用。
实现混合检索:
- 结合向量检索。将疾病的描述文本、药品说明书等长文本进行向量化存储(如使用ChromaDB、Milvus)。
- 当用户提出描述性、非结构化问题时(如“描述一下糖尿病的典型表现”),优先使用向量检索获取详细文本。
- 当用户问题涉及明确实体和关系时(如“A药和B药能一起吃吗”),使用图检索。
- 设计一个路由机制,自动判断使用哪种或混合使用两种检索方式。
增强系统可解释性:
- 在前端界面中,不仅显示最终答案,还可以可视化检索到的知识子图。利用Neo4j的Bloom或前端库(如D3.js, vis.js)将关联的疾病、症状节点和关系以图的形式展示出来,让推理过程“看得见”。
引入评估体系:
- 构建一个小的测试集(Q&A对),从准确性、相关性、完整性、安全性(是否产生有害建议)等维度评估系统性能。
- 与传统的纯向量RAG系统进行对比实验,用数据证明GraphRAG在关系推理问题上的优势。
部署与工程化:
- 使用Docker容器化你的应用(包括Python环境、依赖)。
- 编写
Dockerfile和docker-compose.yml。 - 尝试将服务部署到云服务器(如阿里云ECS、腾讯云轻量应用服务器)或云原生平台(如Railway、Render),并配置域名。
这个项目从零到一的搭建过程,完整覆盖了现代AI应用的核心环节:数据处理、知识工程、大模型应用开发、前后端集成。它不仅是一个优秀的毕业设计,更是你迈向AI工程师或LLM应用开发者的扎实一步。建议你在理解本文每个模块的基础上,选择一个扩展方向深入实践,这会让你的项目脱颖而出。