资讯详情

资讯详情

基于RAG的校园私域知识库问答系统:从数据清洗到部署的完整实践

这学期我们学校的2023级创新实训三终于结项了我们小组做的是一个校园私域知识库问答系统。实训周期一共12周从需求调研、技术选型到数据清洗、模型接入再到最后的部署演示和答辩整个过程走下来比预想中曲折不少但收获也确实很大。这篇就当是给下一届学弟学妹和正在带创新实训的老师们一份踩坑记录把我们从零到一搭建这套系统的思路、参数、代码片段和真实遇到的问题全部交代清楚。先说一句网上到处是各种教程但到了自己写代码、跑数据、调接口的时候你会发现坑都不在教程里而在文档没写的那几行注释里。这篇文章会把我们实操时的方案选择、参数调优、现场排错都展开讲适合想用自然语言问答的方式做校园信息查询、企业内部知识库、甚至是个人文档助手的开发团队拿来参考。1. 项目整体设计与思路拆解1.1 为什么选这个方向创新实训三的主题没有限死但学校要求必须贴近实际场景不能为了做项目而做项目。我们组前期调研了三个方向校园二手交易小程序、食堂菜品推荐系统、私域知识库问答系统。前两个方向开发难度一般但创新点不够答辩时容易被问到为什么不用现成产品。知识库问答这个方向踩中了当时大模型应用的热点而且校园里确实有这样的痛点学生想查培养方案、选课规则、奖学金申请条件、社团活动场地借用流程通常需要翻好几个网站、问好几个群信息分散且更新不及时。我们想做的是一个统一入口学生输入一句自然语言系统直接给出准确答案并且附带信息来源方便追溯。这个方向既有人工智能相关的算法内容又有工程化的数据清洗和系统开发任务小组分工时很容易拉开梯度也让每个人都有明确的技术成长点。实训结束之后这个系统还能继续被学校信息化部门拿去完善不是那种结项就报废的项目。1.2 技术方案选型为什么是RAG而不是微调选题确定后第一个争论焦点就是到底让模型直接回答问题还是把文档喂给微调后的模型我们最初请教了某位带过多届实训的老师他的建议很直接12周时间做一个垂直领域微调数据量不够效果不稳定而且后续知识更新非常麻烦。校园政策每年都会调整如果所有知识都融在模型参数里改一个奖学金的申请条件就得重新训练。这不现实。最后我们选的是RAGRetrieval Augmented Generation检索增强生成整体思路分三步把校园里有参考价值的文档统一采集下来清洗干净后切片再做向量化存进向量数据库。用户提问时先用检索模块把最相关的文档片段捞出来。把问题加上检索到的上下文一起发给大模型接口由模型基于这些上下文组织自然语言回答。这种方案最大的优点是知识更新只需要重新处理新增文档不用动模型本身。而且用户在系统里能看到参考依据来自哪一份文件这对校园场景尤其重要因为学生看到答案的时候会本能地质疑你凭什么这么说有了引用来源说服力会强很多。提示如果你们的实训项目也是做问答类系统应对数据更新快的场景优先考虑RAG如果数据非常固定且你手里有几千条高质量问答对再考虑微调也不迟。1.3 系统总体架构与核心组件整个系统我们拆成四个模块各模块之间通过API通信前端Vue 3 Element Plus部署在Nginx上提供问答对话框、来源展示、历史记录查看。后端Python FastAPI负责接收前端请求、调用检索模块、调用模型接口、返回结果并记录日志。知识库处理包括文档采集脚本、清洗脚本、切片工具、向量化任务是一套离线Pipeline。向量数据库一开始用Milvus后来因为Docker部署太占内存换成了开源的Qdrant实测在16GB内存的服务器上跑200W条向量依然没有明显卡顿。当时还对比了MongoDB加全文索引的方案但向量召回效果差距太大直接放弃。Milvus功能强但配套组件太多实训这种多人协作且服务器配置有限的场景Qdrant安装简单、API文档友好更适合我们。这个选型在后面部署阶段省了不少事。一个容易被忽略的细节是系统必须支持不知道就坦白说不知道。我们的对话策略里专门加了一个低置信度判定如果检索出的最大相关分数低于某个阈值就直接回复知识库中暂无相关信息而不是强行生成一段胡编的内容。这个细节在答辩时很加分评委一看就知道团队处理过模型幻觉问题。2. 核心细节解析与实操要点2.1 知识库构建数据清洗是第一生产力我们第一周收集文档来源包括学校官网的通知公告、教务系统里的培养方案、学生工作部的资助政策、后勤处的办事指南以及各学院公众号里的常见问答汇总。最终攒了大概2.3GB的原始材料包含PDF、Word、Excel、HTML和纯文本五种格式。如果直接用这些原始文件去做检索效果一定惨不忍睹因为很多PDF是从OA系统打的里面有大量页眉页脚、重复水印和表格错位。清洗Pipeline我们做了这么几步格式识别与解析PDF用PyMuPDF提取文本Word用docx库HTML用BeautifulSoup去除标签和脚本。去噪去掉连续空格、全角半角混乱、页眉页脚中的重复文字、日期和页码。规则去重很多文件在官网里存在多个版本比如某个奖学金的评选通知去年一份、今年一份。我们按文件标题和发布时间做了MD5比对相同正文只保留最新一版。表格还原一些政策类文件里嵌了Excel表格单用文本提取会把表格内容拆得七零八落。这里我们写了一个小工具把表格区域单独截出来按行转成自然语言描述例如国家奖学金申请条件成绩排名前10%这样。清洗前后的文本长度差距很大有的PDF看起来有50页清洗后有效内容只有8页其他全是教务系统导出的水印和空行。数据质量决定了后面一切环节的上限这点是我们整个项目最深刻的教训之一。注意一定不要在清洗阶段动手删掉包含敏感信息的字段。学生名单、身份证号、联系电话这些内容要单独脱敏后再入库。我们当时专门写了一个正则加关键词的双层脱敏脚本防止隐私数据被检索出来后进入模型上下文。2.2 文档切片与向量化参数不是拍脑袋定的RAG系统里切片策略直接影响召回效果。切片太大模型接收的上下文容易超长检索返回的片段会混入无关信息切片太小语义不完整一句提问对应的上下文经常被截断。我们试过三种配置切片方式参数召回准确率上下文冗余度固定长度256字符无重叠62%低固定长度512字符重叠5074%中按标题层级切分章节为粒度83%高最终我们用的是第三种先识别文档里的标题结构按章节切分同时在章节内部如果内容超过800字再按段落边界继续切保证每个切片至少包含一个完整的语义单元。这样做的本质是先逻辑分段再自动切细比单纯按字符切要可靠得多。向量化模型我们用的是国产开源的中文向量模型维度1024。批量向量化的速度很快2.3GB的数据切完大约有18万个切片两台机器跑一个晚上全部向量化完成。需要提醒的是向量化之后一定要保存一份切片ID与原文路径的映射表否则检索召回后你都不知道该把用户往那个页面上引导。2.3 检索模块召回加排序两层逻辑不能省检索我们实现了两层架构第一层用Qdrant做向量召回取Top100候选切片第二层用重排模型Reranker对候选集合做精排取Top5作为最终上下文。很多简化版的RAG系统只做第一层直接把Top5给模型当上下文实践下来的问题是向量召回的第二三名经常是一字不差但语义完全不一样的内容比如校级奖学金申请和国家奖学金申请边界容易被模糊。重排层的选型先用一个8GB显存的GPU跑开源的中文Reranker模型单条Query重排100个片段耗时约200毫秒完全在可接受范围。如果你们没有GPU也可以直接用向量召回时的距离分数排序效果会掉一些但在数据量小比如几万切片的情况下差距不算特别大。检索时的Query改写也很关键。校园场景里很多问题带着口语化的表达比如我想问一下奖学金什么时候评和奖学金评选时间其实检索意图差不多但直接拿原句去向量化效果往往不如先改写一遍。我们接了一个大模型接口专门做Query改写把口语自动归一成标准检索问句实测召回准确率提升了大概8个百分点。2.4 大模型接口接入与对话流程模型接口我们初期对比了好几家最终选定的是国内某开放平台发布的对话模型API。接入逻辑非常简单就是POST一个包含system、user、assistant三段消息的JSON过去但真正的细节在于Prompt设计system消息明确告知模型你是校园信息助手只根据提供的知识片段回答用户问题若片段中没有相关内容请明确告知用户不知道不要编造建议。user消息不直接把检索结果倒进去而是用模板包一层把来源文件名标注出来让模型知道每条内容的出处。为了防止模型输出过于教条我们允许它在回答开头先给出结论再补充细节比如可以申请申请条件是……。温度参数我们从默认的0.7调到了0.2因为校园政策类问答讲究准确不需要太发散。如果你们做的是聊天机器人场景温度可以调大但做知识库问答温度越低越稳定。另一个很坑的地方是上下文长度。我们的向量切片平均是400字Top5就是2000字加上问题和对话历史很容易撑爆模型的上下文窗口。我们在后端加了一个历史消息压缩逻辑当前轮对话保留完整历史超过5轮之后把更早的历史用一个大模型一次性压缩成摘要再拼接。这个模块写起来不复杂但测试时发现很多线上的上下文超限报错都是因为它没生效而导致的。3. 实操过程与核心环节实现3.1 两周一迭代从Demo到可演示版本这个实训是跨学期的实际编码时间并不连续我们按两周一迭代来推进第1到2周完成数据调研和采集做出一个包含500条问题的问答需求清单。第3到4周搭好后端API骨架实现最基本的向量召回模型调用回显效果还是个黑框框。第5到6周完成后端调优和前端基础界面对话框支持流式输出和来源展示。第7到8周清洗第二批高优先级文档扩充知识库加入Query改写和Reranker。第9到10周部署到服务器配置HTTPS做并发压测。第11到12周准备演示环境、整理代码文档、模拟答辩问答。这个节奏看起来很常规但有一个关键经验第一轮Demo一定要窄而深。也就是哪怕只支持20个问题也要把完整的检索链路跑通而不是先把界面做好、再慢慢加知识库。我们组有个中途差点把前端做成了重头戏还好及时发现并纠正了方向。搞实训项目最忌讳的就是把时间全花在页面上结果核心链路数据质量不行答辩一演示就露馅。3.2 后端核心代码与参数细节后端我们用了FastAPI原因是异步支持好、写起来快、Swagger文档自动生成团队内部分工也方便。下面这段是检索加生成主链路的简化版代码参数已经改成我们最终调试好的配置from fastapi import FastAPI, HTTPException from qdrant_client import QdrantClient from sentence_transformers import SentenceTransformer import openai app FastAPI() client QdrantClient(hostlocalhost, port6333) encoder SentenceTransformer(/models/embedding_model) openai.api_key your_api_key TOP_K_CANDIDATE 100 TOP_K_FINAL 5 CONFIDENCE_THRESHOLD 0.35 app.post(/chat) async def chat(payload: dict): query payload[query] rewritten rewrite_query(query) # 调用大模型接口做Query改写 query_vector encoder.encode(rewritten).tolist() hits client.search( collection_namecampus_kb, query_vectorquery_vector, limitTOP_K_CANDIDATE ) if not hits or hits[0].score CONFIDENCE_THRESHOLD: return {answer: 知识库中暂无相关信息请换个问法或联系辅导员。, citations: []} top_hits rerank(rewritten, hits[:TOP_K_CANDIDATE])[:TOP_K_FINAL] context build_context(top_hits) answer call_llm(query, context) return {answer: answer, citations: [h.payload[source] for h in top_hits]}这里重点解释几个我们踩过坑的参数CONFIDENCE_THRESHOLD 0.35Qdrant返回的score不是0到1的概率在不同距离函数下数值范围不一样。我们最终用余弦距离实测这个阈值区分度最好。你们接入时不要直接照抄要拿一批真实问题去跑一遍看看答不上来时score普遍落在什么范围再反向订阈值。TOP_K_CANDIDATE 100候选数量不是越多越好。100个候选配合Reranker在18万切片上性能还可以如果候选太少Reranker优势体现不出来如果候选太多重排耗时过高。build_context内部会把每个片段的来源文件名包在引用标记里比如来源2024年本科生奖学金评审办法.docx。这样模型回答时能自动带上出处。3.3 前端联调与流式输出前端我们找的是Vue 3生态里现成组件库没做特别复杂的界面。整个页面分成三大块中间是对话列表右侧是当前回答的引用来源卡片顶部是知识库覆盖范围的说明文字。实际做下来最花时间的不是界面布局而是流式输出和后端日志的配合。大模型API本身支持流式返回我们后端用SSEServer-Sent Events把增量推给前端。这个过程中遇到过一个经典问题页面流式输出时如果网络抖动消息会奇怪地中断但后端日志显示任务其实还挂着。排查了很久才发现是前端在SSE连接断开后没有重试机制最后加了一个简单的指数退避重连才稳定。另一个前后端联调的经验是接口返回结构一定要在设计阶段定死。我们早期接口里的citations字段一会儿是列表、一会儿是字符串数组前端对应解析逻辑反复改浪费了不少时间。后来直接把JSON Schema固定下来前端Mock数据先跑通后端再按契约实现联调效率立刻提升。3.4 部署与压测服务器配置是4核CPU、16GB内存、一块Tesla T4显卡。整个部署用Docker Compose编排了四个容器前端Nginx、后端API、Qdrant、一个向量化微服务。最开始我们尝试过把Python环境和模型加载全都打包进一个镜像结果镜像体积超过8GB每次启动要5分钟改起来痛不欲生。后来拆成两个镜像一个负责接口服务载入重排模型另一个负责文本向量化中间用HTTP通信虽然是多了一步网络请求但开发迭代速度明显提升了。压测我们用的Locust模拟了50个并发用户每个用户平均间隔3秒发一轮问答。主要测了两个指标请求成功率要求99%以上。回答总耗时P95需要控制在5秒以内。实测下来瓶颈不在Qdrant也不在大模型API而是Reranker的GPU推理。50并发时单条请求的P95耗时能达到3.4秒其中Reranker占了约1.6秒。我们临时加了一个简单的缓存如果Query改写后的文本和最近10分钟内某条请求完全相同直接复用上一次的检索结果。这个缓存命中率大约25%P95降到了2.8秒能接受了。注意部署阶段一定要把API密钥和数据库密码写进环境变量文件不要直接硬编码在源码里。答辩结束时有个小组做代码展示不小心把第三方API密钥打到了大屏幕上当场被提醒这种教训比较尴尬。4. 常见问题与排查技巧实录整个开发过程中我们记录了一本问题台账这里挑几个最有代表性的分享出来基本都符合网上搜不到、只能现场分析的特点。4.1 向量召回效果差的问题排查现象用户问体测成绩在哪查召回结果前几名全是关于体质健康测试免测申请的文件相关但答非所问。排查思路先看Query改写结果发现改写后的句子太长模型把在哪查这种字眼扩充成了一大段描述反而稀释了关键词。用改写后的Query做高维向量可视化发现它和目标的距离分布非常扁平说明向量化特征不够集中。我们尝试去掉Query改写步骤直接拿原始问题召回效果反而好一些虽然也会漏召回但至少不明显偏。结论Query改写不是万能药。如果你们的问题本来就是规范化表达直接召回比改写后再召回更稳。后来我在系统里加了一个规则只有当原问句包含明显口语词比如咋啥我想问时才走改写其他情况直接跳过。4.2 模型输出格式不稳定现象系统偶尔会把知识片段里的表格描述原封不动地倒出来一段回答里混杂着大量换行和编号用户看得很累。原因分析知识库切片里保留了一些结构化文本比如一、二、三这种序号模型不知道该把它们压缩成自然语言段落。处理方式我们在后续迭代里对切片内容做了轻量级预处理把一、1.这些编号枚举改成1这样的行文格式同时在Prompt里加了一句回答时请用通顺的段落重新组织知识片段中的内容不要直接复制。加了这两件事之后输出质量肉眼可见提升。4.3 数据库挂了容器重启就失联现象有一天拷贝服务器文件时误删了Qdrant的持久化目录整个容器重启之后索引全部丢失18万条向量需要重新向量化。排查经历这是一个比较低级的失误但也暴露了我们的流程问题没有给Qdrant数据目录做定期备份。最后我们写了一个定时任务每天凌晨把/qdrant/storage目录打包到另一块磁盘上保留最近7天版本。这个工作在实训中很快但万一不备份重新向量化的成本是几个小时后面大家就都养成习惯了。4.4 常见问题速查表问题现象可能原因解决方案检索返回内容明显不相关Query改写过于发散限制改写触发条件或关闭改写模型回答长度忽长忽短temperature偏高、Prompt未约束温度降到0.2并在system中加入长度建议上下文超限报错历史消息压缩未生效检查压缩任务触发条件增加单轮长度上限首次请求响应很慢模型加载需要时间启动预热脚本注释掉懒加载逻辑部分PDF文字提取乱码OCR字体映射问题改用PyMuPDF的rawdict模式流式输出中断不恢复前端SSE未重连增加指数退避重连机制这些坑都不深但如果不记录几乎一定会改一次代码又重新踩一遍。强烈建议每个实训小组从一开始就维护一本共享的问题台账。5. 项目管理与实训成果落地5.1 团队协作与角色分配我们小组5个人按能力分成三条线后端兼算法2人负责知识库Pipeline、检索链路、大模型接入。前端兼交互1人负责Vue界面、流式展示、移动端适配。数据兼测试1人负责文档采集清洗、脱敏、标注测试集。组长兼部署1人负责服务器、Docker编排、进度协调和答辩材料。这个分工最合理的地方在于数据和算法边界清晰但互相依赖避免了两头都做导致沟通混乱。每周五下午固定开一次组会会上只讨论三件事这周目标达成了没有、下周优先级是什么、有没有需要其他成员提前准备的东西。这套机制看起来很简单但对比隔壁组平时各干各的最后三周极限整合的状态我们的压力分布均匀太多了。5.2 文档沉淀比写代码更重要实训答辩的时候评委老师最常问的问题并不是系统演示效果怎么样而是你们这个项目解决的核心问题是什么技术上有什么难点数据怎么来。这就需要文档能讲清楚完整的故事。我们准备了四份材料需求文档列出100条真实用户问题标注优先级和覆盖范围。技术方案包括架构图、选型对比、Prompt模板、参数说明。测试报告包含验证集上的召回率、回答准确率、人工评测样本。操作手册从拉取代码到启动服务覆盖所有环境依赖。这些材料不仅仅是为了应对答辩实际操作中也会发现写完技术方案之后你自己对项目结构的理解会提升一大截很多之前模糊的设计决策会变得相当笃定。5.3 给下一届学弟学妹的几点建议最后说几条基于我们实际经历的经验选项目时先问自己一句假设半年后回来看这个项目我还会觉得它有意思吗如果觉得没意思大概率做到中途会失去动力。数据先准备好再做算法。我们第一版Demo效果不佳90%的原因都是原始文档脏数据太多不是在算法上缺东西。尽早部署到一台真实服务器上。很多问题只有在公网能访问的环境里才会浮现比如证书记载的HTTPS配置、接口并发限制、Nginx超时设置。记录一切调试过程。这个实训项目不能光靠代码仓库里的commit记录更要把参数调整的前因后果写成Markdown笔记后面写结题报告时会感谢当时的自己。答辩前至少完整演示五遍每次都用同一个账号、同一个浏览器环境防止现场暴露接口密钥或者缓存失效导致的问题。我在这个项目里最大的体会是创新实训的价值不在于做出一个多厉害的系统而在于逼着你在一个有限周期里走完发现问题—设计方案—动手实现—验证效果—总结复盘的完整闭环。这个闭环在以后写代码或者带团队时都能受用。建议下一届实践时把每一天的思考和调整都沉淀成文档这样实训结束时你手里就不只是一个系统还有一份属于自己的成长记录。
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →