ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

私有化RAG知识库搭建实战:架构选型与踩坑复盘

私有化RAG知识库搭建实战:架构选型与踩坑复盘 先说结论如果你也想在公司内部搞一套私有化 RAG 知识库我可以很负责任地告诉你真正难的不是把模型跑起来而是把文档处理好、把检索效果调到能用、把权限和运维这件脏活干完。我用两周时间从零搭完这套系统中间踩的坑比教程里写的多得多下面把完整架构和踩坑过程整理出来给正在评估或已经动手的团队一个参考。我自己当时的情况是这样公司有大量售前方案、交付文档、内部技术 wiki、验收报告散落在共享盘、语雀和 GitLab 里大家查资料基本靠问人和猜文件名。我们想做一个统一的内部知识库问答入口同时因为文档里有客户信息、项目报价、内网拓扑等敏感内容数据绝对不能走公网 API所以私有化部署从一开始就是硬约束不是可选项。这套系统最后长什么样、为什么这么选型、每一层是怎么落地的、哪些问题最折磨人我会按下面几条线展开。1. 两周前的现场一套散装知识库与三个现实的约束1.1 痛点能搜到文件名搜不到答案先说最原始的痛点。我们内部之前的检索基本靠语雀全文搜索和共享盘文件名搜索结果让人一言难尽输入报销标准出来一堆语雀文档标题里带报销的文章没有一篇直接告诉你住宿标准是每天 400 元以内需要附发票。你得挨个点开文档再用 CtrlF 找。更麻烦的是很多知识长在 Excel 表格、PPT 和扫描版 PDF 里。共享盘上几万份文件真正有价值的信息被埋在段落、页脚和表格交叉处。部门老员工离职后他负责的那块业务知识就变成了去过的人才知道在哪里。所以初始目标很朴素让员工在 Web 页面或企业微信机器人里用一句大白话问问题系统直接给出带出处的答案。如果答案不够准确至少要把最相关的几份文档翻出来让人少翻十次目录。1.2 三个约束决定了私有化路线和上级对齐后需求变成三条硬约束这三条也直接决定了后面所有技术选型数据不出内网。客户名录、项目成本、内网拓扑这类信息如果传出去是重大事故所以任何云端 API、在线知识库产品一律排除。必须支持权限隔离。不同部门只能检索自己授权范围内的文档不能出现销售问一句华东区客户情况把售前部门内部讨论稿也捞出来的情况。实施周期短。业务方给的时间是两周内出可用 Demo第二周要接入真实业务数据并让试用部门开始提问题。实际上这三条约束很典型。很多团队一上来就纠结哪个大模型聪明但真正卡住项目进度的永远是数据接入、权限过滤和效果调优这种看起来不性感的工作。1.3 目标定义与验收口径两周时间不可能追求完美所以我们把验收口径定为三个能能回答 70% 的常见高频问题答案能溯源到具体文档。能在一个月后支持多个部门并行使用互不越权。能让业务方在提问后 10 秒内拿到结果而不是转圈圈或者超时。这个口径在后面救了我一命。因为一旦脱离可用去追完美两周绝对会失控。建议任何团队在开工前先把什么叫做完了定义清楚否则后面每一步都会争执。2. 技术选型每一个决定背后都有明确理由2.1 基础模型Qwen 系列为主Llama 的局限私有化部署的第一步是选底座模型。当时对比了几个方向Llama 系列、Qwen 系列以及一些做微调的垂直小模型。先说结论国内企业做中文知识库问答我优先推荐 Qwen 系列。原因很实际——我们试了 Llama 3.1 8B 的中文效果它在中文长文本理解、专有名词和多轮表达上和 Qwen 有明显差距。中文场景下报销和费用报销这种同义词Llama 的向量空间表达不如 Qwen 稳定生成时也更容易出现语义偏移。团队的语料全是中文没必要在这个维度硬刚。最合适的是 Qwen2.5 14B 量化版。两张 24G 显存的卡就能跑起来配合 vLLM 做推理单路生成速度可以到每秒钟 30~40 token 的水平。如果还有资源上 32B 效果会更好但 14B 在能部署的体面和答得够好之间最平衡。另外别忽视商用协议问题。企业使用里Llama 和部分开源模型需要确认授权边界Qwen 系列的宽松授权对内部项目更省心。这一点需要法务或技术负责人提前确认别等部署完再谈合规。2.2 Embedding 模型bge-m3 与混合检索的关系Embedding 选型直接决定了检索的上限。我们测试过 text2vec、bge-large-zh-v1.5、bge-m3最终选择了bge-m3。原因有三点它支持 8192 token 的长文本向量化处理长段落时不容易丢语义。它自带 Dense、Sparse 和 ColBERT 三种向量能力其中 Sparse 向量天然支持关键词匹配。这意味着我们不需要额外引入 BM25 服务去做关键词检索一个模型就能支持语义检索 关键词检索的混合模式。它对中文、英文和代码混合文本的处理都不错企业文档里PDF 解析结果 英文缩写 中文描述混杂的情况经常出现单一中文模型容易掉链子。这里有个常见误区以为 Embedding 模型选最火的就行但很多模型要求 query 侧加特定指令前缀不加效果会掉几个点。比如 bge-large-zh-v1.5 就建议给 query 增加为这个句子生成表示以用于检索相关文章的指令。bge-m3 则不需要。这个细节不实测很难发现但直接表现为检索命中率差异。选型后一定要先把 query 和 document 侧的处理方式读清楚。2.3 向量库与全文检索Milvus 和它的替代者向量库是另一个绕不开的选择。我们对比了主流方案方案优势短板我们的判断Milvus支持标量过滤、分区、混合检索、千万级数据友好部署运维复杂组件多最终选用因为权限过滤和混合检索是刚需pgvector部署简单基于 PostgreSQL 理解成本低数据量大后性能下降过滤和分区分片能力弱适合百份文档以内的轻量场景Elasticsearch全文检索能力最强也支持向量内存消耗大映射和管道配置繁琐如果团队已有 ES 且文档以文本为主也可以考虑我们是几百份真实业务文档起步峰值几千份数据量不算大但权限过滤部门维度和混合检索语义关键词两个需求非常明确。Milvus 的 Python SDK 可以直接在搜索请求里带 filter 条件比如part_id in [sales, presales]这一点非常省事。pgvector 也能做但过滤和向量检索的组合性能表现不如 Milvus 稳定。Milvus 的部署确实不轻松它需要 etcd、MinIO、milvus 本身三个组件。但用 docker-compose 一键起一套单机版也很成熟适合中小团队。上线后它就是整个系统里最需要监控的组件我会在后面踩坑部分专门讲。2.4 框架选择自研管道和 Dify 之间的取舍这个决定被问得最多为什么不直接用 Dify 或 FastGPT非要自己写管道我如实说Dify 这类开源平台确实能快速搭出带界面、带工作流的知识库应用内部试用完全够用。我们去评估的时候也认真考虑过。但最终没选是因为三个卡点文档解析和切片策略不够灵活。我们有很多扫描 PDF、复杂表格、PPTDify 的默认管道应对一般文档没问题但遇到需要定制 OCR 流程和按表格结构切块的场景改造成本高。权限模型不好做细粒度隔离。Dify 的企业版支持部分权限但开源版要做部门级隔离、和公司 LDAP 打通需要改不少代码。我们后续要做检索效果的评估和监控自研管道可以完全掌控日志、指标和调用链方便针对问题定位。当然这个取舍有个前提我们团队有后端和算法基础两周内能接受前期搭很多代码。如果是纯业务团队、文档结构统一、不需要复杂权限直接上 Dify 私有化部署是更理性的选择。别为了技术自主而自讨苦吃。最终的技术栈是Qwen2.5-14B-Instruct vLLM bge-m3 Milvus bge-reranker-v2-m3 FastAPI React 简易管理端。企业微信机器人在第二周接入。3. 整体架构一条从文件到答案的流水线3.1 六个层次和它们各自负责的事整个系统按数据流拆成六层每一层只干一件事边界特别清楚数据源层共享盘扫描器、语雀页面同步、本地文件上传。文件种类涵盖 PDF、Word、Excel、PPT、Markdown。解析与清洗层把各种格式转成标准化的 Markdown 文本对扫描件做 OCR对表格做结构化保留并抽取标题层级。切片与元数据层把长文档切成长度合理、语义完整的文本块同时记录来源、页码、标题路径、所属部门、上传时间。向量化与存储层bge-m3 把文本块转成 Dense Sparse 向量连同元数据一起写入 Milvus。检索与重排层query 处理后做混合检索得到候选片段再交给 Reranker 精排。生成与应用层把精排后的片段和用户问题组装成 Prompt送 Qwen 生成最终通过 Web 页面或企微机器人返回答案。这个分层其实就是标准 RAG 管道的骨架。真正需要注意的是各层之间的耦合比如解析层的输出格式会直接影响切片策略切片策略又决定检索层能不能快速过滤。如果一开始就把层边界写好后期调参和排错都能直击目标。3.2 写入链路与查询链路的完整流转写入链路离线管道文件进入系统先做格式识别和体检是扫描件还是文本型 PDF有没有加密编码是什么。解析器将文件转为 Markdown保留标题层级、表格和图片 OCR 文本。切片器按标题结构递归切块生成带元数据的 Chunk。bge-m3 为每个 Chunk 生成稠密向量和稀疏向量写入 Milvus。查询链路在线管道大致是这样用户问题进入 API 服务先做基于规则的轻量改写去掉口语、补全你这类指代词。用同一套 bge-m3 生成 query 的稠密向量和稀疏向量。在 Milvus 里同时执行向量检索和关键词检索各取 Top 50合并后按分数加权得到粗排结果。粗排结果交给 bge-reranker-v2-m3 做交叉编码精排取 Top 5~8 片段。拼接片段和用户问题注入 PromptQwen 生成答案并附上来源编号。这里最关键的思路是向量召回只是起点不是终点。纯向量检索对语义理解好但对专有名词、型号、人名这类必须一字不差的查询很弱。稀疏向量关键词能力补上这一块。最后用 Reranker 做精排可以用交叉编码器更细致地判断每个候选片段和问题的相关性。这三段式是当前 RAG 系统里性价比最高的检索组合。3.3 权限隔离在企业知识库里的特殊地位权限隔离是私有化企业知识库和公开演示 Demo 最大的区别之一。我们用一个简单的方案解决文档入库时由管理员标记所属部门Milvus 的每个 Chunk 都带 part_id 或 group_id 元数据。查询时系统根据企业微信用户身份反查其部门列表在 Milvus 的搜索请求里拼出过滤条件在检索阶段就把无权数据挡在外面绝不让不可见内容进入大模型上下文。这个方案不算高级但胜在简单可靠。真正要警惕的是绕过检索直接拼 Prompt这种隐藏漏洞。如果后续接入 Reranker 或外部服务必须保证过滤条件在每一跳都生效不能只过滤一次。我们没有做更细的文档级 ACL因为业务方要求就是部门隔离这个粒度已经够了。4. 落地细节解析、切片、向量化、检索、重排的执行方案4.1 文档解析先识别文档体检再谈抽取第一天到第三天的全部精力花在了解析上这一步是决定最终检索质量的第一道闸门。我们遇到的第一类问题是扫描件。很多历史合同、客户盖章确认单都是扫描 PDF解析出来是纯图片没有文字层。解决方式是在解析流程里接了一个 OCR 服务PaddleOCR 版面分析。它能识别标题、正文、表格区域并尽量保留阅读顺序。值得注意的是OCR 对中文小字号、表格边框复杂的页面并不稳定需要设置合理的阈值并人工抽检。第二类问题来自 PDF 里的真表格。用普通文本抽取会把表格列拆得七零八落丢失行和列的对应关系。我们的方案是表格区域单独抽取转成 Markdown 表格原样保存并且把一个表格视为一个独立 Chunk不允许在半中间切断。这直接避免了后面踩坑里最严重的一个问题。第三类问题是 Word / PPT。mammoth 可以把 docx 转成 Markdownpython-pptx 读出每一页的文本框。本质工作是杂乱但必须做把解析结果封装成统一的 Document 对象包含文件路径、标题层级、正文、表格和元数据下游只管消费这一个格式。我给团队定的原则是解析宁可慢不能错。因为错误解析的文本进入向量库后检索系统会一本正经地从错误内容里找答案这种幻觉最坑人。如果文档有严重的解析失败宁可打回待处理也不要硬灌。4.2 切片策略结构优先窗口兜底切片是 RAG 项目里被忽视、却直接影响命中率的一环。我们最初的思路是按固定 token 大小切结果发现大量片段上下文语义被截断。比如一个销售方法论里的为什么和怎么做经常被切到两个片段里检索时只能召回一半。后来改为结构优先、窗口兜底的递归切片先按 Markdown 标题层级切块一层层递归。优先保证一个二级标题下的内容作为整体。如果某一二级标题下的内容过长超过了设置的窗口大小再按滑动窗口切分。滑动窗口的步长小于窗口长度保留部分重叠避免关键句恰好被切在边界。实际参数我放在下面供参考参数项设定值说明目标块大小300~500 token太短语义不完整太长占上下文空间重叠窗口80~120 token保证跨段落的引用不断裂表格独立成块是表格整体为一个 Chunk不跨切段落连接符保留标题路径答案生成时能回溯上下文当时很多人建议用 1000 token 大块更准我们实测后觉得 1000 token 会让多片段拼进 Prompt 时互相干扰答案容易啰嗦且没有重点。300~500 是问答场景更稳妥的区间。具体数字最终还是要用自己的数据测试不能照抄文档值。4.3 向量化与入库元数据是后续检索的命根子嵌入本身很无聊难在元数据设计。每个 Chunk 的元数据字段我设计成这样文档 ID、文档标题、文档类型、部门标签权限用文件路径、页码、标题路径如首页 / 制度 / 报销流程上传时间、版本号、解析状态为什么元数据如此重要因为在检索时它承担两个功能一是权限过滤二是答案溯源。没有标题路径和页码回答里给不出精确的来自哪个文档第几页用户信任度会大大降低。后期做文档重新入库也是通过文档 ID 版本号精准删除旧片段再写新片段。嵌入的 batch size 我建议根据显存调。bge-m3 在 24G 显卡上可以跑 batch size 32 左右速度不算瓶颈真正耗时的是解析和 OCR。4.4 检索链路向量召回只是起点重排才是决定体验的一步检索链路我把它细分成三步缺一不可第一步是 query 预处理。我们做了很轻的规则改写去掉无意义的口语词把咱们公司改成公司把报销流程是啥改成报销流程。没有上复杂的 HyDE 或多轮意图识别因为初期这个投入产出比太低。第二步是混合检索。bge-m3 同时生成 query 的稠密向量和稀疏向量在 Milvus 里做两个子检索。稠密检索找语义相近的片段稀疏检索找关键词完全命中的片段。两者 Top 50 合并后用加权分数粗排。这个组合能明显缓解语义对但关键信息没对齐的问题。第三步是 Reranker 精排。把粗排后的候选片段和问题逐对丢进 bge-reranker-v2-m3 做交叉编码重新打分后取最高的 5~8 个片段。实验下来Reranker 对最终回答质量的影响非常大尤其是当 Top 10 里混着几篇内容相似但实际不相关的文档时精排能直接改变结果。这里有个血泪教训粗排 Top K 要尽量大50 起步精排后再截断。如果一开始就只召回 10 个后面再怎么精排也救不回来因为正确片段可能根本不在召回集里。4.5 生成环节提示词里必须写清楚不知道和引用来源很多人觉得生成环节就是把片段拼一起、丢给大模型其实 Prompt 设计决定了系统是能用还是不能用。我们的 Prompt 模板包含三条明确约束只能基于提供的上下文回答严禁编造上下文没有的信息。如果上下文不足以回答直接说在已导入的资料中未找到相关信息不要强行给答案。答案里每个关键信息都要用[来源1]这样的编号标注编号对应 Prompt 里拼入的片段。编号引用这个设计特别重要。我们实测发现没有来源编号时模型会一本正经地胡说八道三个片段里只有半个相关它也能编出完整回答。加了编号后至少用户可以点开来源核对这就把系统从好玩但不可信拉回到内部可用的生产力工具。5. 两周踩坑实录五个让我失眠的问题5.1 表格被拦腰切断财务数据回答得驴唇不对马嘴现象第一版上线后十几个测试问题里有三个错得离谱。最典型的是问住宿报销标准模型一本正经地回答出差住宿标准为每人每天 850 元实际文档里写的是 400 元且分城市等级不同。排查链路先看日志找到这条回答用了哪些上下文片段。发现回答案例引用的是一个切片了 20 行的表格内容模型只看到了表格的标准上限列没看到它对应的城市等级列也没有看到标题行。进一步检查切片器发现我们的固定 token 切分把 Markdown 表格中间截断了表格的列对齐信息彻底丢失。虽然后续做了表格独立成块但最初那批数据是旧切分逻辑产出的。根因切片策略只考虑了文本语义没有考虑 Markdown 结构的完整性。修复方案重新解析所有文档表格结构整体作为 Chunk 存储表格前后不使用滑动窗口跨切并在切片前解析 Markdown 表格语法树遇到表格节点直接标记为不可分割。修复后验证财务类问题的准确率从 40% 提升到 75%。5.2 召回片段堆满上下文反而答非所问现象上线中期用户反馈明明文档已经改对了回答还是答非所问。我们定位发现问题不是文档内容而是 Prompt 里的片段太多、太杂上下文质量被稀释。排查链路日志显示一次请求往往拼入 15~20 个片段Prompt 总 token 数经常冲破 8000。当错误片段数量多于正确片段时模型的主要注意力放在了错误内容上甚至主动忽略最相关的那段。根因我们把精排后的 K 值设得太宽而且没有按相关性做动态截断。修复方案精排 K 值从 15 调整到 5~8同时判断第一个片段和用户问题的重合度如果 Top1 已经非常匹配Reranker 分数超过阈值就直接用前 3 个片段不硬塞满 K。这既降低了 Prompt 长度也减少了模型被无关信息带偏的概率。5.3 答案不标引用用户根本不敢用现象Demo 看起来效果不错但业务方第一句话是它回答得再好我也不敢直接引用因为我不知道它哪句话是真的。排查链路我们意识到不是所有模型回答都自动带来源编号。Qwen 在没有看到必须标来源编号约束时更倾向于写成自然流畅的一段话不带任何出处。而带来源编号的答案用户至少可以反查原始文档。根因生成 Prompt 的约束写得太弱模型默认模式是写人话不是写有据可查的人话。修复方案在 Prompt 里增加一段结构化约束明确答案中所有事实性信息必须跟随来源编号编号必须引用提供的上下文中实际存在的片段编号。同时把组织好的上下文片段在 Prompt 里按编号列出并在系统返回结构里单独回传答案引用的片段列表方便前端高亮展示。5.4 线上卡顿最后查到 HTTP 链接池被占满现象第二周接入真实用户流量后系统开始随机卡死。有时候请求 20 秒才返回有时候直接 500。排查链路一开始怀疑是模型推理扛不住但看了 vLLM 监控GPU 利用率只到 60%。然后又查日志发现大量请求卡在向 Milvus 和 Embedding 服务发起 HTTP 调用的环节。最终定位我们内部封装的 Python 客户端使用全局 requests.Session连接池默认只有一个 host 的 10 个连接。在线请求一多所有线程都在等连接释放表现为整体吞吐量骤降。修复方案把基础调用库换成 httpx.AsyncClient并发数调到 100同时给 vLLM、Embedding、Milvus 各服务都配上独立的连接池。修复后压测并发 50 时 P95 延迟从 15 秒降到 4 秒以内。这个坑特别隐蔽也特别典型。RAG 管道的性能瓶颈往往不在大模型而在周边服务的连接管理。上线前一定要做并发压测不要只测单请求延迟。5.5 文档更新后旧版本还在回答新旧混淆现象有人把最新的报销制度上传后再问相关问题答案却还是旧版本的数字。排查链路查询检索结果发现 Milvus 里新旧两个版本的 Chunk 同时存在。旧版本切片的标题和新版高度相似粗排阶段两个都进了候选集Reranker 没能百分百区分新旧模型看到矛盾信息后可能采信更完整的旧版本内容。根因我们的上传管道只负责写入新文档没有按文档 ID 清理旧版本数据。修复方案增加重传机制——同一文档再次上传时先在 Milvus 里按doc_id version删除旧 Chunk再写入新 Chunk。同时给每个 Chunk 加updated_at字段重排时如果新旧版本同时存在优先用最新版本。修复后这类问题彻底解决。提示文档更新是知识库运营中最高频、最容易出事故的操作管道必须把删除旧数据和写入新数据做成一个事务性的入库动作否则新旧混存的问题一定会出现。6. 上线后的数据与 RAG 瓶颈6.1 自建评估集与三个核心指标两周不是终点实际上真正的调优在上线后才开始。我们做的最有价值的一件事是花了一天时间从真实用户问题里抽了 100 条按部门分布做了一份标准评估集。评估集不看整体效果好不好而是拆成三个指标指标含义初版数值两周后Recall5正确答案片段是否出现在检索前 5 名0.520.78Hit Rate用户问题的答案片段是否能被召回0.730.90人工准确率最终回答被业务人员判为正确可用的比例0.670.85这三个指标对应三个不同层Hit Rate 是检索兜底能力意味着至少要找得到Recall5 是粗排精排质量人工准确率则是端到端生成效果。分别统计能帮我们快速定位问题到底出在检索还是生成。调优过程中最有用的两条经验第一评估集里要刻意包含边界问题例如带否定、带时间条件、跨两个部门的查询第二每次改解析或改切片后都重新跑一遍评估集用分数说话不要凭感觉判断效果变好了还是变差了。6.2 用户真实反馈和当前瓶颈用户真实使用后发现高频问题集中在制度查询、流程查询、系统操作手册这几类准确率明显比冷门技术资料好。因为高频问题往往文档结构清晰、描述稳定检索容易命中。当前 RAG 系统的瓶颈也暴露得很明显主要集中在三类多跳和关联类问题处理不了。比如我们今年在华东地区中了哪些标总额多少需要跨多个文档做汇总统计纯向量检索没法完成这种推理任务。表格类文档虽然解决了被切断的问题但语义检索对第几列是什么含义理解仍然吃力。用户问哪个客户的回款最晚即使数据在表格里模型也经常取错行列。长尾专有名词、缩写和内部黑话命中率偏低。比如BTB 流程这种内部约定语在文档里出现频率低稀疏向量匹配不到向量语义又不够准确。这些现象说明基础版 RAG 已经能在企业里承担翻文档的职责但离懂业务还有距离。6.3 下一步GraphRAG、Agentic RAG 与多模态的路线选择面对上面几个瓶颈后续方向其实比较明确。首先是 GraphRAG。很多问答题涉及谁、什么时候、和哪个项目、有什么关系这类实体关系查询纯向量的全局关联能力很弱。我们需要做实体抽取和关系图谱让系统能依据图结构做多跳检索。但 GraphRAG 的构建成本不低实体抽取需要针对公司文档反复调优不建议在没有基础 RAG 效果数据时就贸然上。其次是 Agentic RAG。遇到统计一下华东区中标金额这种问题需要模型自主决定先查哪些文档、再对结果做计算这需要一个轻量编排层。我们计划在现管道的 query 预处理环节接入简单的工具调用让模型能决定先检索主档再检索明细表而不是把所有内容一次性塞进 Prompt。再者是多模态。用户问的问题经常需要看文档里的架构图、截图纯文本上下文帮不上忙。但多模态模型部署成本更高短期内我们打算优先做好图片的 OCR 和图表转 Markdown让图片信息先变成可检索文本再考虑引入多模态模型理解原图。最后想说一个很多人没考虑到的点知识库上线后真正维护文档版本、处理用户反馈、持续扩充评估集比搭建系统本身更需要投入。RAG 不是一个部署完就好的系统它像一个不断吸收新文档并持续变准的知识生物。如果团队没有设置一个兼职甚至专职的运营角色哪怕架构再漂亮半年后也会因为文档过期、用户问题变化而变得越来越不可用。个人实测下来最稳健的落地路线是先选 20 份高频业务文档做冷启动跑通一条端到端链路并让一个试点部门真实使用根据反馈迭代解析和切片再逐步扩大覆盖面。一开始就追求导入全部共享盘数据大概率会把团队精力耗死在处理存量脏数据上反而拖垮整个项目。
返回列表