拓冰建站拓冰建站
首页 / 资讯中心 / 正文

基于RAG与MCP的私有知识库问答助手部署与调优实战

个人知识库这件事我折腾了挺久。最初用的是文件夹加全文搜索后来资料越来越多搜索返回几百条结果真正想要的答案被淹没在里面。直到我把 RAG 检索增强生成这套思路落地用 weknora 搭了一个私有问答助手再把 MCP 协议接进去之后整个使用体验才算彻底改变——现在我可以直接用自然语言问它上次总结的 MySQL 慢查询优化清单里关于索引下推的注意事项是什么它会在几秒内给我一个带出处引用的答案。这篇文章不打算讲太多理论重点是把这整套东西的部署过程、技术选型逻辑、以及实际调试中踩过的坑完整记录下来。适合手里已经攒了大量技术文档、笔记、PDF但一直觉得能搜到但找不到答案的人。只要你 Docker 能跑起来半天时间完全可以把这套东西架起来之后要做的就是持续往里面喂文档、调参数、跟它磨合。1. 先想清楚RAG 解决的是找答案而不是存文件的问题1.1 一个每天都在发生的小场景先说个具体的例子。我之前有一个 notes 目录里面按年份和技术方向分了十几个文件夹存了大概 800 多个 Markdown 文件还有一部分从网上收集的 PDF 和 Word 文档。平时想查点什么第一反应是打开 IDE 或者用系统自带搜索去 grep 关键词。说实话关键词搜索在文件少的时候还行文件一多问题就出来了我明明记得文档里写过长连接导致连接数耗尽这句话但 grep 出来二十几处分布在不同的文件里每一处只有一行匹配上下文还得自己翻。这其实就是能检索和能回答之间的差距。全文搜索返回的是命中的行但它不会帮你归纳、不会把相关的多篇文档综合起来、也不会告诉你这几种方案各自的适用场景。而 RAG 做的是另一件事它先把文档切成片段、转成向量等用户提问时把问题也转成向量去匹配最相关的片段然后把匹配到的片段作为上下文交给大语言模型去生成答案。也就是说它把找和想这两件事直接连起来了你问的是一个问题得到的是一段经过组织、带出处的回答而不是一堆需要自己再读一遍的搜索记录。1.2 RAG 的完整链路和三个关键环节一个标准的 RAG 系统链路可以拆成下面几步文件加载把 PDF、Word、Markdown、TXT 等原始文件读进来转成纯文本文本分块把长文本切成固定大小的块避免超长内容超出模型的上下文限制向量化用嵌入模型Embedding Model把每个文本块转成一个高维向量索引存储把向量连同原文、元数据一起存入向量数据库用户查询将用户问题转成向量在向量库中做相似度检索上下文拼装把召回的相关文本块和用户问题一起组装成 Prompt生成回答交给大模型生成最终答案并在回答中标注引用来源。其中第 1 到第 4 步是离线索引阶段第 5 到第 7 步是在线问答阶段。整条链路里最影响问答质量的其实是三个环节分块是否合理、嵌入模型是否适合你的语言和领域、以及召回和重排是否准确。很多人部署完 rag 项目之后发现效果不如预期十有八九问题就出在这三个地方而不是出在大模型本身。1.3 为什么不直接用微调这是我在给朋友推荐这套方案时被问得最多的问题。微调确实可以让模型记住特定领域的内容但个人知识库的场景有几个硬伤第一数据更新频率高今天加一篇文档、明天删一个旧方案微调一次成本不低第二微调需要构造训练数据个人场景没有那么多精力去做标注第三微调后的模型是一个黑盒它给出答案时你很难知道它依据的是哪篇文档出了问题也不容易追溯。RAG 的优势在于文档随时增删索引实时更新答案可以附带来源引用方便核对也不需要对模型本身做任何训练操作。如果你的目标是让助手帮我从一堆文档里找答案、做归纳RAG 是明显更省力的路线。我这套系统从部署到跑通第一批问答前后只花了一个下午这个效率微调绝对做不到。2. weknora 部署Docker 私有化搭建与关键组件解析2.1 环境准备硬件、系统与目录规划我在部署时用的是一台 4 核 8G 的云服务器系统是 Ubuntu 22.04。如果你的文档量在几千篇以内、并且问答时使用 API 形式的大模型这个配置完全够用。如果打算跑本地模型建议至少 16G 内存加一张 12G 显存以上的显卡否则本地模型的推理速度会很影响体验——我自己在只有 CPU 的机器上试过跑 7B 模型一个问题要等将近半分钟基本没法用。部署前先规划好目录结构我习惯把所有数据放在/opt/kb下面/opt/kb ├── docker-compose.yml ├── .env ├── data │ ├── documents # 原始文档 │ ├── vectorstore # 向量库持久化目录 │ └── logs └── models # 本地模型文件如果用本地模型建议把数据目录单独放在数据盘上不要放在系统盘。我之前图省事把向量库直接写在系统盘后来索引文件涨到几个 G系统盘告警差点把服务搞挂最后还得折腾迁移得不偿失。2.2 docker-compose 配置解析weknora 官方推荐用 Docker Compose 方式部署我用的配置文件简化后大概是这个样子services: weknora: image: weknora/weknora:latest container_name: weknora restart: unless-stopped ports: - 8080:8080 volumes: - ./data/documents:/data/documents - ./data/vectorstore:/data/vectorstore - ./data/logs:/data/logs environment: - APP_PORT8080 - EMBEDDING_MODELBAAI/bge-large-zh-v1.5 - EMBEDDING_DEVICEcpu - VECTOR_STOREchroma - VECTOR_STORE_PATH/data/vectorstore - LLM_PROVIDERopenai-compatible - LLM_BASE_URLhttp://host.docker.internal:11434/v1 - LLM_API_KEYdummy - LLM_MODELqwen2.5:14b extra_hosts: - host.docker.internal:host-gateway启动命令很简单docker compose up -d docker compose logs -f weknora等日志里出现类似 Application startup complete 的字样服务就起来了浏览器访问http://服务器IP:8080就能打开管理界面。这里解释几个关键的配置项方便你自己按需调整EMBEDDING_MODEL指定嵌入模型。我用的BAAI/bge-large-zh-v1.5是一个中文效果不错的开源嵌入模型支持中文和英文混排向量维度 1024。如果你的文档以英文为主可以换成bge-large-en-v1.5或者更轻量的bge-small。这个参数决定了后续所有向量化操作的基础选错模型后面全盘受影响。VECTOR_STORE向量数据库类型。weknora 内置了 Chroma 和 FAISS 两种轻量级选择个人知识库这个量级用 Chroma 足够它支持按集合管理多个知识库后续清理和重建索引比较方便。文档量到了百万级再考虑 PostgreSQL pgvector 或者 Milvus但那种体量已经不是个人知识库的范畴了我也不建议一上来就上重武器。LLM_PROVIDER大模型的接入方式。上面配置用的是 openai-compatible也就是任何提供 OpenAI 兼容接口的服务都可以接——包括本地部署的 Ollama、vLLM以及各类云服务商的兼容网关。这套设计的好处是你可以随时切换后端模型而不需要改业务代码。2.3 大模型接入API 还是本地模型这是整个选型里比较纠结的一点。我个人的建议是先跑通 API 形式确认效果再根据隐私和成本需求决定是否换本地模型。API 形式的优势是效果稳定、部署零负担。缺点也很明显文档内容会发送到外部服务对隐私要求高的场景不合适。本地模型正好反过来数据不出服务器但对硬件有要求而且小参数模型的生成质量和理解能力确实会差一些。我的实测感受是做技术问答7B 到 14B 量级的量化模型比如 Qwen2.5-14B 的 Q4 量化版可以覆盖大部分日常问题但遇到需要多步推理的复杂问题还是能感觉到和 API 大模型的差距。所以我的做法是双轨制默认走本地模型需要在复杂问题上追求更好效果时把模型切换到 API 网关。反正 weknora 支持多模型配置切换成本几乎为零。3. 文档接入环节最容易被低估解析、分块与向量化3.1 文件解析PDF、Word、Markdown 的差别很多人部署完 weknora 之后的第一反应是往上传文档然后发现问答效果很差。问题往往不出在检索而出在第一步的解析。我的经验是Markdown 和 TXT 这类纯文本格式解析最干净几乎没有损耗。Word 文档要看版本docx 格式解析器处理得比较好老版本的.doc文件建议先转成 docx 再入库。PDF 是最麻烦的如果是文字版 PDF也就是可以选中文字的解析效果尚可如果是扫描版或其他图片型 PDF直接上传基本等于白传必须先用 OCR 工具做一层识别。weknora 本身对 PDF 解析做了不少优化但复杂排版多栏、表格、页眉页脚仍然容易出现信息错乱。我在实测中发现PDF 里的表格是重灾区。一个包含十几行参数对比的表格解析完之后经常变成一行行脱离上下文的碎片检索时要么召回不到要么召回了但上下文缺了表头模型根本不知道那列数字代表什么。后面我会在第 6 节讲这个问题具体怎么处理。3.2 分块策略chunk_size、overlap 到底怎么调分块Chunking是 RAG 里最影响效果、但也最容易被忽略的参数。块太大语义完整但检索粒度粗而且容易混入不相关的内容块太小检索精准但语义被切碎模型难以理解上下文。weknora 默认的分块参数是按 token 数来算的默认值一般在 500 到 800 之间overlap重叠默认是 50 到 100。我折腾下来觉得对于技术文档这个场景比较稳妥的起步配置是chunk_size: 500 tokenoverlap: 100 token为什么要设 overlap因为文本块之间的边界是硬切的一个完整的技术概念可能正好被切在边界上前后各一半。重叠的部分可以让相邻块都包含完整的上下文提升召回的覆盖率。我做过一个简单的对比测试同样一份 Spring 配置说明文档overlap 从 0 调到 100 之后某个关于连接池参数的问题从召回两条不相关内容变成了准确命中第三条。这个提升非常可观。3.3 嵌入模型选型中文场景别乱选这一节我想多说几句因为嵌入模型的选择直接决定了语义相似的判断准不准。早期我踩过一个坑用了一个以英文为主的通用嵌入模型结果中文文档的召回效果惨不忍睹。问题是如何配置数据库连接池召回回来的片段经常是线程池连接数这些词面相近但语义不同的内容答案自然也是南辕北辙。后来换成了针对中文优化的模型效果立刻不一样。目前中文场景比较常用的开源嵌入模型有模型维度特点BAAI/bge-large-zh-v1.51024中文效果好支持英文社区生态成熟BAAI/bge-m31024多语言支持 100 语言支持 8K 长文本text2vec-large-chinese1024纯中文场景体积适中shibing624/text2vec-base-chinese768轻量适合低资源环境如果你处理的是纯技术文档中英混排的情况很常见我会优先推荐 bge-m3多语言支持可以避免文档里夹杂英文术语导致向量漂移的问题。另外提醒一点检索阶段用的嵌入模型最好和索引阶段保持一致否则向量空间不统一召回效果没有保证。我见过有人索引时用 bge-large、检索时换成 text2vec结果相似度分数全线漂移排查了很久才发现是模型不一致导致的。4. MCP 接入把问答助手从会读书升级成能干活4.1 MCP 协议的角色划分几句话就能讲清楚MCPModel Context Protocol模型上下文协议是 Anthropic 在 2024 年底开源的一个开放协议目的很简单给大模型和外部工具、数据源之间定一个统一的标准接口。我通常用 USB-C 来类比以前各种设备充电口乱七八糟USB-C 出现之后一根线能解决大部分设备的充电和数据传输问题。MCP 对大模型生态做的事情类似——以前每个应用接入 AI 都得定制一套集成方案现在只要实现了 MCP大家用同一套接口标准就能互相通信这就是为什么现在到处都在讨论 mcp server、mcp host 这些概念。MCP 架构里有三个角色MCP Host宿主程序通常是大模型应用本身比如 weknora 的问答服务、Claude Desktop、各种支持 MCP 的 IDEMCP Client运行在 Host 内部的连接器负责和 Server 建立会话、发起请求MCP Server工具或数据源的提供方每个 Server 暴露一组工具Tool或资源通过 JSON-RPC 协议与 Host 通信。传输方式有两大类本地进程用 stdio标准输入输出远程服务用 HTTP/SSE 或流式 HTTP。weknora 目前两种都支持这给后续扩展留了很大的空间。4.2 在 weknora 里配置 MCP 工具我在 weknora 里做的第一件事是把几个常用的 MCP Server 接进来。配置入口在管理后台的工具/外接服务页面本质上就是写一个 JSON 配置声明每个 Server 的名字、启动方式和参数。我当时的配置大概是这样的{ mcp_servers: [ { name: local-file-search, type: stdio, command: python, args: [/opt/kb/mcp-servers/file-search/server.py], env: { BASE_DIR: /opt/kb/data/documents }, description: 在本地文档目录中执行文件名和内容搜索, enabled: true }, { name: db-schema-query, type: http, url: http://127.0.0.1:9001/mcp, headers: { Authorization: Bearer sk-local-demo }, description: 查询内部数据库的表结构说明, enabled: true } ] }配置完成之后在问答界面里通过自然语言就能触发这些工具。比如我问帮我查一下本地文件里有没有关于 Redis 集群部署的文档问答助手会分析出这个问题需要调用 local-file-search 工具然后执行搜索、拿到结果、再结合 RAG 检索到的内容给出一份综合回答。4.3 实测让助手调用接口和命令而不只是读文档接入 MCP 之后最大的变化是问答助手的能力边界从知识库里的文本延伸到了真实世界。举个例子。我在本地维护了一批服务发布记录的 Markdown 文件。在过去我想知道这个月有哪些服务升级过得先搜索、再打开文件、自己归纳。现在直接问这个月服务升级记录里涉及支付模块的变更有哪些助手会调用 file-search 工具搜目录把相关内容抓回来再结合知识库里的变更规范文档输出一个带日期和版本的清单。这里有一个重要的细节MCP 工具调用和 RAG 检索不是互斥的而是分层的。知识库负责提供文档里写了什么MCP 工具负责提供当前系统和数据是什么两者一起拼进上下文模型才能做出更准确的判断。我最初以为接上 MCP 就可以少建知识库索引实际用下来发现根本不是这样——一个是静态知识一个是动态能力缺了哪个答案都会显得单薄。5. 检索问答链路调优向量召回、重排与上下文拼装5.1 召回参数Top-K 和相似度阈值怎么定在线问答阶段系统会先把用户问题转成向量然后从向量库中检索最相似的文本块。检索数量通常用 Top-K 表示还有一个相似度阈值用来过滤低质量结果。我的实践建议是Top-K 先设 8 到 10 个相似度阈值根据你的嵌入模型来定。bge 系列模型的相似度分数一般在 0.3 到 0.9 之间我通常把阈值设在 0.5 左右——低于 0.5 的片段召回回来大概率是噪声反而会干扰模型生成答案。这里要特别提醒不要为了追求召回全而把阈值调得很低。召回的片段越多Prompt 越长模型在生成答案时受到无关信息的干扰就越大。个人知识库场景宁可少召回几个片段也不要让大量低质量内容混进上下文。这个道理和带新人一样你给他十份互相矛盾的材料他反而不知道听谁的。5.2 重排只用向量相似度是不够的向量相似度擅长衡量语义相近但语义相近不等于与当前问题最相关。举个例子你问如何配置 Nginx 的负载均衡向量召回来的片段可能包括Nginx 常见配置错误排查Nginx 反向代理与负载均衡的区别——它们确实都和 Nginx 有关但最核心的配置步骤反而排在后边。这种情况下加一层重排Rerank模型效果会提升很多。重排模型的作用是把召回的候选片段按与问题的相关程度重新排序让最相关的内容排在最前面。weknora 支持配置一个独立的重排模型我用的是BAAI/bge-reranker-base效果不错。接入重排之后我的检索策略变成了宽召回 精重排第一步用向量检索召回 Top 20 个候选第二步用重排模型筛选出 Top 5 个真正相关的片段。这样既不会漏掉潜在的相关内容又能保证最终进入 Prompt 的片段质量。这套组合拳打下来问答的准确率提升是最明显的。5.3 Prompt 拼装上下文窗口怎么安排最后一步是把召回的片段、MCP 工具返回的结果、以及用户问题组合成 Prompt。这个环节最容易犯的错误是一股脑全部塞进去。我的经验是Prompt 的组装顺序和结构可以这样设计系统指令说明助手的角色、回答要求如只能基于给定上下文回答不要臆造检索片段按重排后的顺序排列每段标注来源文件名工具结果如果有 MCP 工具调用把返回结果放在检索片段之后用户问题放在最后。一个精简的模板大概是你是一个技术问答助手。请只根据下面的资料回答问题如果资料中没有相关信息请明确说知识库中未找到相关答案不要自行编造。 参考资料 [1] 来源docs/nginx/load-balancer.md 内容upstream 指令用于定义后端服务器组…… [2] 来源docs/nginx/common-config.md 内容proxy_pass 用于将请求转发到后端…… 用户问题如何配置 Nginx 的负载均衡这个模板的核心是限制模型自由发挥告诉它资料里没有就直说比让它硬编一个答案要可靠得多。实际测试中加上这句约束之后幻觉出现的频率明显下降。我后来对比过带不带这句约束的问答结果没有约束时模型偶尔会在答案末尾补充一些看似合理但完全不在文档里的建议这种内容在技术决策场景里是很危险的。6. 排障实录搭建过程中最耽误时间的五个问题6.1 中文分块把语义切碎了第一个让我头疼的问题是中文文本的分块。英文按空格分 token 很自然但中文没有天然分隔符如果分块算法对中文处理得不好经常会出现一个完整的技术要点被切成两半的情况。我的解决办法是先检查 weknora 生成的分块结果看看有没有明显的语义断裂然后调整分块策略。weknora 支持按段落优先切分如果一个段落的长度不超过 chunk_size就把它作为一个独立的块只有超过上限才做硬切。这个策略对技术文档特别友好因为大部分技术文档都是按段落组织内容的段落本身就是最小的语义单元。另外一个技巧是在导入文档之前先对 Markdown 做一层预处理把标题层级信息作为元数据保留。这样即使一个标题下内容被分到多个块里检索时也能通过标题把上下文关联起来。这个做法相当于给每个文本块打了一个章节锚点对提高长文档的召回精度帮助很大。6.2 PDF 表格解析成乱码前面提到过PDF 表格是重灾区。我有一份数据库设计文档里面全是表结构说明上传之后问答效果一塌糊涂——问用户表的索引有哪些模型给出的答案来自完全不相关的上下文。排查下来问题出在表格解析环节解析器把每一行拆成了独立的文本块表头信息丢失了。解决方法是两个其一对关键文档做预处理在入库前手动把表格转成 Markdown 表格格式或者转成字段名: 字段说明的列表形式其二利用 weknora 的重新解析功能对不同类型的文档使用不同的解析策略。对于扫描版 PDF我最后放弃了直接用 weknora 解析而是先用 OCR 工具转成文本再导入。个人知识库里真正需要保留的扫描件其实不多这个转换成本是可以接受的。关键是要形成一个习惯入库前先检查文档的解析预览确认没有明显乱码和结构丢失再建立索引。偷懒跳过这一步后面排查问题的成本只会更高。6.3 删除的文档还在被检索到有段时间我发现明明已经在管理后台删掉了一份文档问相关问题的时候它仍然出现在引用来源里。排查之后发现是索引没有同步删除。这类问题的根因通常是删除操作只删了原始文件记录但没有触发向量库中的向量删除。weknora 新版应该会自动处理但如果你用的是旧版本或者像我一样直接操作过底层文件很容易出现脏索引。我的处理办法是删档之后主动触发一次索引重建。weknora 后台有重建索引的按钮也可以调用 APIcurl -X POST http://localhost:8080/api/kb/rebuild \ -H Authorization: Bearer ${API_KEY}重建完成之后再验证问题就消失了。这段经历给我的教训是任何 RAG 系统都要把索引的生命周期管理当成一等公民来对待增删改查四个操作里增和查大家都会做删和改才是容易踩坑的地方。6.4 开放性问题幻觉严重RAG 系统最常见的翻车场景是处理开放性问题。你问如何优化系统性能这个问题没有明确指向检索回来的片段可能是性能相关的若干文档模型在拼凑这些内容时很容易顺着话头编。我的对策分两步第一步在 Prompt 里加上如果问题过于宽泛请先引导用户明确范围的指令第二步把这种问题当成知识库提问而非客观事实提问让答案明确标注以下内容综合了多篇文档部分建议需结合实际情况验证。说到底RAG 助手的定位是辅助检索和归纳不是绝对权威的技术顾问。让用户在回答中看到引用来源自己做最终判断这才是知识库工具的正确使用方式。我在管理后台给助手加了一句系统提示当用户问开放式问题时先帮他拆解问题、列出知识库中相关的文档方向而不是强行给一个结论。这样既降低了幻觉风险又保留了对话的实用性。6.5 上下文窗口被低质量内容塞满最后一个问题是资源浪费型问题召回设定得太宽进入 Prompt 的片段很多但大多数都不相关导致上下文窗口被塞满模型的注意力被分散回答质量反而下降。这个问题比较容易诊断——打开后台日志看每次问答的 Prompt 长度如果经常接近模型上下文上限说明检索链路需要收紧。我是这么调的把 Top-K 从 10 降到 6调高相似度阈值例如从 0.45 调到 0.55重排之后只保留前 3 到 4 个片段对每个片段做长度截断过长的片段先取开头部分。这几步调整做完之后问答响应速度也明显变快了因为输入 Token 变少模型生成时间缩短了一大截。我后来反思了一下RAG 系统的调优本质上就是在做信息密度的取舍与其给模型一大堆可能相关的内容不如精准地给几条真正相关的内容这个思路贯穿了从分块、召回、重排到 Prompt 设计的每一个环节。这套系统跑到现在我已经把日常工作里最常用的几十个技术主题文档都导了进去问答命中率大概在七八成左右。剩下的两三成大部分问题出在源文档本身就写得模糊或者我提问的方式太含糊上倒不是系统的问题。如果你也打算搭一套我的建议是别一上来就追求大而全先把高频、结构清晰的文档入库跑通全链路、建立对系统脾气的感知再逐步扩充。数据清洗和分块策略这些看不见的功夫比模型选型更值得花时间。
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门