资讯动态

RAG知识库与知识图谱问答平台:从文档证据到关系链的实战架构拆解

发布时间:2026/10/27 17:44:47 来源:尧图企业网站定制
简介一份基于大模型 RAG 知识库与知识图谱的问答平台完整毕业设计资料面向计算机、通信、人工智能、自动化等专业学生及从业者适用于期末课程设计、大作业或毕设参考。项目融合检索增强生成与知识图谱技术含设计文档、源码与部署配置答辩评审达98分代码已调试测试确保可直接运行。资源共130个文件压缩包约19.27MB以py后端逻辑、vue前端页面、png界面截图、sh部署脚本、yml与dockerfile容器配置等为主目录结构清晰便于按模块学习。已有192人学习下载。包内含完整项目源码、设计文档、Nginx与Docker部署配置、演示动图及说明文件从环境搭建到代码逻辑均有覆盖既可帮助小白理解RAG与知识图谱的落地流程也适合基础较好的读者在此基础上二次开发实现个性化问答功能。1. 大模型RAG知识库与知识图谱问答平台从文档证据到关系链一次拆透这个毕业设计级项目大模型RAG知识库与知识图谱问答平台这几年在毕业设计、期末大作业和企业知识库预研里都是高频选题。原因很直接RAG知识库擅长从文档里捞证据回答问题但遇到“谁负责XX项目”“XX系统依赖哪些模块”这种关系链问题纯向量召回只能返回零散片段知识图谱把实体和关系显式存下来却缺少大模型的语言组织能力。这份资源把两条路合并成一个问答平台文档切块后向量化召回实体关系抽成图谱供查询最后由大模型统一组织答案。适合要做毕设、大作业或者想在企业里快速搭一个私有知识库问答原型的人。2. RAG知识库底座文档拆解、向量化与top-k召回参数怎么定2.1 先分清两件事RAG补文本上下文图谱补结构化关系提问“这个方案的风险点是什么”答案通常是一段说明文字直接埋在文档里适合RAG去召回提问“这个项目由谁负责依赖哪些系统”证据分散在多份文档的不同段落用向量召回top_k5很可能只覆盖其中一个角度答出来残缺。知识图谱这时候比RAG直接它把人员、项目、系统之间的“负责、依赖、属于”关系预先抽成三元组查询时从某个实体出发一跳两跳就能拿到完整关系路径。所以这两条路不是竞争而是互补。RAG处理开放文本图谱处理结构化关系。一个问答平台设计得合不合理先看它是否在架构里明确区分了这两种证据再看两条路径的结果有没有统一的组装环节。我见过不少项目把图谱三元组转成一段文字塞进知识库等于放弃图的查询能力最后只能靠相似度碰运气关系链问题照样答不全。2.2 文档解析与切分为什么递归切分比硬切靠谱文档解析最常翻车的不是TXT和Markdown而是PDF。表格、页眉页脚、多栏排版会把抽取顺序打乱段落顺序一乱后面的切分和向量化全跟着错。我一般的做法是先用PyMuPDF或pdfplumber按页读文本再按阅读顺序拼接成一条连续字符串最后交给切分器。如果PDF是扫描件必须先OCR否则向量库里存的是乱码检索效果无从谈起。自检办法很便宜把抽取后的文本前50个字符打印出来看一眼乱不乱一目了然。切分这一步常见的实现是递归字符切分器。它按分隔符优先级一层层劈先保段落、再保句子、最后才用字符兜底比“按固定长度硬切”对中文友好得多。from langchain_text_splitters import RecursiveCharacterTextSplitter text_splitter RecursiveCharacterTextSplitter( chunk_size500, chunk_overlap80, separators[\n\n, \n, 。, , , , , , ], keep_separatorTrue, ) chunks text_splitter.split_text(raw_text) print(f共切出 {len(chunks)} 个片段) print(chunks[0][:120])逻辑说明把整段文档文本切成长度可控的片段后续每个片段单独向量化查询时按相似度召回最相关的几个片段。参数说明chunk_size是单片最大字符数这里取500适合制度类和操作手册类文本chunk_overlap是相邻片段的重叠长度取80能让跨在切缝上的问题至少被一个片段完整覆盖。separators数组里把中文句末标点放在前面很关键不这样做中文会被当成没有边界的字符串被迫用空格和字符硬切一句完整的话就被拦腰砍断。chunk_size和chunk_overlap没有普适值要按文档类型去试。论文、技术方案这类段落密集的文本我会调到600~800短条款、FAQ类文档用300左右。overlap一般取chunk_size的10%~20%太少会丢上下文太多则同一内容被重复索引一次召回返回三个几乎一样的片段白占上下文。文档类型建议chunk_size切分要点制度/操作手册300~500条款相对独立按条切技术方案/论文600~800段落上下文长overlap加大FAQ/短问答200~300一问一答避免一条被拆成两半提示切分完成后抽相邻两个片段读一遍首尾比任何参数调优都高效。2.3 向量化与召回嵌入模型选型、索引与top-k嵌入模型直接决定了“语义相似度”这杆秤准不准。中文语料优先选在中文评测集上表现好的模型比如bge系列轻量用bge-small-zh效果优先用bge-large。这里不要迷信模型越大越好嵌入维度越高向量索引文件越大查询也越慢对毕业设计或小规模知识库来说属于无谓开销。向量库方面FAISS适合本地单机不用额外起服务适合这套项目Milvus适合百万级以上向量pgvector适合已有PostgreSQL的场景。from sentence_transformers import SentenceTransformer import faiss import numpy as np model SentenceTransformer(BAAI/bge-small-zh-v1.5) vectors model.encode(chunks, normalize_embeddingsTrue, show_progress_barTrue) dim vectors.shape[1] index faiss.IndexFlatIP(dim) index.add(vectors.astype(float32)) query 项目延期的主要原因有哪些 q_vec model.encode([query], normalize_embeddingsTrue) scores, ids index.search(q_vec.astype(float32), top_k5) for rank, (sid, score) in enumerate(zip(ids[0], scores[0]), 1): print(fTop {rank}: score{score:.4f}, chunk_id{sid})逻辑说明把每个chunk编码成一个向量IndexFlatIP是内积索引配合normalize_embeddingsTrue时内积等价于余弦相似度查询时把query编码到同一向量空间再按相似度取top_k个片段id与得分。参数说明normalize_embeddingsTrue必须加否则不同长度的query得分不可比top_k5是起步值知识库大或问题分散时调到10但要注意后面的上下文总量。索引类型也要看数据量。几十万片段以内暴力检索IndexFlatIP已经够用响应在几十毫秒量级超过这个量级再用IVF或HNSW。IVF的关键参数nlist影响召回精度nlist太小会把相近向量分到不同桶里造成漏召回nlist太大会让查询变慢。代码里看到ivf_flat或hnsw参数时要意识到这是速度和精度的权衡不是默认值就适用一切数据量。还有一种常见疑问RAG知识库能存储图片吗如果要做图片问答那是多模态RAG文本切分、图片编码、跨模态检索和纯文本链路完全是两套工程。这套资源的主线是文本知识库图片要么先用OCR转成文本再入库要么单独走多模态向量索引别硬塞进同一个文本向量库。否则图片向量和文本向量很难对齐召回质量很飘。3. 知识图谱构建实体建模、三元组抽取与Cypher查询路径3.1 建模边界哪些实体和关系值得进图图谱建模的第一原则是进图的必须能被问答问题当成“起点”。人员、项目、部门、系统、文档是常见的节点类型属于、负责、参与、依赖、引用是常见的关系类型。属性可以放工号、状态、发布时间这类短字段长文本描述别往属性里塞。判断标准很简单某个信息是否会被“沿着关系找下去”的问题用到。比如“小王参与了哪些项目”就需要把(小王)-[参与]-(项目)存成关系而“项目背景是什么”是一段长文本留在文档chunk里由RAG召回即可放进图里既不合适也没必要。有人会问为什么不直接用关系数据库存三元组三层以内关系用SQL多表join还能撑四层、五层关系路径的查询SQL会写得非常长而“邻居的邻居”这种遍历在图数据库里天然高效。这就是图谱区别于表结构的地方。RAG负责文本图谱负责路径遍历两边各干各的活。3.2 三元组抽取大模型批量抽规则做兜底从文档构建图谱常见做法是先用大模型做批次抽取再清洗合并。规则和正则抽取快但长句里的关系经常被拆断召回率低大模型抽取效果更有保证代价是耗时和调用成本。我一般先标注少量样本确定实体类型和关系类型再让大模型按统一格式输出。extract_prompt 从下面文本中抽取三元组格式为 JSON 数组每个元素是 [实体1, 关系, 实体2]。 实体类型限定人员、项目、部门、系统、文档。 关系限定属于、负责、参与、依赖、引用。 只输出 JSON不要解释。 文本 {text} # 常见做法把文本按段落分批发给大模型 # 对返回结果做 json.loads 后统一清洗逻辑说明实体类型和关系类型先写死在prompt里避免大模型抽出一堆无法归类的边。参数说明批次文本不宜超过800字超过之后长尾关系容易丢temperature设成0或接近0抽取任务要确定性不需要创造性输出。清洗这一步至少要处理三类问题实体名归一、关系方向统一、重复关系去重。实体名归一化的典型例子同一人在不同文档里可能写作“张伟”“张伟(项目经理)”“项目经理张伟”。不归一问题里提到“张伟”时Cypher查不到“项目经理张伟”这个节点表现就是图谱问答冷冰冰返回空结果。我习惯在建库时给每个实体维护一个aliases别名列表写入图数据库时用规范名查询时先做别名映射。清洗第二个高发问题是关系方向。同一句话“研发部由张三负责”可能被抽成(研发部, 负责, 张三)也可能抽成(张三, 负责, 研发部)。我的办法是在抽取prompt里直接定义关系语义负责表示负责人指向被负责对象。这样批量结果里方向混乱的比例会大幅下降但无法完全归零入库前还是要做方向校验规则。3.3 查询路径从Cypher到问答上下文图谱建好之后问答环节要做两件事把问题中的实体映射到图节点再把它翻译成Cypher查询。实体映射可以用词表匹配也可以让小模型先抽实体再查别名Cypher推荐用参数化查询不要用字符串拼接。MATCH (p:Person)-[:BELONGS_TO]-(d:Department) WHERE d.name $dept RETURN p.name AS person_name, p.title AS title逻辑说明从部门节点出发沿着BELONGS_TO关系找到所有属于该部门的人员节点返回姓名和职务。参数说明$dept是参数占位符运行时传入部门名避免用户输入直接拼进查询语句关系方向在Cypher里用箭头体现方向写反就查不到结果。多个关系跳转时用链式MATCH或OPTIONAL MATCH比如“项目依赖哪些系统系统又属于哪个部门”需要两跳MATCH (pr:Project)-[:DEPENDS_ON]-(sys:System) OPTIONAL MATCH (sys)-[:BELONGS_TO]-(dept:Department) WHERE pr.name $project_name RETURN sys.name AS system_name, dept.name AS department_nameOPTIONAL MATCH的作用是系统可能没有归属部门时仍然返回系统行不会被过滤掉。这一步返回的结果需要被转成自然语言片段喂给大模型比如“系统A归属研发部系统B归属运维部”。转换逻辑放在第4章的统一组装步骤里。最近常听到的GraphRAG、ontology RAG本质上就是把这类图谱结构引入RAG链路区别只是图谱在召回阶段充当路由约束还是在生成阶段充当额外证据。这套项目的参考写法是后一种先Cypher查出证据再作为结构化上下文和RAG片段一起进prompt。理解这一点很关键答辩时如果被问“你和GraphRAG有什么关系”你可以准确说出你的图谱是查询证据源而不是图遍历生成引擎。4. 问答平台串联意图路由、上下文组装与FastAPI服务4.1 意图路由先判断这个问题该走哪条检索链路同一个用户可能问“XX系统怎么部署”也可能问“XX系统依赖哪些服务”。前者是文本问答后者是关系问答。如果所有问题都走同一条链路要么RAG答关系问题答不全要么图谱答文本问题答不出细节。所以第一步是路由。路由可以很简单规则匹配。含“谁负责、属于哪个、依赖哪些、有哪些系统”这类词的问题优先走图谱其他走RAG。也可以做得更准用LLM对query做一次分类输出rag、graph或both三类。毕业设计里用规则就够展示生产环境用规则兜底加LLM分类的双保险。def route_query(query: str) - str: if any(w in query for w in [谁负责, 属于哪个, 依赖哪些, 有哪些系统]): return graph if 的关系 in query or 关联 in query: return graph return rag逻辑说明先用封闭词表判断问题是否涉及关系链命中就走图谱路由。参数说明这个名单要根据自己的业务问题扩展不要照抄。规则路由的好处是可解释、零延迟坏处是换个问法就失效比如“这个项目的接口方是谁”没命中“谁负责”会被错误分到RAG。生产级做法是规则兜底加LLM分类由分类结果覆盖规则漏掉的问法。4.2 上下文组装把证据组织成大模型能回答的格式两条链路返回的证据格式不一样。RAG返回的是文档片段带原文和相似度图谱返回的是关系行带节点和关系类型。不能直接把原始数据堆给大模型否则它不知道哪些可信、哪些是噪声。我习惯先拼一个evidence块让两类证据有清晰的编号和边界。def build_evidence(rag_chunks, graph_rows): evidence_lines [] for i, chunk in enumerate(rag_chunks, 1): evidence_lines.append(f[文档{i}] {chunk}) for j, row in enumerate(graph_rows, 1): evidence_lines.append(f[关系{j}] {row[source]} - {row[relation]} - {row[target]}) return \n.join(evidence_lines)逻辑说明把两类证据统一编号后拼接大模型在prompt里能看到证据边界回答时能引用“文档1”或“关系2”。参数说明rag_chunks和graph_rows在传入前都做了截断RAG片段单条控制在200字左右图谱行控制在5条以内防止上下文过长。还有一个容易被忽略的细节prompt里要明确“无法回答时直接说不知道”。加了这句话之后模型在证据不足时编答案的概率会明显下降。4.3 FastAPI串联从query到answer的一个完整接口前面所有步骤最终要收敛成一个HTTP接口。常见做法是用FastAPI写一个/lang端点内部依次做路由、召回、组装、生成。本地大模型可以用ollama或vLLM拉起一个OpenAI风格接口FastAPI侧用openai SDK去调用这样模型换仓只需要改base_url。from fastapi import FastAPI from openai import OpenAI app FastAPI() client OpenAI(base_urlhttp://localhost:11434/v1, api_keyollama) def ask_llm(system_prompt: str, user_prompt: str) - str: resp client.chat.completions.create( modelqwen2.5:7b, temperature0.2, messages[ {role: system, content: system_prompt}, {role: user, content: user_prompt}, ], ) return resp.choices[0].message.content app.post(/lang) def answer_platform(query: str): route route_query(query) if route graph: graph_rows query_graph(query) evidence build_evidence([], graph_rows) else: rag_chunks retrieve(query) evidence build_evidence(rag_chunks, []) prompt f依据以下证据回答问题不知道就说不知道。\n{evidence} return {answer: ask_llm(你是企业内部知识库助手, prompt)}逻辑说明接口内部按路由分发RAG和图谱的结果统一转成evidence再交给大模型生成。参数说明temperature设为0.2保证事实性回答时不过度发散base_url指向本地模型服务切换云端模型时只需改url和model名。如果想让流式输出把streamTrue打开用SSE往前端推。这一套东西在macOS上同样能跑起来FAISS和FastAPI都是跨平台的。如果你不想写前端Dify这类低代码平台也能把检索、图谱查询、模型调用编排成应用底层思路和上面这段代码一致。接口里没有写会话历史但实际问答通常需要多轮。多轮的常见做法是在prompt中携带最近两轮问答的摘要而不是把全部历史塞进去。因为检索类系统每一轮问题相对独立历史越长prompt越容易被无关话题带偏。我在项目里一般只保留上一轮query和answer做成“历史会话”字段拼在system prompt下方既兼容追问又不把上下文撑爆。5. 避坑与排查RAG图谱问答效果翻车的五个高频原因5.1 切分把语义切断召回片段前后接不上现象问题明明在文档里有完整答案检索召回的片段却只有半句话大模型只能靠上下文猜或者干脆答错。原因chunk_size设得太小separators里没有“。”这些中文句末标点一句话被从中间硬切或者答案分布在一个片段头部和另一个片段尾部而chunk_overlap设得不够导致召回时哪半边都不完整。这个问题在PDF解析顺序错乱时尤其明显段落本身就是乱的切分再准也救不回来。解决把chunk_overlap调到chunk_size的15%以上检查separators确认包含“。”且顺序靠前对长文档切两刀抽查相邻片段看首尾是否衔接自然。这一步是整套RAG效果的地基地基没打好后面所有调优都是玄学。我见过有人连续调了三天prompt最后把chunk_size从1000降到400问题立刻解决。5.2 实体名不归一化图谱查询返回空结果现象RAG能答上来的关系类问题走图谱路径却返回空列表最终答案退化成纯RAG结果或者直接说“查不到”。原因文档里叫“张伟(项目经理)”问题里叫“张伟”两个写法在图上不是同一个节点。Cypher默认按完全匹配查节点名别名对不上就是空。更隐蔽的是同一实体被重复写入成多个节点查询按名字匹配时命中了其中一个但关系只挂在另一个节点上看起来就是“有空节点但没邻居”。解决入库时给每个实体维护一个aliases别名列表查询时先做实体链接把问法映射到规范名再拼Cypher。同时给图谱节点建唯一性约束和索引比如在Neo4j里用CREATE CONSTRAINT ON (p:Person) ASSERT p.name IS UNIQUE防止重复实体悄悄堆积。这步做完关系类问题的空结果率会明显降下来。5.3 top-k拉高后上下文过载生成反而变差现象top_k从5调到10以后答案没有更完整反而变得啰嗦甚至引用无关片段出现幻觉式输出。原因排在前面的片段确实相关后面的几个相似度已经很低属于噪声。喂给大模型的上下文越长注意力越分散离核心证据越远的内容越可能被模型当成依据。这本质上是RAG的上下文瓶颈召回数量不是越多越好证据精炼比数量重要。解决top_k提高的同时把每段提取长度压缩到150~200字。或者在组装证据前加一个重排环节用cross-encoder或LLM对候选片段二次打分只保留强相关的5条。第4章的build_evidence函数里加一个重排过滤参数返回前先跑一遍打分这条路径在检索质量不稳定时比调prompt见效快。5.4 本地部署大模型显存不足服务频繁OOM现象本地起7B或13B模型后启动成功但一问就崩日志里出现CUDA out of memory有时连embedding模型都一起被挤爆。原因上下文越长显存占用越高FastAPI进程同时加载着embedding模型、向量索引和生成模型显存叠加超出显卡容量。很多项目在Mac或小显存机器上部署时忽略了embedding模型也占显存以为只算大模型一份。解决生成模型用量化版本比如Q4_K_M的7B模型日常显存占用在5~6G量级比FP16小不少embedding模型优先用小于1G的bge-small并允许它跑CPU对话上下文长度限制在4096以内配合第4章的“只保留最近两轮历史”策略。如果还是超就把生成模型换成更小参数量别硬扛着调参。5.5 只调生成prompt不查检索证据质量现象prompt改了好几版答案还是不对最后把最终答案和检索证据一起打印出来才发现召回片段里根本没有答案。原因把效果问题全归因到大模型身上忽略了RAG系统的上游。检索没召回生成再强也巧妇难为无米之炊。图谱侧同理Cypher查出来的节点不对后面喂什么文本都白搭。解决把最终答案和它对应的证据一起打印出来逐条看证据是否覆盖答案。覆盖了问题在生成环节没覆盖问题在切分或嵌入模型。这是排查RAG问题最直接的方法别凭感觉盲调。我自己的习惯是每次改动前先把“问题→证据→答案”三元组存成一个日志文件跑完一轮翻日志比盯着终端输出猜原因高效得多。6. 进阶验收技巧用证据链路检查代替盲调prompt6.1 一张评估集、三次检查、一个回归脚本做到第5.5条时有一个切实可用的落地方法把“最终答案证据”同时吐出来逐条人工打分。准备20~30个覆盖文档问答和图谱问答的测试问题跑一次完整链路记录三个指标证据里是否包含答案召回覆盖、大模型是否依据证据回答生成忠实、图谱路径是否查到了正确关系图谱准确。这三个指标分开看才不会把问题归错方向。def evaluate_one(query: str, expected: str): route route_query(query) rag_chunks retrieve(query) if route ! graph else [] graph_rows query_graph(query) if route graph else [] evidence build_evidence(rag_chunks, graph_rows) answer ask_llm(你是企业内部知识库助手, f依据证据回答。\n{evidence}) return { query: query, route: route, have_evidence: expected_keywords_in(evidence, expected), answer: answer, evidence: evidence, }逻辑说明返回结果里同时保留答案和证据每条样本都能复盘。参数说明expected_keywords_in是一个简单的关键词包含判断从expected里抽两三个关键词去evidence里搜索用来快速检查证据是否覆盖答案点想严格一点就把answer和expected交给另一个LLM打分更贴近人对“答没答对”的感知。运行完评估先把“证据里没答案”的样本全部修掉再谈prompt优化。这个顺序很关键先保证上游能找到答案再让下游把答案组织准确。图谱侧的验证同理单独抽10个关系类问题直接看Cypher返回的节点对不对不经过大模型这样图谱建模的问题能隔离出来。下载这份资源后建议先按第4章的接口骨架跑通一个最小demo再逐步把第2章、第3章的资料换成自己的业务文档效果检验就靠这一章的评估脚本。我在这类项目上的血泪教训是RAG图谱问答平台的绝大多数翻车都发生在“证据链路”而不是“大模型”上。从那以后我每次调优都强制走一遍“问题→证据→答案”的三段式检查先把证据查清楚再决定要不要动prompt。希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