把一个 RAG 项目做成可插拔的知识基础设施

很多 RAG 项目看起来都像“能回答问题的 Demo”,但真正难的从来不是把答案跑出来,而是把它做成一套可替换、可观测、可回归、可接入 Agent 生态的知识基础设施。

这个项目最值得写进博客的,不是“我用了哪些框架”,而是它把一整套面试里最容易被问穿的问题,落成了工程事实:

  • 检索不是单向量召回,而是 BM25 + Dense Embedding + RRF + Cross-Encoder/LLM Rerank 的分层检索
    • 摄取不是简单读 PDF,而是 五阶段文档摄取流水线:PDF 解析、语义切分、元数据增强、向量化、幂等入库
    • 多模态不是额外拼接,而是 Vision LLM 图像转文本 后复用同一条纯文本检索链路
    • 对话不是单接口拼接,而是 FastAPI + LangGraph 的状态机编排,支持意图识别、指代澄清和并行子查询拆分
    • 稳定性不是靠运气,而是 Redis + MySQL 混合会话存储,把短期热缓存和长期记忆沉淀分层处理 如果你想快速抓住这个项目的本质,可以先记住一句话:

这不是一个“知识库问答系统”,而是一个能把 RAG 能力发布成标准化上下文服务的工程底座。 下面我就围绕这 5 个侧重点来拆解,它们基本就是这篇博客最该讲透的部分。

一、项目的整体目标

这个仓库的目标不是“做一个能回答问题的接口”,而是做一个可插拔、可替换、可观测、可评估的 RAG 平台。它同时服务两类场景:

  1. 作为知识检索与问答底座,给用户提供稳定的私有知识查询能力。
    1. 作为学习和面试项目,帮助开发者系统理解 RAG 工程化落地的关键环节。 从架构上看,项目大致分成三层:
  • 摄取层:把 PDF 等原始文档处理成结构化 chunk
    • 检索与生成层:完成混合召回、重排、上下文组装和答案生成
    • 服务层:通过 MCP Server、FastAPI 后端和 Dashboard 对外提供能力 这种分层的好处很明显:每一层都能独立替换和演进,不会把整个系统绑死在某一个模型、某一种向量库或某个前端形态上。

二、为什么说它是“模块化 RAG”

很多 RAG 项目的问题,不在于功能能不能跑,而在于后续怎么改。一旦嵌入模型、重排模型、向量库、切分器、LLM Provider 都写死,项目后期几乎没法做实验。

这个仓库的设计思路是把关键组件都抽象成可替换模块:

  • LLM Provider 可切换

    • Embedding 可切换
    • Reranker 可切换
    • Vector Store 可切换
    • Splitter 和 Transform 也可配置 这意味着你可以很自然地做三类实验:
  • 模型实验:切换不同 LLM / Embedding / Reranker 看效果

    • 策略实验:调整 chunk size、overlap、召回 top_k、rrf 参数
    • 部署实验:在本地、云端或不同存储后端之间迁移 对工程来说,真正重要的是“系统能不能演进”,而不是“第一次能不能跑通”。

这篇文章真正要讲的 5 个技术侧重点

如果只停留在“可插拔”四个字,还是不够。这个项目真正像工程底座的原因,是它把下面 5 个容易被忽略但很关键的能力做成了体系,而不是零散功能:

  • 分层检索:BM25 负责关键词,Dense 负责语义,RRF 负责融合,Rerank 负责精排
    • 五阶段摄取:PDF 解析、语义切分、元数据增强、向量化、幂等入库一条链路打通
    • 多模态复用:Vision LLM 先把图像转成文本,再走统一文本检索管线
    • 状态机对话:FastAPI + LangGraph 实现意图识别、澄清、并行子查询与生成编排
    • 混合会话存储:Redis 负责短期热状态,MySQL 负责长期历史沉淀,保证多轮稳定性 这些点比“我支持几个模型”更像真正的系统设计,也更适合在面试里展开。

