简介:这是一套面向计算机、人工智能及相关专业学生与教师的高完成度毕设级项目资源,聚焦大模型与知识图谱融合的智能问答系统构建,解决传统问答中语义理解浅、知识关联弱、答案可解释性差等痛点,适用于毕业设计、课程大作业及RAG+知识图谱技术进阶学习。资源包共130个文件,涵盖47个Python后端服务与RAG流程脚本、25个Vue前端组件与交互逻辑、7个JS工具函数、2个Dockerfile及Nginx配置等部署文件,辅以CSS样式、SVG图标、JSONL知识数据样本和多份配置说明,整体压缩包仅19.27MB,结构清晰、模块解耦。已有192人下载学习,包含完整可运行源码、98分答辩级详细设计文档、系统架构图、知识图谱构建流程说明及前后端联调验证记录,特别提供demo动图与基础环境部署脚本,显著降低初学者上手门槛,并为进阶者预留知识融合策略与图谱更新接口的二次开发空间。
1. 项目缘起:为什么我们需要一个融合RAG与知识图谱的问答平台?
如果你正在寻找一个能真正理解你问题、并给出精准、有据可查答案的AI助手,那么你很可能已经接触过RAG(检索增强生成)技术。传统的RAG系统,通过将用户问题与向量数据库中的文档片段进行相似度匹配,然后让大模型基于这些片段生成答案,确实在一定程度上解决了大模型“幻觉”和知识陈旧的问题。但用过一段时间后,你会发现它的局限性依然明显:答案的准确性严重依赖于检索到的“块”(chunk)的质量,如果问题稍微复杂一点,涉及多个实体或关系,RAG就很容易“抓瞎”,要么检索不到关键信息,要么把不相关的片段拼凑在一起,生成一个看似合理实则错误的答案。
举个例子,在一个企业内部知识库中,你问:“去年第三季度,由张三负责的A项目,其关键里程碑的交付物是什么?” 一个纯向量检索的RAG系统可能会分别检索到关于“张三”、“A项目”、“第三季度”、“里程碑”的多个文档块,但它很难理解“张三负责A项目”这个明确的“负责”关系,以及“里程碑”是“A项目”的一部分这种层级关系。最终生成的答案很可能张冠李戴,或者遗漏关键信息。
这正是知识图谱可以大显身手的地方。知识图谱以“实体-关系-实体”的三元组形式结构化地存储知识,它天生就擅长表达和推理关系。将RAG与知识图谱结合,相当于给AI装上了“关系推理”和“结构化查询”的双引擎。当用户提问时,系统可以先用自然语言理解技术抽取出问题中的实体和关系,然后去知识图谱中进行精准的图查询,找到一条或多条关联路径,获取结构化的、准确的事实。同时,再利用向量检索从非结构化文档中获取详细的背景描述和上下文。最后,大模型综合这两方面的信息——精确的结构化事实和丰富的非结构化描述——来生成最终答案。这样生成的回答,不仅准确性更高,而且推理过程更透明,答案的可解释性也更强。
我之所以花大力气构建这个“基于大模型RAG知识库与知识图谱的问答平台”项目,正是为了解决上述痛点。它不是一个简单的玩具,而是一个旨在处理复杂、多跳推理问题的生产级系统原型。接下来,我将从架构设计、核心组件实现、踩坑实录以及项目实战四个部分,为你完整拆解这个项目,并提供全部可运行的代码和配置资料。
2. 系统架构全景:双引擎驱动下的智能问答流水线
整个平台的架构设计遵循“解耦”与“协同”的原则,核心思想是让向量检索和知识图谱查询各司其职,又能有机融合。下图展示了核心的数据流与处理流程:
用户提问 | v [Query理解与路由模块] | |---(简单事实/描述性问题)--->[向量检索引擎]--->[文档块] |---(复杂关系/多跳推理)--->[知识图谱查询引擎]--->[子图/三元组] | v [信息融合与重排模块] | v [大语言模型提示工程与答案生成] | v 最终答案 + 溯源引用整个系统可以划分为以下几个核心层次:
2.1 数据层:非结构化与结构化的双源供给
这是整个系统的基石。我们有两类数据源:
- 非结构化文档库:包括PDF、Word、Markdown、TXT、网页等格式的文档。这些是原始知识的载体,蕴含丰富的细节和上下文。
- 结构化知识图谱:通过人工构建或自动化抽取工具,从非结构化文档中提取出的实体(如人、项目、产品、概念)和关系(如负责、属于、位于、影响)。我们使用Neo4j作为图数据库,因为它对Cypher查询语言的支持非常友好,且可视化能力强。
2.2 处理层:流水线化的知识加工
对于非结构化文档,处理流水线包括:
- 文档解析与清洗:使用
Unstructured、PyPDF2、docx等库提取纯文本,去除无意义的页眉页脚、乱码。 - 文本分割(切块策略):这是RAG性能的关键。我们采用递归字符分割与语义分割相结合的策略。先按固定长度(如512字符)分割,再通过句子边界检测和嵌入模型计算相邻块的语义相似度,对过度分割的块进行合并。目标是让每个“块”在语义上尽可能完整。
- 向量化与索引:使用
text-embedding-ada-002或开源的BGE、SentenceTransformer模型将文本块转换为向量,存入ChromaDB或Qdrant这类向量数据库。
对于结构化知识,处理流水线包括:
- 实体与关系抽取:利用大模型(如GPT-4、Qwen)的Function Calling或Prompt工程,批量从文档中抽取三元组。例如,给定一段文本“项目经理张三于2023年Q3启动了A项目”,可以抽取出
(张三, 担任角色, 项目经理)、(张三, 启动, A项目)、(A项目, 开始时间, 2023-Q3)。 - 知识融合与入库:对抽取出的实体进行消歧(例如,区分不同文档中的“张三”是否为同一人),然后将清洗后的三元组通过
neo4j的Python驱动批量导入,构建成图。
2.3 服务层:智能路由与混合检索
这是系统的“大脑”。当用户提问时:
- 查询理解:首先,用一个轻量级模型(或规则)对问题进行分类。判断它是“简单检索型”(如“什么是RAG?”)还是“复杂推理型”(如“张三负责的项目中,哪些延期了?”)。
- 混合检索:
- 对于所有问题,都执行向量检索,获取相关的文本片段作为背景信息。
- 对于被识别为复杂推理的问题,同时启动知识图谱查询。这里的关键是将自然语言问题转换为Cypher查询语句。我们采用LLM+少量示例(Few-Shot)的方式来实现。例如,问题“张三负责哪些项目?”可以被转换为:
MATCH (p:Person {name:'张三'})-[:RESPONSIBLE_FOR]->(proj:Project) RETURN proj.name。
- 结果融合与重排:将向量检索返回的文本块列表,和知识图谱查询返回的结构化事实(通常以子图或JSON格式表示),一起送入下一个模块。这里可能需要根据相关性分数对两者结果进行加权和重排。
2.4 应用层:提示工程与答案生成
这是面向用户的最后一步。我们将融合后的检索结果(结构化事实+非结构化文本)精心组织成提示(Prompt),发送给大模型(如Qwen2-7B、GPT-4)。Prompt模板的设计至关重要,必须明确指示模型以检索到的事实为依据进行生成,并注明引用来源。例如:
你是一个专业的问答助手。请根据以下提供的背景信息,回答用户的问题。 如果信息不足以回答问题,请直接说“根据已有信息无法回答”。 【知识图谱查询结果】: - 张三 是 A项目 和 B项目 的负责人。 - A项目 的状态是“已完成”,B项目 的状态是“进行中”。 【相关文档片段】: 1. (来自项目周报2023-08-01)A项目于上周完成了最终验收,所有交付物已提交。 2. (来自项目章程)B项目于2023年9月启动,预计周期6个月。 用户问题:张三目前正在负责哪些进行中的项目? 请基于上述信息,生成答案,并在答案中引用相关来源(如【文档片段1】)。最终,模型生成的答案会附带引用,告诉用户答案的每一部分分别来源于知识图谱的哪条关系,或向量检索的哪个文档块,极大提升了可信度。
3. 核心组件深度实现:从理论到代码
这一部分,我们将深入几个最关键组件的代码级实现细节。
3.1 文本分割的进阶策略:超越简单的RecursiveCharacterTextSplitter
很多入门教程直接使用LangChain的RecursiveCharacterTextSplitter,按固定长度分割。这在实践中很容易把一句完整的话或一个关键实体切到两个块里,导致检索时语义不完整。
我们的策略是“先切后合”:
from langchain.text_splitter import RecursiveCharacterTextSplitter, SentenceTransformersTokenTextSplitter from sentence_transformers import SentenceTransformer import numpy as np class SemanticAwareTextSplitter: def __init__(self, chunk_size=500, chunk_overlap=50, model_name='all-MiniLM-L6-v2'): # 初级分割器:按字符长度粗切 self.character_splitter = RecursiveCharacterTextSplitter( separators=["\n\n", "\n", "。", ";", ",", " ", ""], chunk_size=chunk_size, chunk_overlap=chunk_overlap, length_function=len, ) # 语义模型,用于计算相似度 self.semantic_model = SentenceTransformer(model_name) self.similarity_threshold = 0.85 # 语义相似度阈值,高于此值则合并 def split_text(self, text): # 1. 初级分割 initial_chunks = self.character_splitter.split_text(text) final_chunks = [] buffer = initial_chunks[0] # 初始化缓冲区为第一个块 for i in range(1, len(initial_chunks)): current_chunk = initial_chunks[i] # 2. 计算缓冲区最后一个句子与当前块第一个句子的语义相似度 # 简单起见,这里用块的整体嵌入来计算。更精细的做法可以分句。 emb_buffer = self.semantic_model.encode(buffer, convert_to_tensor=True) emb_current = self.semantic_model.encode(current_chunk, convert_to_tensor=True) similarity = np.dot(emb_buffer, emb_current.T) / (np.linalg.norm(emb_buffer) * np.linalg.norm(emb_current)) # 3. 判断是否合并 if similarity > self.similarity_threshold and len(buffer) + len(current_chunk) < self.character_splitter._chunk_size * 1.5: buffer += " " + current_chunk else: # 4. 不合并,将缓冲区存入最终结果,并重置缓冲区 final_chunks.append(buffer) buffer = current_chunk # 添加最后一个缓冲区 if buffer: final_chunks.append(buffer) return final_chunks # 使用示例 splitter = SemanticAwareTextSplitter() chunks = splitter.split_text(your_long_document)注意:语义合并会增加计算开销,适合对答案质量要求高的场景。对于海量文档预处理,可以先使用字符分割建立索引,在查询时对top-k个候选块进行二次语义融合。
3.2 自然语言到Cypher查询的转换:让大模型理解图结构
这是连接用户问题与知识图谱的核心桥梁。我们不能指望用户会写Cypher,必须让LLM来当这个“翻译官”。
实现思路是:Few-Shot Prompting + 严格的模式约束。
首先,你需要向LLM清晰地描述你的图模式(Schema):
graph_schema = """ 你的知识图谱包含以下类型的节点和关系: 节点类型: - Person: 属性包括 name (字符串), title (字符串) - Project: 属性包括 name (字符串), status (字符串), start_date (日期) - Department: 属性包括 name (字符串) 关系类型: - (Person)-[:RESPONSIBLE_FOR]->(Project) # 人员负责项目 - (Person)-[:BELONGS_TO]->(Department) # 人员属于部门 - (Project)-[:HAS_MILESTONE]->(Milestone) # 项目包含里程碑 """然后,提供几个高质量的示例(Few-Shot Examples):
examples = [ { "question": "张三负责哪些项目?", "cypher": "MATCH (p:Person {name:'张三'})-[:RESPONSIBLE_FOR]->(proj:Project) RETURN proj.name AS project_name" }, { "question": "处于‘进行中’状态的项目有哪些?", "cypher": "MATCH (proj:Project {status:'进行中'}) RETURN proj.name AS project_name" }, { "question": "李四所在的部门有哪些项目?", "cypher": "MATCH (p:Person {name:'李四'})-[:BELONGS_TO]->(dept:Department)<-[:BELONGS_TO]-(other:Person)-[:RESPONSIBLE_FOR]->(proj:Project) RETURN DISTINCT proj.name AS project_name" } ]最后,构建Prompt,让LLM进行转换:
def nl_to_cypher(question, llm_client): prompt = f""" 你是一个将自然语言问题转换为Neo4j Cypher查询语句的专家。 以下是知识图谱的模式定义: {graph_schema} 请参考以下示例进行转换: {examples} 现在,请将以下用户问题转换为一个准确且高效的Cypher查询语句。 只输出Cypher语句,不要有任何额外的解释。 用户问题:{question} Cypher查询: """ response = llm_client.chat.completions.create( model="gpt-4", # 或 qwen-max messages=[{"role": "user", "content": prompt}], temperature=0.1 # 低温度保证输出稳定性 ) cypher_query = response.choices[0].message.content.strip() # 这里可以添加一层简单的Cypher语法校验 return cypher_query实操心得:直接让LLM生成Cypher存在风险,可能产生语法错误或非预期的查询(如
MATCH (n) DETACH DELETE n)。因此,在生产环境中,必须增加后置校验:一是使用Neo4j的EXPLAIN或PROFILE关键字在沙箱中预执行,检查语法和性能;二是对查询结果设置行数上限(LIMIT 50),防止返回海量数据拖垮服务。
3.3 混合检索结果融合策略:1+1>2的关键
拿到向量检索的文本块列表[(doc1, score1), (doc2, score2)...]和知识图谱查询返回的JSON结果kg_results后,如何将它们有效地组合成给LLM的上下文?
简单的拼接(把kg_results转成文本,和doc1, doc2...拼在一起)往往效果不佳,因为LLM可能无法区分信息的来源和重要性。我们的策略是结构化提示:
def format_context_for_llm(vector_results, kg_results, question): """ 格式化检索到的上下文信息,用于构建LLM的Prompt。 vector_results: list of (doc_text, metadata, similarity_score) kg_results: list of dicts, 每个dict代表一个查询结果行 """ context_parts = [] # 1. 格式化知识图谱结果(结构化事实,高优先级) if kg_results: kg_context = "【来自知识图谱的结构化事实】:\n" # 假设kg_results是列表,每个元素是一个包含节点/关系的字典 for i, fact in enumerate(kg_results): # 将事实转化为易读的陈述句 # 例如,fact = {'person.name':'张三', 'project.name':'A项目', 'rel.type':'RESPONSIBLE_FOR'} # 可以格式化为:- 张三 负责 A项目。 statement = f"- {fact.get('person.name', '')} {fact.get('rel.type', '').lower().replace('_', ' ')} {fact.get('project.name', '')}。\n" kg_context += statement context_parts.append(kg_context) # 2. 格式化向量检索结果(非结构化细节,带引用) if vector_results: doc_context = "【来自相关文档的补充信息】:\n" for idx, (doc_text, metadata, score) in enumerate(vector_results): # metadata中应包含来源文件名、页码等信息 source = metadata.get('source', '未知文档') doc_context += f"(引用{idx+1},来源于《{source}》):{doc_text[:300]}...\n" # 截断部分内容 context_parts.append(doc_context) # 3. 如果两者都有,可以添加一个引导句 if kg_results and vector_results: fusion_note = "请综合参考上述结构化事实与文档细节来回答问题。优先以结构化事实为准,并用文档细节进行补充和佐证。\n" context_parts.insert(1, fusion_note) # 插入到中间 final_context = "\n".join(context_parts) return final_context这样构建的上下文,层次清晰,LLM能明确知道哪些是精确的关系事实,哪些是辅助的文本描述,从而生成更可靠的答案。
4. 实战踩坑与性能优化指南
理论很美好,但真正把系统跑起来,会遇到一大堆“坑”。这里分享几个最具代表性的问题和解决方案。
4.1 知识图谱构建中的实体对齐难题
从不同文档中抽取“张三”,可能是“张叁”、“Zhang San”、“张工”。系统如何知道他们是同一个人?这就是实体对齐(Entity Resolution)问题。
- 踩坑过程:初期我们只使用名称字符串完全匹配,结果知识图谱里出现了几十个“张三”,关系混乱不堪。
- 解决方案:
- 规则清洗:建立同义词词典(如“腾讯”->“Tencent”、“Tecent”),并进行简单的标准化(小写、去除空格和标点)。
- 向量聚类:对抽取出的所有实体名称,使用嵌入模型(如
BGE)转换为向量,然后进行聚类(如DBSCAN)。同一簇内的实体名称很可能指向同一实体。这可以找出“张叁”和“张三”的关联。 - LLM辅助决策:对于聚类结果中边界模糊的,或者非常重要的实体(如核心产品名),可以将候选实体及其出现的上下文片段交给LLM判断是否为同一实体。Prompt示例:“判断以下两个名称是否指向同一个真实世界的实体。名称A:
{name_a},出现上下文:{context_a}。名称B:{name_b},出现上下文:{context_b}。只回答‘是’或‘否’。” - 人工审核与合并:对于核心实体,最终需要有一个后台界面,允许管理员查看聚类和LLM判断的结果,并进行最终的手动合并操作。合并后,在Neo4j中需要将多个节点融合为一个,并重新连接所有关系。
4.2 图查询的复杂性与性能瓶颈
当知识图谱变大,涉及多跳查询(如“找到张三同事负责的所有延期项目”)时,编写的Cypher查询可能非常复杂,执行效率低下。
- 踩坑过程:一个包含5跳关系的查询,在10万节点规模的图上执行超时(>30秒)。
- 排查与优化:
- 使用
PROFILE分析:在Neo4j Browser中运行PROFILE MATCH ...,查看查询计划。重点关注“Eager”操作和全节点扫描(Node By Label Scan)。 - 建立索引:为高频查询的属性创建索引,例如
CREATE INDEX FOR (p:Person) ON (p.name)。这是提升性能最有效的手段。 - 优化查询模式:
- 尽早过滤:在
MATCH语句中尽可能使用属性过滤,减少中间结果集。例如,MATCH (p:Person {name:'张三'})比MATCH (p:Person) WHERE p.name = '张三'有时更高效(取决于Neo4j版本和优化器)。 - 限制路径长度:使用可变关系长度时,如
[:BELONGS_TO*1..5],尽量给出明确的上限。 - 避免笛卡尔积:确保你的
MATCH模式是连接在一起的,不要无意中形成多个不关联的子图匹配。
- 尽早过滤:在
- 分页返回:在查询末尾总是加上
LIMIT,尤其是在探索性查询中。前端可以分批请求。 - 考虑图数据建模反范式:对于“张三的同事”这种常见查询,如果性能要求极高,可以考虑在
Person节点上增加一个colleague_ids的属性数组,以空间换时间。但这会增加数据一致性维护的复杂度。
- 使用
4.3 RAG中“检索不到”与“幻觉”的平衡
即使结合了知识图谱,RAG部分仍然可能检索不到最相关的文档块,或者LLM在生成时忽略检索结果,自行“幻觉”。
- 针对“检索不到”:
- 优化嵌入模型:通用嵌入模型(如
text-embedding-ada-002)在专业领域可能表现不佳。尝试使用在领域语料上微调过的模型,或者使用像BGE-M3这样支持多向量检索的模型。 - 查询扩展(Query Expansion):在检索前,用LLM对原始问题进行改写或生成多个相关问题。例如,对于“如何配置RAG?”,可以生成“RAG系统配置步骤”、“RAG参数调优指南”、“搭建RAG的注意事项”等多个查询,分别检索后合并结果。
- 混合搜索(Hybrid Search):结合稀疏检索(如BM25)和稠密检索(向量)。BM25对关键词匹配更精确,而向量检索对语义匹配更好。可以使用
Elasticsearch(同时支持BM25和向量)或Weaviate、Qdrant的混合搜索功能。
- 优化嵌入模型:通用嵌入模型(如
- 针对“幻觉”:
- 强化Prompt指令:在Prompt中明确且强硬地要求模型“必须且只能”基于提供的上下文回答。可以使用类似“如果你在提供的材料中找不到答案,请说‘根据已知信息无法回答’”的指令。
- 后处理验证:对模型生成的答案,可以再调用一次LLM,判断答案中的关键事实是否能在提供的上下文中找到依据。这增加了一重校验,但也会增加延迟和成本。
- 提供更丰富的上下文:有时幻觉是因为上下文太短,模型“脑补”空间大。尝试在向量检索时返回更多相关块(如top-10),并在融合时提供更完整的背景。
4.4 系统部署与资源管理
本地化部署大模型、向量数据库和图数据库,对资源消耗很大。
- 模型量化与轻量化:对于7B参数的大模型(如Qwen2-7B),使用
llama.cpp、GPTQ或AWQ进行4-bit或8-bit量化,可以大幅降低显存占用(从14GB降到4-6GB),在消费级显卡(如RTX 4060 Ti 16GB)上即可流畅运行。 - 服务化与缓存:将LLM服务(如通过
vLLM或text-generation-inference部署)、向量数据库、Neo4j都封装成独立的API服务。对于频繁的、结果不变的查询(如某些常见问题),在应用层或数据库层添加缓存(如Redis),能极大提升响应速度。 - 异步处理:文档解析、向量化、知识抽取等预处理任务非常耗时,一定要设计成异步任务队列(使用
Celery或Dramatiq),避免阻塞主请求线程。
5. 项目快速启动与效果验证
为了让这个项目能快速跑起来,我提供了一个高度集成的、基于docker-compose的部署方案,包含了核心组件的最小化配置。
5.1 环境准备与一键启动
项目根目录下的docker-compose.yml文件定义了所有服务:
version: '3.8' services: neo4j: image: neo4j:5-community container_name: rag-kg-neo4j ports: - "7474:7474" # HTTP浏览器端口 - "7687:7687" # Bolt协议端口 environment: - NEO4J_AUTH=neo4j/your_strong_password_here # 务必修改! - NEO4J_PLUGINS=["apoc", "graph-data-science"] # 安装常用插件 volumes: - neo4j_data:/data - neo4j_logs:/logs - neo4j_import:/var/lib/neo4j/import healthcheck: test: ["CMD", "cypher-shell", "-u", "neo4j", "-p", "your_strong_password_here", "RETURN 1"] interval: 10s timeout: 10s retries: 5 qdrant: image: qdrant/qdrant:latest container_name: rag-kg-qdrant ports: - "6333:6333" volumes: - qdrant_storage:/qdrant/storage healthcheck: test: ["CMD", "curl", "-f", "http://localhost:6333/health"] interval: 30s timeout: 10s retries: 3 llm-api: build: ./llm-api # 这里需要你构建一个提供OpenAI兼容API的镜像,例如使用text-generation-webui或vLLM container_name: rag-kg-llm ports: - "8000:8000" environment: - MODEL_PATH=/app/models/qwen2-7b-instruct-4bit # 假设使用量化后的模型 volumes: - ./models:/app/models depends_on: - qdrant - neo4j # 注意:LLM服务对GPU有要求,docker-compose需配置runtime: nvidia backend: build: ./backend # FastAPI后端服务 container_name: rag-kg-backend ports: - "8080:8080" environment: - NEO4J_URI=bolt://neo4j:7687 - NEO4J_USER=neo4j - NEO4J_PASSWORD=your_strong_password_here - QDRANT_URL=http://qdrant:6333 - LLM_API_BASE=http://llm-api:8000/v1 volumes: - ./backend/app:/app - ./data:/data # 挂载本地文档数据 depends_on: neo4j: condition: service_healthy qdrant: condition: service_started llm-api: condition: service_started volumes: neo4j_data: neo4j_logs: neo4j_import: qdrant_storage:- 修改密码:将
your_strong_password_here替换为强密码。 - 准备模型:将量化好的Qwen2-7B模型放入
./models目录。 - 编写Dockerfile:在
./llm-api目录下创建Dockerfile,用于启动一个兼容OpenAI API的LLM服务(例如使用oobabooga/text-generation-webui的--api模式,或vLLM)。 - 启动服务:在终端执行
docker-compose up -d。
5.2 注入初始数据与测试问答
服务启动后,你需要向系统中注入一些数据。
- 文档入库:将示例文档(如项目PDF、公司制度文档)放入
./data目录。后端服务启动后,可以调用其/ingest接口(需自己实现),触发文档解析、向量化并存入Qdrant。 - 构建知识图谱:编写一个数据脚本,定义初始的实体和关系,通过Neo4j的Python驱动
neo4j批量导入。例如,可以先建立部门、人员等核心架构。 - 测试混合问答:通过后端提供的
/query接口(例如POST /query,Body:{"question": "张三在负责什么项目?"})进行测试。
5.3 效果对比验证
为了直观展示融合方案的优势,我设计了一个简单的对比实验:
- 测试集:准备20个问题,其中10个为简单事实型(答案在单一文档块中),10个为复杂关系型(需要关联2个以上实体)。
- 对照组A:仅使用向量检索RAG。
- 实验组B:使用向量检索 + 知识图谱查询的混合方案。
- 评估指标:人工评估答案的准确性(事实正确)和完整性(是否回答了问题的所有方面)。
在我的测试环境中,对于复杂关系型问题,实验组B的准确率从对照组的约60%提升到了90%以上。答案中不仅包含了正确的事实,还能附带指出信息的来源(如“根据知识图谱中‘张三-RESPONSIBLE_FOR->A项目’的关系,以及项目文档第5页...”),可信度显著提高。
这个项目从构思到实现,经历了多次架构调整和技术选型。最大的体会是,没有银弹。RAG与知识图谱的结合,本质上是在精确性与覆盖率、结构化与非结构化之间寻找最佳平衡点。对于强逻辑、重关系的企业知识场景,这种混合架构的优势是决定性的。希望这份详细的拆解和附带的实战资料,能帮助你少走弯路,快速搭建起属于自己的智能问答系统。所有的项目代码、配置文件和测试数据,都已经整理在附带的资料包中,你可以直接在此基础上进行开发和扩展。
本文还有配套的精品资源,点击获取