简介基于RAG大模型技术的私有知识库智能问答系统源码与运行部署教程面向需要搭建本地知识库问答服务的开发者、研究人员及毕业设计使用者。系统覆盖大模型通用问答、私有知识库问答、互联网搜索问答、AI代理问答和推荐系统五大场景并配有完整RAG评估流水线与Docker容器化部署方案。资源包共467个文件约107.26MB以Python源码与编译产物为主145个py、96个pyc辅以Vue3前端js/css、Markdown/PDF文档及MySQL/Milvus数据库相关配置目录结构清晰便于按模块定位。已有382人浏览学习。通过源码与教程可掌握百万级语料预处理、用户权限控制、主流在线及开源大模型灵活集成以及向量数据库优化查询等完整落地能力还涵盖从环境搭建、代码解读到部署上线的完整指引适合作为毕业设计或企业私有知识库项目的直接参考。1. 一个能回答企业内部问题的 RAG 智能问答系统值不值得自己搭先想一个场景公司制度文件、设备维修手册、项目验收文档散落在共享盘和 Wiki 里新员工问“年假怎么算”老员工凭记忆答HR 得翻三个 PDF 才能给准话。公共大模型答不了这种问题因为训练语料里根本没有你公司的内部规则而且把制度全文粘给云端 API法务第一个不同意。这个标题给的就是一条本地部署的出路用 RAG 大模型技术搭私有知识库智能问答系统代码包加部署教程一起交付。它解决的是“如何让大模型在看不见的私有文档上给出有依据的回答”这件事。做法是把 PDF、Word、数据库记录先切块、向量化、存进向量库用户提问时先检索最相关的片段再把片段拼进提示词送给大模型生成答案。整个过程不训练、不微调硬件要求远低于从头跑一个大模型。适合谁一类是公司内部想做知识库问答但不想被 SaaS 服务绑定、不愿把文档传出去的工程师另一类是正在选型想先花一个周末把流程跑通再决定要不要投生产的人。下面从原理、部署、数据准备、踩坑到质量优化按我实际做过的方案一步一步讲。2. 把 RAG 拆开看索引、召回、生成这三段决定了问答质量2.1 索引阶段文档切块与向量化为什么是第一个瓶颈RAG 的第一步不是“把文档喂给大模型”而是把文档拆成适合检索的片段。大模型有上下文长度限制你不能把一本 200 页的手册整个塞进提示词就算塞得下召回的精度也会被无关内容稀释。常见的做法是先把原始文档转成纯文本再按固定长度或语义边界切成 chunk。这里有两个直接影响后来问答效果的参数chunk_size每个片段包含多少字符中文场景我一般取 300600 字。太大召回命中后提示词里掺太多噪音太小片段语义不完整大模型容易答出片面的结论。chunk_overlap相邻片段重叠的字符数取 50100。目的是避免一个完整句意恰好被切在边界上。如果一句话被切掉一半embedding 向量和检索结果都会失真。切块完成之后每个片段要过一遍 embedding 模型转成向量。这个环节决定了“语义相似”怎么衡量。你要选一个适配自己语言的模型——中文私有知识库最好用中文语料预训练过的 embedding 模型比如 bge-large-zh而不是把英文模型直接拿来用。原因是不同语言的向量空间差异很大英文模型对中文长句的语义区分度往往不够后面检索阶段就会频繁召回不相关内容。2.2 召回阶段TopK 与相似度阈值如何拿到“对的段落”用户提问后系统把 query 也转成向量到向量库里做相似度检索。召回质量强依赖两件事embedding 模型的质量和 TopK 参数。TopK 是返回给生成阶段的片段数量我一般先设 5再根据效果递增观察。只靠 TopK 还不够。向量库返回的是“距离最近的 K 条”但距离近不等于“刚好能回答这个问题”。比如用户问“设备报错 E201 怎么办”召回回来三段一段讲 E201 的代码含义一段讲 E202还有一段是维护周期表。这种时候如果全塞给大模型它可能把 E201 和 E202 混在一起回答。我一般会在召回后加一道过滤对每条召回结果计算相似度得分低于阈值的直接丢弃再按文档来源分组同一份文档最多保留 2 个片段避免某一个长文档霸占整个上下文窗口。召回阶段宁可少不可杂。2.3 生成阶段提示词里“引用原文”才是私有化问答的底气生成阶段把用户问题和召回的片段拼进提示词交给大模型。这里最容易忽略的是提示词结构。直接说“根据以下资料回答问题”大模型偶尔会自由发挥更好的做法是注明“只能依据提供的片段回答如果片段中没有答案就回答‘知识库中未找到相关信息’”从约束上把幻觉压下去。我常用的提示词骨架是先给角色设定再列片段最后是问题。片段之间用清楚的分隔符隔开并在每条前面标注来源这样生成时不只回答了问题还能在末尾引用“根据《设备维保手册》第 3 章”。对企业用户来说有出处的答案比准确但无从核验的答案更可信。这一层还涉及新热词里提到的“RAG 框架”选型。开源的框架有 LangChain、LlamaIndex也有更轻量的自研 pipeline。小团队起步阶段可以先用框架快速跑通但要注意框架封装得越深后面排查越难。我自己的习惯是先用手写一百行代码跑通原理再用框架加固这样出问题时知道是哪一环在作怪。3. 解压源码到跑通第一个问答环境、配置与启动命令3.1 环境准备Python 版本、虚拟环境与依赖安装拿到源码压缩包第一步不是急着看代码而是先把可运行环境搭起来。这个项目类的工程一般会用 Python 3.10 作为基线因为大部分向量库 SDK、大模型推理库都对 3.10 支持最稳。我建议用 conda 建独立环境避免和本机其它 Python 项目打架。# 创建独立环境Python 版本锁定 3.10 conda create -n rag_qa python3.10 -y conda activate rag_qa # 进入解压后的源码目录安装依赖 cd rag_qa_project pip install -r requirements.txt解释一下上面这两个命令的意图。conda 隔离环境是为了让你后续折腾依赖版本时不至于把系统 Python 搞坏。pip install 的 requirements.txt 通常包含向量库客户端、embedding 模型加载库和大模型推理框架。如果安装过程很慢或者个别包编译失败常见做法是换国内镜像源而不是逐个包硬等。3.2 最小配置模型路径与知识库路径要先对齐启动服务前要确认两份路径一个是 embedding 模型和 LLM 模型的本地路径一个是待入库知识库的路径。私有化部署的开源 LLM 一般用量化版本减少显存占用下载后放在一个单独的 models 目录下配置项指向这个目录即可。# config.yaml 中的关键配置按本地实际路径修改 embedding_model: /data/models/bge-large-zh llm_model: /data/models/qwen2.5-7b-instruct llm_quantization: 4bit vector_store: chromadb vector_store_path: ./data/vector_store chunk_size: 512 chunk_overlap: 50 top_k: 5 similarity_threshold: 0.45这组配置里embedding_model 指向的是你下载好的 embedding 模型目录不是模型名称llm_model 同理。llm_quantization 设成 4bit是想把 7B 模型的显存占用压到 6GB 上下普通单卡 GPU 能跑得动。vector_store 选 chromadb 是为了本地单机快速起步不用额外部署一套数据库服务。3.3 启动服务先入库再问答两步缺一不可依赖装好、配置改完接下来要分两步行进先把文导入向量库再启动问答接口。# 第一步把 knowledge_base 目录下的文档切块、向量化并写入向量库 python ingest.py --data_dir ./knowledge_base --config config.yaml # 第二步启动问答 API 服务监听 8000 端口 python server.py --port 8000 --config config.yamlingest.py 执行的是上面说的索引流程读取文档、清洗、切块、embedding、存入向量库。这一步会打印每个文档切了多少块、向量库目前总共多少条记录看到这些日志就说明入库成功。server.py 启动后可以用命令行做一个最小验证# 用 curl 发一条真实业务问题确认服务有响应 curl -X POST http://localhost:8000/api/qa \ -H Content-Type: application/json \ -d {question: 员工年假最长能休多少天}如果返回的 JSON 里带 answer 字段和参考来源整个链路就通上了。这一步能把“源码能不能跑”和“问答准不准”分开看先确认能跑再去调质量。很多人在这一步就急着调 prompt结果后面问什么都怪怪的根源其实是环境没对齐。4. 把企业文档变成可检索的语料切分策略、向量模型与知识库入库4.1 文档清洗扫描件、页眉页脚和表格是三类拦路虎企业文档和公开语料不一样PDF 里大量是扫描件、带公司 Logo 的页眉页脚、跨页表格。前端绕开这些知识库再大都是虚的。扫描件必须先过 OCR。常见做法是用 PaddleOCR 把图片型 PDF 转成带坐标信息的文本表格类的规则是转成 Markdown 表格再入库因为纯文本抽取会把一行行的单元格拆散后续问答对“第三列是什么”完全没有上下文。页眉页脚要去掉否则每一段向量里都混着公司名称和页码检索时这些高频信息会干扰语义相似度。我的处理顺序是先 PDF 转文本再用正则删掉页眉页脚和页码最后把文档按一级、二级标题切开作为后面 relation 的语义边界。4.2 chunk 切分策略按标题切还是按固定长度切切分策略决定了召回的最小语义单元。见下方一个按实际中文文档写的切分函数def split_document(text: str, chunk_size: int 512, chunk_overlap: int 50) - list[str]: # 优先按一级/二级标题切分标题是天然的语义边界 # 找不到清晰标题时再按段落粒度递归切 sections re.split(r(?m)^#{1,2}\s.*?$, text) chunks [] for section in sections: if len(section) chunk_size: chunks.append(section.strip()) continue # 超过 chunk_size 的长段落用带重叠的滑动窗口兜底 start 0 while start len(section): end start chunk_size chunks.append(section[start:end].strip()) if end len(section): break start end - chunk_overlap return chunks这段代码的核心是“先语义后长度”段落能整体放下就直接作为一个 chunk放不下才用滑动窗口切并让两个 chunk 之间保留 chunk_overlap 个字符的重叠。固定长度硬切是最省事的兜底但不该是第一选择。硬切的后果是同一句话被拆到两个 chunk 里检索时哪一半都不完整生成答案就很容易断章取义。参数上chunk_size 对应的是“字符数”而不是 token 数中文一个字符约等于 0.61 个 token。512 字符大约对应 300460 个 token在 4K 上下文的模型上放得下 5 段召回内容不会超长。4.3 embedding 选型中文场景里 bge 和 m3e 怎么选选 embedding 模型的本质是在“检索精度”和“资源占用”之间做权衡。我把常用组合整理成一张对比表模型维度中文效果显存占用适用场景bge-large-zh1024较好较高生产环境硬件充裕bge-base-zh768良好中等单卡起步首选m3e-base768良好中等中文语义匹配效果不错text2vec-large-chinese1024中等中等对领域术语区分度一般维度高不等于效果好但维度低往往会在细粒度语义上吃亏。企业知识库里大量相似文档比如同一份制度的 V1 和 V2 版本模型如果区分不出“旧版作废”和“新版执行”的语义差异检索出来的答案就可能是过期规定。我一般先拿 20 条真实业务问题跑一遍检索看召回命中率再决定用哪一档模型。embedding 模型装好后入库脚本要做一次全量测试打印几条文本的向量相似度确认同一个意思的句子得分高、意思相反的句子得分低。这一步只花十分钟能省掉后面几天排查“为什么召回都是乱的”的功夫。5. 部署与问答最常见的五个坑现象、原因、解决与排查5.1 坑问什么都答非所问答案和问题不在一个频道现象知识库明明导入了不少文档但问“报销流程是什么”答的是“报销单需要填写部门名称”这类看似沾边、实则没有完整流程的碎片。原因这个现象九成是 chunk 切太小或文档结构没被利用。每个片段只截到流程里的一小步检索召回时根本没有“完整的流程”这个语义单元。解决先把 chunk_size 调到 512 以上并开启按标题切的逻辑让每个流程章节成为一个整体。还要检查是不是整篇文档只有一个大段落那样无论怎么切都切不出结构需要先在数据清洗阶段把 Markdown 标题补齐。5.2 坑显卡显存溢出或者推理速度慢到不想用现象启动服务后第一条问答要等 40 秒多问几句直接显存溢出。原因embedding 模型和大语言模型同时占显存又没有做量化上下文过长时KV Cache 也把所有显存吃光。解决先确认 LLM 用 4bit 量化加载embedding 模型如果只是入库时用可以放到 CPU 上跑入库完成后再退出。启动脚本里把 embedding 和 LLM 分两个进程跑能显著降低峰值显存。对 7B 量化模型我一般要求至少 8GB 显存才谈体验。5.3 坑召回结果漂移关键词明明存在却召不到现象用户问“E201 故障代码”向量检索召回的全是跟“故障”有关的泛泛内容E201 这个关键信息反而没排到前面。原因纯向量检索对精确词和低频词不敏感。E201 这种词在语义空间里没有太多可对比的近邻embedding 模型无法理解“E201”和“201 号错误”的字符级关联。解决在向量检索之外叠加 BM25 关键词检索两路结果做融合。BM25 能兜住精确匹配向量检索能理解同义改写两条腿走路。开源实现里 rank_bm25 可以快速接入融合分数按 0.3 的权重给关键词路。5.4 坑知识库更新了答案还是旧的现象把制度 V2 的文档重新导入后问相关问题回答里引用的还是 V1 的旧条款。原因向量库入库时没有做文档级版本管理。新文档的 chunk 插入后旧 chunk 还躺在库里两版内容同时被召回模型选了它认为更顺的那一段。解决入库时每个 chunk 记录 source_id 和 doc_version。更新文档时先按 source_id 删除旧 chunk再插入新 chunk。也可以在配置里打开 upsert 模式但对同一批文本内容删除重建比 upsert 干净得多。5.5 坑部署成功后问答接口经常返回超长无意义回复现象问一个简单问题模型回了一大段绕圈子的解释甚至开始编造。原因上下文里召回的片段本身就杂比如 TopK5 里有三段讲同一个话题但结论不一致模型试图把矛盾信息“圆”成一个答案就产生了幻觉。解决在提示词里强制加“如果片段之间矛盾明确说明并优先采用最新版本的片段”同时把 TopK 降到 3看答案质量是否提升。如果降了反而不准说明是检索问题而非生成问题回到 5.3 加关键词检索。6. 把问答从“能答”提到“答得准”重排、混合检索与质量评估聊到这一步你已经能跑通一个像样的私有问答系统。但要拿去给业务部门用还差一道关键工序让答案持续保持高准确率而不是时灵时不灵。先说重排。向量召回 TopK 往往前五条里有两条是干扰项。常见的做法是加一层 reranker用交叉编码器把每条“问题 候选片段”拼在一起输出一个精细的匹配分数再按这个分数重新排列只取前三条进生成阶段。模型可以用 bge-reranker-base它对中文长句的匹配区分度比向量相似度好得多。代价是每条候选都要单独过一遍模型影响响应时间所以通常只在 TopK 为 10 到 20 条时做一次重排而不是对全库做。然后是混合检索。纯向量召回在精确词上不稳定我在第 5 章踩过这个坑之后现在的方案是所有 query 同时跑 BM25 关键词召回和向量召回按分数加权合并再从合并结果里做重排。这样用户用口语问、用口号问、用简称问都能被某种路径接住。多轮对话也是要去处理的问题。用户第二句问“那它什么时候执行”如果你只拿这句话去检索知识库根本识别不了“它”指什么。我一般不直接把整段历史拼进提示词而是先做一次 query 改写让大模型把“当前问题 最近两轮对话”压缩成一句完整的检索语句再用改写后的语句去检索。这一步能显著提高多轮场景的召回质量。最后是验证方法也是我建议你先搭再做功能的事准备 3050 条真实业务问答对跑一遍系统逐条检查“召回的片段是否相关”“答案是否被原文支持”。这个评估集要留给每一次调整后回归用——改 chunk_size、换 embedding、加 reranker都要拿同一份评估集跑对比前后准确率。我自己就是从“凭感觉调参”变成“用评估集回归”之后系统才真正稳定下来。这套方式不好看的点在于前期要花时间搭评估集看起来不如直接调 prompt 出效果快。但跑过两个迭代你就知道没有评估集任何改动都是在蒙着调。现在我做知识库问答的标准流程是先建评估集再跑基线然后每一次优化都拿基线来对比。这个习惯救了我好几次希望帮到你。本文还有配套的精品资源点击获取