三、摄取链路:从 PDF 到可检索 Chunk

RAG 的质量,首先取决于数据摄取质量。这个项目的 ingest 流程不是简单地“读文件然后切块”,而是尽量把文档变成可检索、可追踪、可增量更新的结构化数据。

核心思路是:

  1. 文档标准化:把 PDF 统一转换为 Markdown 风格文本,尽量保留标题、段落、列表等结构信息。
    1. 语义切分:使用更适合 Markdown 的分块策略,避免机械切断语义。
    1. Transform 增强:可插入图像描述、元数据补全、chunk refinement 等处理。
    1. Embedding 与 Upsert:把 chunk 写入向量库,同时保留 metadata,方便后续过滤与追踪。
    1. 增量处理:通过文件 hash、历史记录和状态管理,减少重复摄取成本。 这里最关键的不是“用了什么库”,而是一个工程原则:Loader 负责统一格式,Splitter 负责切块,Transform 负责增强,Storage 负责持久化。

这样一来,后面你想替换任何一步,都不会把整条链路炸掉。

再往深一点看,这条链路其实还体现了三个更成熟的工程思路:

  • 幂等摄取:通过文件 hash 和历史状态表做零成本跳过,避免重复解析和重复入库
    • 结构优先:先把 PDF 变成结构化 Markdown,再交给切块器,减少“纯字符切分”对语义的破坏
    • 多模态复用:把图片先转成文本描述,再复用同一套文本检索链路,不需要为图像单独搭一条复杂管线

四、检索链路:Hybrid Search + Rerank

如果说摄取决定了“库里有什么”,那检索决定了“能不能找到真正有用的东西”。

这个项目采用的是典型的两段式检索:

1. 粗召回

粗召回阶段同时使用:

  • Sparse Search:例如 BM25,适合关键词精确匹配、专有名词和术语检索

    • Dense Search:基于 embedding 的语义召回,适合同义表达和模糊问法 然后再用 RRF(Reciprocal Rank Fusion)做融合。这样做的目的很直接:
  • BM25 负责“找得准关键词”

    • Dense 负责“找得懂语义”
    • RRF 负责“把两种召回优势合并起来”

2. 精排重排

粗召回拿到候选集之后,再用 rerank 做二次排序。这个步骤非常关键,因为它决定了最终上下文是不是“真的相关”。

从面试角度讲,这里最值得讲的不是“我用了重排”,而是你要能解释清楚:

  • 为什么只靠向量召回不够
    • 为什么粗召回和精排要分开
    • 为什么 rerank 能提升 top-k 质量但会增加推理成本 这也是典型的工程 trade-off:速度、成本、质量三者不能同时最优,只能做平衡。

如果把这个环节再往前推进一步,它其实已经不是“检索功能”,而是一个小型的检索决策系统:

  • 当问题更像关键词查询时,BM25 检索权重更高
    • 当问题更像语义问答时,Dense Embedding 检索收益更高
    • 当候选集已经比较接近时,Cross-Encoder 或 LLM Rerank 决定最终上下文顺序
    • 当问题包含多个子意图时,先拆成并行子查询,再汇总上下文 这也是为什么这个项目比普通知识库更像“可控的检索引擎”。

五、MCP 集成:让知识库能力变成标准工具

这个项目的另一个亮点,是把 RAG 能力通过 MCP 暴露出去,而不是把它锁死在自定义前端里。

这样做的价值很大:

  • GitHub Copilot、Claude 等支持 MCP 的客户端可以直接调用
    • 不需要为知识库单独开发一套聊天 UI
    • 你可以把知识检索能力当成标准工具,嵌入到更大的 Agent 系统里 从架构上看,MCP 的意义在于把“问答系统”升级成“上下文服务”。

这会带来一个很实用的结果:

你的知识库不再只是一个网站,而是一个可以被各种 AI 客户端调用的能力层。

六、FastAPI + LangGraph:对话编排不是简单的 if-else

项目里还有一个很实用的后端编排层:FastAPI 负责对外 API,LangGraph 负责内部状态流转。

这套设计解决了几个常见问题:

  • 会话怎么维护
    • 多轮对话怎么做记忆
    • 用户问题是否需要澄清
    • 子问题是否需要并行检索
    • 模型超时或异常时怎么降级 实际 workflow 里,大致会经历这些节点:
  1. session:加载会话状态和历史记忆
    1. intent:做意图识别和问题重写
    1. clarify:必要时直接澄清,不进入检索
    1. retrieve:调用 MCP 检索知识
    1. generate:组装上下文并生成回答
    1. output:保存结果与记忆摘要 这套结构比“一个接口里写到底”稳得多,因为它把每个步骤的责任拆开了,也更方便埋点、测试和调试。

更重要的是,这种写法让系统具备了几个高级能力:

  • 条件路由:意图不明确时直接进入澄清分支,而不是硬把错误问题送进检索
    • 并行子查询:复杂问题自动拆分后并发检索,缩短总响应时间
    • 记忆压缩:把近期历史和长期摘要分开管理,降低 prompt 膨胀风险
    • 容错降级:生成模型出问题时自动切换候选模型,而不是让整条链路失败 如果把这一段再抽象一下,它其实讲的是一个更重要的命题:多轮 RAG 的难点不在“能不能答”,而在“能不能稳定地答对、答快、答得可解释”。

如果说传统后端是“请求来了就处理”,那这里更像“请求来了先判断该走哪条工作流”。

七、可观测性和评估:RAG 不能靠感觉调

很多 RAG 项目最后会卡在一个问题上:为什么这次回答好了,下一次又差了?

这个项目的思路是把链路变成可观察对象:

  • ingestion trace:看文档是怎么被处理的

    • query trace:看检索、融合、重排的全过程
    • dashboard:把这些信息可视化
    • evaluation:用评估集和指标去做回归验证 这样调参就不再是“凭感觉改 chunk size”,而是可以围绕具体指标做实验,比如:
  • 召回率有没有提升

    • top-k 命中有没有变好
    • rerank 后的排序是否更合理
    • 某类文档是不是总被漏掉 如果没有这套闭环,RAG 最后会变成“玄学工程”;有了这套闭环,优化才有方向。

这个部分其实最能拉开项目层次。因为真正成熟的 RAG 系统,不只是会回答,还要能回答三个问题:

  • 为什么这次命中了这个 chunk
    • 为什么重排前后顺序变了
    • 为什么某次改动让指标变好了或者变差了 能回答这三个问题,项目才算进入了“可运营”阶段,而不只是“可演示”阶段。

八、这个项目最值得学习的地方

如果你是想把这个项目写进简历或用于面试,我建议你不要只背功能点,而要把它归纳成三句话:

  1. 我做了一个模块化 RAG 平台,支持摄取、检索、生成、评估的完整链路。
    1. 我把知识库能力通过 MCP 标准化暴露出去,可以直接接入 Copilot 等 AI 客户端。
    1. 我建立了可观测和可评估机制,让 RAG 优化从经验驱动变成数据驱动。 这三句话比“我用了很多框架”更有说服力,因为它体现的是系统设计能力,而不是 API 调包能力。

结语

我理解这个项目的过程里,最大的收获不是“学会了某个库”,而是对 RAG 工程化有了更完整的认识:

  • 文档处理要讲结构
    • 检索要讲召回与排序的平衡
    • 服务层要讲标准接口与可扩展性
    • 评估要讲数据闭环 如果把它当作一个学习项目,它能帮你建立 RAG 的全局认知;如果把它当作一个面试项目,它能帮你讲清楚工程设计和权衡取舍;如果把它当作一个底座,它还能继续扩展成 Agent、知识中台或多模态问答系统。

这也是我认为它最有价值的地方:它不是一个终点,而是一个可以持续演进的起点。